package hudson.plugins.openshift;
import com.openshift.client.*;
import com.openshift.client.cartridge.ICartridge;
import com.openshift.client.cartridge.IStandaloneCartridge;
import com.openshift.client.cartridge.StandaloneCartridge;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.Descriptor.FormException;
import hudson.model.Hudson;
import hudson.model.Queue;
import hudson.model.TaskListener;
import hudson.slaves.AbstractCloudComputer;
import hudson.slaves.AbstractCloudSlave;
import hudson.slaves.CloudRetentionStrategy;
import hudson.slaves.NodeProperty;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
public class OpenShiftSlave extends AbstractCloudSlave {
private static final long serialVersionUID = 8486485671018263774L;
private static final Logger LOGGER = Logger.getLogger(OpenShiftSlave.class
.getName());
private String applicationUUID;
private String builderType;
private final String builderSize;
private final String builderPlatform;
private final long builderTimeout;
private String uuid;
/**
* The name of the slave should be the 'sanitized version of the framework
* that is used, removing all '.' and '-' characters (i.e. jbossas70, php53)
* <p/>
* The framework should be the exact OpenShift framework used (i.e.
* jbossas-7)
*/
@DataBoundConstructor
public OpenShiftSlave(String name, String applicationUUID, String builderType, String builderSize, String builderPlatform,
String label, long builderTimeout, int executors, int slaveIdleTimeToLive) throws FormException, IOException {
super(name, "Builder for " + label, null, executors, Mode.NORMAL,
label, new OpenShiftComputerLauncher(),
new CloudRetentionStrategy(slaveIdleTimeToLive), Collections
.<NodeProperty<?>>emptyList()
);
LOGGER.info("Creating slave with " + slaveIdleTimeToLive + "mins time-to-live");
this.applicationUUID = applicationUUID;
this.builderType = builderType;
this.builderSize = builderSize;
this.builderPlatform = builderPlatform;
this.builderTimeout = builderTimeout;
}
private String getNamespace() {
return System.getenv("OPENSHIFT_NAMESPACE");
}
@Override
public String getRemoteFS() {
return "/var/lib/openshift/" + uuid + "/app-root/data/jenkins";
}
@Override
public FilePath getRootPath() {
return createPath(getRemoteFS());
}
@SuppressWarnings("unchecked")
@Override
public AbstractCloudComputer<OpenShiftSlave> createComputer() {
return new OpenShiftComputer(this);
}
@Override
protected void _terminate(TaskListener listener) throws IOException,
InterruptedException {
LOGGER.info("Terminating slave " + name + " (uuid: " + uuid + ")");
if (getComputer() != null && getComputer().getChannel() != null) {
LOGGER.info("Closing the SSH channel...");
getComputer().getChannel().close();
}
LOGGER.info("Terminating OpenShift application...");
terminateApp();
}
protected IStandaloneCartridge getCartridge(IOpenShiftConnection connection) throws OpenShiftException {
if(applicationUUID!=null && !applicationUUID.equals("")) {
// new build configs provide the application uuid for cloning
IApplication baseApp = Util.getApplicationFromUuid(applicationUUID);
if(baseApp==null) {
throw new OpenShiftException("Could not locate application with UUID "+applicationUUID);
}
if(baseApp.getCartridge().getUrl()!=null) {
// downloadable cartridge
return new StandaloneCartridge(baseApp.getCartridge().getName(), baseApp.getCartridge().getUrl());
} else {
// cartridge from repository
String cartridgeType=baseApp.getCartridge().getName();
List<IStandaloneCartridge> cartridges = connection.getStandaloneCartridges();
for (IStandaloneCartridge cartridge : cartridges) {
if (cartridge.getName().equals(cartridgeType)) {
return cartridge;
}
}
throw new OpenShiftException("Cartridge for " + cartridgeType + " not found");
}
} else {
// old configs provided the builder type.
String targetCartridgeName = builderType.replace("redhat-", "");
List<IStandaloneCartridge> cartridges = connection.getStandaloneCartridges();
for (IStandaloneCartridge cartridge : cartridges) {
if (cartridge.getName().equals(targetCartridgeName)) {
return cartridge;
}
}
throw new OpenShiftException("Cartridge for " + targetCartridgeName + " not found");
}
}
private void terminateApp() {
try {
getBuilderApplication().destroy();
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Unable to terminate builder application", e);
}
}
@Extension
public static final class DescriptorImpl extends SlaveDescriptor {
public String getDisplayName() {
return "OpenShift Slave";
}
@Override
public boolean isInstantiable() {
return false;
}
}
public String getHostName() throws IOException {
try {
IApplication app = getBuilderApplication();
String url = null;
String type = getCartridge(OpenShiftCloud.get().getOpenShiftConnection()).getName();
for (IGearGroup gearGroup : app.getGearGroups()) {
for(ICartridge cart : gearGroup.getCartridges()) {
if(cart.getName().equals(type)) {
url = ((IGear) gearGroup.getGears().toArray()[0]).getSshUrl();
break;
}
}
if(url != null) break;
}
if(url == null) {
throw new IOException("Unable to find ssh url for " + name);
}
if (url.indexOf("@") != -1)
url = url.substring(url.indexOf("@") + 1);
url = url.replace("/", "");
return url;
} catch (Exception e) {
throw new IOException("Unable to find application url for " + name, e);
}
}
public void connect(boolean delayDNS) throws IOException {
LOGGER.info("Connecting to slave " + name + "...");
try {
// Force a refresh of the user info to get the application UUID
IApplication app = getBuilderApplication();
if (app == null)
throw new IOException("Failed to connect/find application " + name);
uuid = app.getGearGroups().iterator().next().getGears().iterator().next().getId();
LOGGER.info("Established UUID = " + uuid);
} catch (Exception e) {
throw new IOException("Unable to connect to application " + name, e);
}
// Sleep for 5 seconds for DNS to propagate to minimize cache penalties
if (delayDNS) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Ignore
}
}
long startTime = System.currentTimeMillis();
long currentTime = startTime;
// Wait until DNS is resolvable
while (isBuildRunning() && (builderTimeout == -1 || currentTime - startTime < builderTimeout)) {
try {
String hostname = getHostName();
LOGGER.info("Checking to see if slave DNS for " + hostname + " is resolvable ... (timeout: " + builderTimeout + "ms)");
InetAddress address = InetAddress.getByName(hostname);
LOGGER.info("Slave DNS resolved - " + address);
break;
} catch (UnknownHostException e) {
LOGGER.info("Slave DNS not propagated yet, retrying... (remaining: " + (builderTimeout - (currentTime - startTime)) + "ms)");
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
// Ignore interruptions
}
currentTime = System.currentTimeMillis();
}
}
if (builderTimeout >= 0 && currentTime - startTime >= builderTimeout) {
LOGGER.warning("Slave DNS not propagated. Timing out.");
throw new IOException("Slave DNS not propagated. Timing out.");
}
}
protected boolean isBuildRunning() {
boolean running = true;
Queue queue = Hudson.getInstance().getQueue();
if (queue != null) {
Queue.Item[] items = queue.getItems();
if (items.length == 0)
running = false;
}
return running;
}
public void provision() throws Exception {
// Create a new application of the right type
createApp();
// Force a connection to establish the UUID
connect(true);
}
private void createApp() throws IOException, OpenShiftException {
IOpenShiftConnection connection = OpenShiftCloud.get().getOpenShiftConnection();
IUser user = connection.getUser();
IStandaloneCartridge cartridge = getCartridge(OpenShiftCloud.get().getOpenShiftConnection());
IDomain domain = user.getDomain(getNamespace());
List<IGearProfile> gearProfiles = domain.getAvailableGearProfiles();
IGearProfile gearProfile = gearProfiles.get(0);
for (IGearProfile profile : gearProfiles) {
if (profile.getName().equals(builderSize)) {
gearProfile = profile;
}
}
LOGGER.info("Creating builder application " + cartridge.getName() + " "
+ name + " " + user.getDomain(getNamespace()).getId() + " of size "
+ gearProfile.getName() + " ...");
ApplicationScale scale = ApplicationScale.NO_SCALE;
if(builderPlatform.equalsIgnoreCase(Platform.WINDOWS.toString())) {
scale = ApplicationScale.SCALE;
}
IApplication app = domain.createApplication(name, cartridge, scale, gearProfile);
// No reason to have app running on builder gear - just need it installed
LOGGER.info("Stopping application on builder gear ...");
app.stop();
}
private IApplication getBuilderApplication() {
IUser user;
try {
user = OpenShiftCloud.get().getOpenShiftConnection().getUser();
} catch (IOException e) {
throw new RuntimeException(e);
}
return user.getDomain(getNamespace()).getApplicationByName(name);
}
public String getUuid() {
return uuid;
}
public enum Platform {
WINDOWS("Windows"),
LINUX("Linux");
private final String platform;
private Platform(String s) {
platform = s;
}
public String toString(){
return platform;
}
}
}