package hudson.plugins.virtualization; import hudson.util.Secret; import hudson.util.FormValidation; import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.model.Label; import hudson.Extension; import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import java.util.Map; import java.util.HashMap; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.ArrayList; import java.util.Set; import java.util.HashSet; import java.util.TreeMap; import java.util.SortedMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import java.util.logging.Level; import java.util.logging.LogRecord; import java.io.IOException; import net.java.dev.vcc.api.Computer; import net.java.dev.vcc.api.ManagedObjectId; import net.java.dev.vcc.api.Datacenter; import net.java.dev.vcc.api.DatacenterManager; import javax.servlet.ServletException; /** * Represents a virtual datacenter. */ public class VirtualDatacenter extends Cloud { private static final Logger LOGGER = Logger.getLogger(VirtualDatacenter.class.getName()); private final String datacenterUri; private final String username; private final Secret password; private final int refreshSeconds; private transient Map<ManagedObjectId<Computer>, Computer> computers = null; private transient SortedMap<String, VirtualComputer> virtualComputers = null; private transient long nextRefresh = 0; private transient Thread updatingCacheThread = null; private transient Datacenter datacenter = null; @DataBoundConstructor public VirtualDatacenter(String datacenterUri, String username, String password, int refreshSeconds) { super("vcc-api"); this.datacenterUri = datacenterUri; this.username = username; this.password = Secret.fromString(password.trim()); this.refreshSeconds = refreshSeconds <= 0 ? 60 : refreshSeconds; updateComputersCache(); } protected Object readResolve() { updateComputersCache(); return this; } public String getDatacenterUri() { return datacenterUri; } public String getUsername() { return username; } public String getPassword() { return password.getEncryptedValue(); } public int getRefreshSeconds() { return refreshSeconds; } private synchronized Thread updateComputersCache() { if (updatingCacheThread == null || !updatingCacheThread.isAlive()) { updatingCacheThread = new Thread() { @Override public void run() { LOGGER.info("Starting cache update"); Map<ManagedObjectId<Computer>, Computer> computers = new HashMap<ManagedObjectId<Computer>, Computer>(); SortedMap<String, VirtualComputer> virtualComputers = new TreeMap<String, VirtualComputer>(); Set<String> removeNames = new HashSet<String>(); synchronized (VirtualDatacenter.this) { if (VirtualDatacenter.this.virtualComputers != null) { virtualComputers.putAll(VirtualDatacenter.this.virtualComputers); removeNames.addAll(VirtualDatacenter.this.virtualComputers.keySet()); } } try { getConnection(); for (Computer c : datacenter.getAllComputers()) { computers.put(c.getId(), c); if (!virtualComputers.containsKey(c.getName())) { virtualComputers.put(c.getName(), new VirtualComputer(VirtualDatacenter.this, c.getName())); } removeNames.remove(c.getName()); } for (String name: removeNames) { virtualComputers.remove(name); } LOGGER.info("Saving updated cache"); synchronized (VirtualDatacenter.this) { VirtualDatacenter.this.computers = computers; VirtualDatacenter.this.virtualComputers = virtualComputers; nextRefresh = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(refreshSeconds); updatingCacheThread = null; } } catch (IOException e) { LogRecord rec = new LogRecord(Level.SEVERE, "Cannot connect to datacenter {0} as {1}/******"); rec.setThrown(e); rec.setParameters(new Object[]{datacenterUri, username}); LOGGER.log(rec); } catch (InterruptedException e) { LogRecord rec = new LogRecord(Level.SEVERE, "Cannot connect to datacenter {0} as {1}/******"); rec.setThrown(e); rec.setParameters(new Object[]{datacenterUri, username}); LOGGER.log(rec); } finally { LOGGER.info("Finished updating cache"); } } }; updatingCacheThread.start(); } return updatingCacheThread; } public Datacenter getConnection() throws IOException, InterruptedException { LOGGER.info("Checking for connection"); try { synchronized (this) { if (datacenter == null || !datacenter.isOpen()) { LOGGER.info("Reconnect"); datacenter = MakeConnectionThread.getConnection(datacenterUri, username, password.toString()); } return datacenter; } } finally { LOGGER.info("Have connection"); } } public synchronized Map<ManagedObjectId<Computer>, Computer> getComputers() { if (computers == null || System.currentTimeMillis() > nextRefresh) { updateComputersCache(); } return computers == null ? new HashMap<ManagedObjectId<Computer>, Computer>() : computers; } public synchronized Map<String, VirtualComputer> getVirtualComputers() { if (virtualComputers == null || System.currentTimeMillis() > nextRefresh) { updateComputersCache(); } return virtualComputers == null ? new HashMap<String, VirtualComputer>() : virtualComputers; } public Collection<NodeProvisioner.PlannedNode> provision(Label label, int i) { return Collections.emptySet(); } public boolean canProvision(Label label) { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("VirtualDatacenter"); sb.append("{datacenterUri='").append(datacenterUri).append('\''); sb.append(", username='").append(username).append('\''); sb.append('}'); return sb.toString(); } public Descriptor<Cloud> getDescriptor() { return Hudson.getInstance().getDescriptor(getClass()); } @Extension public static final class DescriptorImpl extends Descriptor<Cloud> { public final ConcurrentMap<String,VirtualDatacenter> datacenters = new ConcurrentHashMap<String, VirtualDatacenter>(); public VirtualDatacenter lookupDatacenter(String username, String datacenterUri) { for (Cloud cloud: Hudson.getInstance().clouds) { if (cloud instanceof VirtualDatacenter) { VirtualDatacenter datacenter = (VirtualDatacenter) cloud; if (username.equals(datacenter.getUsername()) && datacenterUri.equals(datacenter.getDatacenterUri())) { return datacenter; } } } return null; } public String getDisplayName() { return "Virtual Datacenter (via vcc-api)"; } public FormValidation doTestConnection( @QueryParameter String datacenterUri, @QueryParameter String username, @QueryParameter String password) throws IOException, ServletException { try { if (datacenterUri == null) { return FormValidation.error("Datacenter URI is not specified"); } if (!datacenterUri.startsWith("vcc+")) { return FormValidation.error("Datacenter URI is not a valid vcc-api URI"); } if (username == null) { return FormValidation.error("Username is not specified"); } if (password == null) { return FormValidation.error("Password is not specified"); } Datacenter datacenter = MakeConnectionThread .getConnection(datacenterUri, username, Secret.fromString(password).toString()); datacenter.close(); return FormValidation.ok("Connected successfully"); } catch (IOException e) { LogRecord rec = new LogRecord(Level.WARNING, "Failed to check datacenter connection to {0} as {1}/******"); rec.setThrown(e); rec.setParameters(new Object[]{datacenterUri, username}); LOGGER.log(rec); return FormValidation.error(e.getMessage()); } catch (InterruptedException e) { LogRecord rec = new LogRecord(Level.WARNING, "Failed to check datacenter connection to {0} as {1}/******"); rec.setThrown(e); rec.setParameters(new Object[]{datacenterUri, username}); LOGGER.log(rec); return FormValidation.error(e.getMessage()); } } } static final class MakeConnectionThread extends Thread { private final String datacenterUri; private final String username; private final char[] password; private volatile Datacenter datacenter = null; private volatile IOException ioException = null; private MakeConnectionThread(String datacenterUri, String username, char[] password) { this.datacenterUri = datacenterUri; this.username = username; this.password = password; } public static Datacenter getConnection(String datacenterUri, String username, String password) throws IOException, InterruptedException { MakeConnectionThread t = new MakeConnectionThread(datacenterUri, username, password.toCharArray()); t.setContextClassLoader(VirtualDatacenter.class.getClassLoader()); t.start(); t.join(); if (t.datacenter != null) { LOGGER.log(Level.INFO, "Have connection to datacenter URI: {0} as {1}/******", new Object[]{datacenterUri, username}); return t.datacenter; } if (t.ioException == null) { throw new IOException("Unknown error trying to establish a connection"); } throw t.ioException; } @Override public void run() { try { LOGGER.log(Level.INFO, "Trying to establish a connection to datacenter URI: {0} as {1}/******", new Object[]{datacenterUri, username}); datacenter = DatacenterManager.getConnection(datacenterUri, username, password); LOGGER.log(Level.INFO, "Established connection to datacenter URI: {0} as {1}/******", new Object[]{datacenterUri, username}); } catch (IOException e) { LogRecord rec = new LogRecord(Level.WARNING, "Failed to establish connection to datacenter URI: {0} as {1}/******"); rec.setThrown(e); rec.setParameters(new Object[]{datacenterUri, username}); LOGGER.log(rec); ioException = e; } } } }