diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37cf854 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +work diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a77afbf --- /dev/null +++ b/pom.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +The MIT License + +(c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.jenkins-ci.plugins</groupId> + <artifactId>plugin</artifactId> + <version>1.580.1</version> + </parent> + <groupId>com.parallels</groupId> + <artifactId>parallels-desktop</artifactId> + <version>0.0</version> + <packaging>hpi</packaging> + + <name>Parallels Desktop Cloud</name> + <url>http://parallels.com</url> + <licenses> + <license> + <name>MIT License</name> + <url>http://opensource.org/licenses/MIT</url> + </license> + </licenses> + <!-- Assuming you want to host on @jenkinsci: + <scm> + <connection>scm:git:git://github.com/jenkinsci/${project.artifactId}-plugin.git</connection> + <developerConnection>scm:git:git@github.com:jenkinsci/${project.artifactId}-plugin.git</developerConnection> + <url>http://github.com/jenkinsci/${project.artifactId}-plugin</url> + </scm> + --> + <repositories> + <repository> + <id>repo.jenkins-ci.org</id> + <url>http://repo.jenkins-ci.org/public/</url> + </repository> + </repositories> + <pluginRepositories> + <pluginRepository> + <id>repo.jenkins-ci.org</id> + <url>http://repo.jenkins-ci.org/public/</url> + </pluginRepository> + </pluginRepositories> +</project> diff --git a/src/main/java/com/parallels/desktopcould/ParallelsDesktopCloud.java b/src/main/java/com/parallels/desktopcould/ParallelsDesktopCloud.java new file mode 100644 index 0000000..10c0d03 --- /dev/null +++ b/src/main/java/com/parallels/desktopcould/ParallelsDesktopCloud.java @@ -0,0 +1,162 @@ +/* + * The MIT License + * + * (c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.parallels.desktopcloud; + +import hudson.Extension; +import hudson.model.Hudson; +import hudson.model.Computer; +import hudson.model.Descriptor; +import hudson.model.Label; +import hudson.model.Node; +import hudson.slaves.Cloud; +import hudson.slaves.ComputerLauncher; +import hudson.slaves.NodeProvisioner; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.kohsuke.stapler.DataBoundConstructor; + + +public final class ParallelsDesktopCloud extends Cloud +{ + private static final Logger LOGGER = Logger.getLogger(ParallelsDesktopCloud.class.getName()); + private static final AtomicInteger counter = new AtomicInteger(); + + private final List<ParallelsDesktopVM> vms; + private final ComputerLauncher pdLauncher; + private final String remoteFS; + + @DataBoundConstructor + public ParallelsDesktopCloud(String name, String remoteFS, ComputerLauncher pdLauncher, + List<ParallelsDesktopVM> vms) + { + super(name); + this.remoteFS = remoteFS; + if (vms == null) + this.vms = Collections.emptyList(); + else + this.vms = vms; + this.pdLauncher = pdLauncher; + } + + @Override + public Collection<NodeProvisioner.PlannedNode> provision(Label label, int excessWorkload) + { + LOGGER.log(Level.SEVERE, "Going to provision " + excessWorkload + " executors"); + Collection<NodeProvisioner.PlannedNode> result = new ArrayList<NodeProvisioner.PlannedNode>(); + final ParallelsDesktopConnectorSlaveComputer connector = getConnector(); + for (int i = 0; (i < vms.size()) && (excessWorkload > 0); i++) + { + final ParallelsDesktopVM vm = vms.get(i); + if (label.matches(Label.parse(vm.getLabels()))) + { + final int count = counter.incrementAndGet(); + final String vmId = vm.getVmid(); + final String slaveName = "PD Slave #" + count; + vm.setSlaveName(slaveName); + --excessWorkload; + result.add(new NodeProvisioner.PlannedNode(slaveName, + Computer.threadPoolForRemoting.submit(new Callable<Node>() + { + @Override + public Node call() throws Exception + { + connector.checkVmExists(vmId); + return connector.createSlaveOnVM(vm); + } + }), 1)); + } + } + return result; + } + + private ParallelsDesktopConnectorSlaveComputer getConnector() + { + try + { + ParallelsDesktopConnectorSlave slave = new ParallelsDesktopConnectorSlave("PDConnectorSlave", remoteFS, pdLauncher); + Hudson.getInstance().addNode(slave); + return (ParallelsDesktopConnectorSlaveComputer)slave.toComputer(); + } + catch (Descriptor.FormException ex) + { + LOGGER.log(Level.SEVERE, null, ex); + } + catch (IOException ex) + { + LOGGER.log(Level.SEVERE, null, ex); + } + catch(Exception ex) + { + LOGGER.log(Level.SEVERE, null, ex); + } + return null; + } + + @Override + public boolean canProvision(Label label) + { + if (label != null) + { + for (ParallelsDesktopVM vm : vms) + { + if (label.matches(Label.parse(vm.getLabels()))) + return true; + } + } + return false; + } + + public List<ParallelsDesktopVM> getVms() + { + return vms; + } + + public ComputerLauncher getPdLauncher() + { + return pdLauncher; + } + + public String getRemoteFS() + { + return remoteFS; + } + + @Extension + public static final class DescriptorImpl extends Descriptor<Cloud> + { + @Override + public String getDisplayName() + { + return "Parallels Desktop Cloud"; + } + } +} diff --git a/src/main/java/com/parallels/desktopcould/ParallelsDesktopCloudRetentionStrategy.java b/src/main/java/com/parallels/desktopcould/ParallelsDesktopCloudRetentionStrategy.java new file mode 100644 index 0000000..ca47182 --- /dev/null +++ b/src/main/java/com/parallels/desktopcould/ParallelsDesktopCloudRetentionStrategy.java @@ -0,0 +1,95 @@ +/* + * The MIT License + * + * (c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.parallels.desktopcloud; + +import hudson.slaves.RetentionStrategy; +import hudson.model.Hudson; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.DataBoundConstructor; + + +public class ParallelsDesktopCloudRetentionStrategy extends RetentionStrategy<ParallelsDesktopVMSlaveComputer> +{ + private static final Logger LOGGER = Logger.getLogger("PDCloudRetentionStrategy"); + + @DataBoundConstructor + public ParallelsDesktopCloudRetentionStrategy() + { + super(); + } + + @Override + public long check(ParallelsDesktopVMSlaveComputer c) + { + LOGGER.log(Level.SEVERE, "Check VM computer " + c.getName()); + if (c.isOffline()) + return 1; + if (c.isIdle()) + { + LOGGER.log(Level.SEVERE, "Stopping node..."); + c.getNode().stop(); + try + { + LOGGER.log(Level.SEVERE, "Disconnecting computer..."); + c.disconnect(null).get(); + LOGGER.log(Level.SEVERE, "Removing node..."); + Hudson.getInstance().removeNode(c.getNode()); + LOGGER.log(Level.SEVERE, "Node was removed."); + } + catch (Exception e) + { + LOGGER.log(Level.SEVERE, e.toString()); + } + } + return 1; + } + + @Override + public void start(ParallelsDesktopVMSlaveComputer c) + { + LOGGER.log(Level.SEVERE, "Starting VM computer " + c.getName()); + c.connect(false); + } + + @Override + public DescriptorImpl getDescriptor() + { + return DESCRIPTOR; + } + + @Restricted(NoExternalUse.class) + public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); + public static final class DescriptorImpl extends hudson.model.Descriptor<RetentionStrategy<?>> + { + @Override + public String getDisplayName() + { + return "ParallelsDesktop Cloud Retention Strategy"; + } + } +} diff --git a/src/main/java/com/parallels/desktopcould/ParallelsDesktopConnectorSlave.java b/src/main/java/com/parallels/desktopcould/ParallelsDesktopConnectorSlave.java new file mode 100644 index 0000000..7f6affd --- /dev/null +++ b/src/main/java/com/parallels/desktopcould/ParallelsDesktopConnectorSlave.java @@ -0,0 +1,72 @@ +/* + * The MIT License + * + * (c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.parallels.desktopcloud; + +import hudson.model.Computer; +import hudson.model.Descriptor; +import hudson.model.Slave; +import hudson.model.Node.Mode; +import hudson.slaves.ComputerLauncher; +import hudson.slaves.NodeProperty; +import hudson.slaves.RetentionStrategy; +import hudson.Extension; +import java.io.IOException; +import java.util.ArrayList; +import org.kohsuke.stapler.DataBoundConstructor; + + +public class ParallelsDesktopConnectorSlave extends Slave +{ + @DataBoundConstructor + public ParallelsDesktopConnectorSlave(String name, String remoteFS, ComputerLauncher launcher) + throws IOException, Descriptor.FormException + { + super(name, "", remoteFS, 1, Mode.NORMAL, "", launcher, + new RetentionStrategy.Demand(1, 1), new ArrayList<NodeProperty<?>>()); + } + + @Override + public Computer createComputer() + { + return new ParallelsDesktopConnectorSlaveComputer(this); + } + + @Override + public String getRemoteFS() + { + String res = super.getRemoteFS(); + return res; + } + + @Extension + public static final class DescriptorImpl extends SlaveDescriptor + { + @Override + public String getDisplayName() + { + return "Parallels Desktop connector slave"; + } + } +} diff --git a/src/main/java/com/parallels/desktopcould/ParallelsDesktopConnectorSlaveComputer.java b/src/main/java/com/parallels/desktopcould/ParallelsDesktopConnectorSlaveComputer.java new file mode 100644 index 0000000..4f3d40e --- /dev/null +++ b/src/main/java/com/parallels/desktopcould/ParallelsDesktopConnectorSlaveComputer.java @@ -0,0 +1,196 @@ +/* + * The MIT License + * + * (c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.parallels.desktopcloud; + +import java.lang.reflect.Field; + +import hudson.model.Node; +import hudson.model.Slave; +import hudson.remoting.Channel; +import hudson.slaves.SlaveComputer; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.concurrent.ExecutionException; +import java.lang.Thread; +import jenkins.security.MasterToSlaveCallable; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import net.sf.json.JSONSerializer; + + +public class ParallelsDesktopConnectorSlaveComputer extends SlaveComputer +{ + private static final Logger LOGGER = Logger.getLogger("PDConnectorSlaveComputer"); + + public ParallelsDesktopConnectorSlaveComputer(Slave slave) + { + super(slave); + } + + public boolean checkVmExists(String vmId) + { + try + { + RunVmCallable command = new RunVmCallable("list", "-i", "--json"); + Channel channel = getChannel(); + if (channel == null) + { + LOGGER.log(Level.SEVERE, "Connecting to node"); + connect(false).get(); + } + channel = getChannel(); + String callResult = channel.call(command); + JSONArray vms = (JSONArray)JSONSerializer.toJSON(callResult); + for (int i = 0; i < vms.size(); i++) + { + JSONObject vmInfo = vms.getJSONObject(i); + if (vmId.equals(vmInfo.getString("ID")) || vmId.equals(vmInfo.getString("Name"))) + return true; + } + return true; + } + catch (IOException ex) + { + LOGGER.log(Level.SEVERE, ex.toString()); + } + catch (InterruptedException ex) + { + LOGGER.log(Level.SEVERE, ex.toString()); + } + catch (ExecutionException ex) + { + LOGGER.log(Level.SEVERE, ex.toString()); + } + return false; + } + + private String getVmIPAddress(String vmId) throws Exception + { + int TIMEOUT = 60; + for (int i = 0; i < TIMEOUT; ++i) + { + RunVmCallable command = new RunVmCallable("list", "-f", "--json", vmId); + String callResult = getChannel().call(command); + LOGGER.log(Level.SEVERE, " - (" + i + "/" + TIMEOUT + ") calling for IP"); + LOGGER.log(Level.SEVERE, callResult); + JSONArray vms = (JSONArray)JSONSerializer.toJSON(callResult); + JSONObject vmInfo = vms.getJSONObject(0); + String ip = vmInfo.getString("ip_configured"); + if (!ip.equals("-")) + return ip; + Thread.sleep(1000); + } + throw new Exception("Failed to get IP for VM '" + vmId + "'"); + } + + public Node createSlaveOnVM(ParallelsDesktopVM vm) throws Exception + { + String vmId = vm.getVmid(); + String slaveName = vm.getSlaveName(); + LOGGER.log(Level.SEVERE, "Starting slave '" + slaveName+ "'"); + LOGGER.log(Level.SEVERE, "Starting virtual machine '" + vmId + "'"); + RunVmCallable command = new RunVmCallable("start", vmId); + String callResult = getChannel().call(command); + LOGGER.log(Level.SEVERE, "Waiting for IP..."); + String ip = getVmIPAddress(vmId); + LOGGER.log(Level.SEVERE, "Got IP address for VM " + vmId + ": " + ip); + try + { + Class<?> c = vm.getLauncher().getClass(); + Field f = c.getDeclaredField("host"); + f.setAccessible(true); + f.set(vm.getLauncher(), ip); + f.setAccessible(false); + } + catch (NoSuchFieldException x) + { + LOGGER.log(Level.SEVERE, "No 'host' field in launcher of " + slaveName); + } + return new ParallelsDesktopVMSlave(vm, this); + } + + public void stopVM(String vmId) + { + try + { + LOGGER.log(Level.SEVERE, "Suspending..."); + RunVmCallable command = new RunVmCallable("suspend", vmId); + String res = getChannel().call(command); + LOGGER.log(Level.SEVERE, res); + } + catch (Exception ex) + { + LOGGER.log(Level.SEVERE, ex.toString()); + } + } + + private static final class RunVmCallable extends MasterToSlaveCallable<String, IOException> + { + private static final String cmd = "/usr/local/bin/prlctl"; + private final String[] params; + + public RunVmCallable(String... params) + { + this.params = params; + } + + @Override + public String call() throws IOException + { + List<String> cmds = new ArrayList<String>(); + cmds.add(cmd); + cmds.addAll(Arrays.asList(this.params)); + + LOGGER.log(Level.SEVERE, "Running command:"); + for (String s: cmds) + LOGGER.log(Level.SEVERE, " [" + s + "]"); + ProcessBuilder pb = new ProcessBuilder(cmds); + pb.redirectErrorStream(true); + Process pr = pb.start(); + BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream())); + String line; + String result = ""; + while ((line = in.readLine()) != null) + { + result += line; + } + try + { + pr.waitFor(); + } + catch (InterruptedException ex) + { + LOGGER.log(Level.SEVERE, ex.toString()); + } + return result; + } + } +} diff --git a/src/main/java/com/parallels/desktopcould/ParallelsDesktopVM.java b/src/main/java/com/parallels/desktopcould/ParallelsDesktopVM.java new file mode 100644 index 0000000..c253593 --- /dev/null +++ b/src/main/java/com/parallels/desktopcould/ParallelsDesktopVM.java @@ -0,0 +1,97 @@ +/* + * The MIT License + * + * (c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.parallels.desktopcloud; + +import hudson.Extension; +import hudson.model.Describable; +import hudson.model.Descriptor; +import hudson.slaves.ComputerLauncher; +import jenkins.model.Jenkins; +import org.kohsuke.stapler.DataBoundConstructor; + + +public class ParallelsDesktopVM implements Describable<ParallelsDesktopVM> +{ + private String vmid; + private String labels; + private String remoteFS; + private String slaveName; + private ComputerLauncher launcher; + + @DataBoundConstructor + public ParallelsDesktopVM(String vmid, String labels, String remoteFS, ComputerLauncher launcher) + { + this.vmid = vmid; + this.labels = labels; + this.remoteFS = remoteFS; + this.launcher = launcher; + } + + public String getVmid() + { + return vmid; + } + + public String getLabels() + { + return labels; + } + + public String getRemoteFS() + { + return remoteFS; + } + + public ComputerLauncher getLauncher() + { + return launcher; + } + + public void setSlaveName(String slaveName) + { + this.slaveName = slaveName; + } + + public String getSlaveName() + { + return slaveName; + } + + @Override + public Descriptor<ParallelsDesktopVM> getDescriptor() + { + return Jenkins.getInstance().getDescriptor(getClass()); + } + + @Extension + public static final class DescriptorImpl extends Descriptor<ParallelsDesktopVM> + { + @Override + public String getDisplayName() + { + return "Parallels Desktop virtual machine"; + } + } +} diff --git a/src/main/java/com/parallels/desktopcould/ParallelsDesktopVMSlave.java b/src/main/java/com/parallels/desktopcould/ParallelsDesktopVMSlave.java new file mode 100644 index 0000000..ba40250 --- /dev/null +++ b/src/main/java/com/parallels/desktopcould/ParallelsDesktopVMSlave.java @@ -0,0 +1,77 @@ +/* + * The MIT License + * + * (c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.parallels.desktopcloud; + +import hudson.model.Descriptor; +import hudson.model.Slave; +import hudson.model.Node.Mode; +import hudson.model.Computer; +import hudson.slaves.NodeProperty; +import hudson.Extension; +import java.io.IOException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.kohsuke.stapler.DataBoundConstructor; + + +public class ParallelsDesktopVMSlave extends Slave +{ + private static final Logger LOGGER = Logger.getLogger("PDVMSlave"); + private final transient ParallelsDesktopConnectorSlaveComputer connector; + private final ParallelsDesktopVM vm; + + @DataBoundConstructor + public ParallelsDesktopVMSlave(ParallelsDesktopVM vm, ParallelsDesktopConnectorSlaveComputer connector) + throws IOException, Descriptor.FormException + { + super(vm.getSlaveName(), "", vm.getRemoteFS(), 1, Mode.NORMAL, vm.getLabels(), vm.getLauncher(), + new ParallelsDesktopCloudRetentionStrategy(), new ArrayList<NodeProperty<?>>()); + this.connector = connector; + this.vm = vm; + } + + @Override + public Computer createComputer() + { + return new ParallelsDesktopVMSlaveComputer(this); + } + + public void stop() + { + LOGGER.log(Level.SEVERE, "!!! Stop node '" + getNodeName() + "', id '" + vm.getVmid() + "'"); + connector.stopVM(vm.getVmid()); + } + + @Extension + public static final class DescriptorImpl extends SlaveDescriptor + { + @Override + public String getDisplayName() + { + return "Parallels Desktop VM slave"; + } + } +} diff --git a/src/main/java/com/parallels/desktopcould/ParallelsDesktopVMSlaveComputer.java b/src/main/java/com/parallels/desktopcould/ParallelsDesktopVMSlaveComputer.java new file mode 100644 index 0000000..8901702 --- /dev/null +++ b/src/main/java/com/parallels/desktopcould/ParallelsDesktopVMSlaveComputer.java @@ -0,0 +1,51 @@ +/* + * The MIT License + * + * (c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.parallels.desktopcloud; + +import hudson.model.Slave; +import hudson.slaves.SlaveComputer; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public class ParallelsDesktopVMSlaveComputer extends SlaveComputer +{ + private static final Logger LOGGER = Logger.getLogger("PDVMSlaveComputer"); + + public ParallelsDesktopVMSlaveComputer(Slave slave) + { + super(slave); + } + + public ParallelsDesktopVMSlave getNode() + { + return (ParallelsDesktopVMSlave)super.getNode(); + } + + protected void onRemoved() + { + LOGGER.log(Level.SEVERE, "!!!!!! ON REMOVED"); + } +} diff --git a/src/main/resources/com/parallels/desktopcloud/ParallelsDesktopCloud/config.jelly b/src/main/resources/com/parallels/desktopcloud/ParallelsDesktopCloud/config.jelly new file mode 100644 index 0000000..f9acbd5 --- /dev/null +++ b/src/main/resources/com/parallels/desktopcloud/ParallelsDesktopCloud/config.jelly @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +The MIT License + +(c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--> + +<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" + xmlns:t="/lib/hudson" xmlns:f="/lib/form"> + <f:entry field="name" title="Name"> + <f:textbox default="Parallels Desktop Cloud"/> + </f:entry> + <f:entry title="${%Remote FS root}" field="remoteFS"> + <f:textbox/> + </f:entry> + + <f:dropdownList name="pdLauncher" title="${%Secondary launch method}" + help="${descriptor.getHelpFile('launcher')}"> + <j:forEach var="d" items="${h.getComputerLauncherDescriptors()}" varStatus="loop"> + <f:dropdownListBlock value="${d.clazz.name}" name="${d.displayName}" + selected="${it.delegateLauncher.descriptor==d}" + title="${d.displayName}"> + <j:set var="descriptor" value="${d}"/> + <j:set var="instance" + value="${it.delegateLauncher.descriptor==d ? it.delegateLauncher : null}"/> + <tr> + <td> + <input type="hidden" name="stapler-class" value="${d.clazz.name}"/> + </td> + </tr> + <st:include from="${d}" page="${d.configPage}" optional="true"/> + </f:dropdownListBlock> + </j:forEach> + </f:dropdownList> + + <f:entry title="${%Virtual Machines}" description="${%List of virtual machines to be launched as slaves}"> + <f:repeatable field="vms"> + <div> + <table width="100%"> + <st:include page="config.jelly" from="${descriptor}" + class="com.parallels.desktopcloud.ParallelsDesktopVM" /> + </table> + </div> + <div align="right"> + <f:repeatableDeleteButton/> + </div> + </f:repeatable> + </f:entry> +</j:jelly> diff --git a/src/main/resources/com/parallels/desktopcloud/ParallelsDesktopSlave/configure-entries.jelly b/src/main/resources/com/parallels/desktopcloud/ParallelsDesktopSlave/configure-entries.jelly new file mode 100644 index 0000000..7a0912a --- /dev/null +++ b/src/main/resources/com/parallels/desktopcloud/ParallelsDesktopSlave/configure-entries.jelly @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +The MIT License + +(c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--> + +<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" + xmlns:t="/lib/hudson" xmlns:f="/lib/form"> + + <f:entry title="${%Description}" help="/help/system-config/master-slave/description.html"> + <f:textbox field="nodeDescription"/> + </f:entry> + + <f:entry title="${%Remote FS root}" field="remoteFS"> + <f:textbox/> + </f:entry> + + <f:dropdownList name="slave.delegateLauncher" title="${%Secondary launch method}" + help="${descriptor.getHelpFile('launcher')}"> + <j:forEach var="d" items="${h.getComputerLauncherDescriptors()}" varStatus="loop"> + <f:dropdownListBlock value="${d.clazz.name}" name="${d.displayName}" + selected="${it.delegateLauncher.descriptor==d}" + title="${d.displayName}"> + <j:set var="descriptor" value="${d}"/> + <j:set var="instance" + value="${it.delegateLauncher.descriptor==d ? it.delegateLauncher : null}"/> + <tr> + <td> + <input type="hidden" name="stapler-class" value="${d.clazz.name}"/> + </td> + </tr> + <st:include from="${d}" page="${d.configPage}" optional="true"/> + </f:dropdownListBlock> + </j:forEach> + </f:dropdownList> + + <!-- pointless to show this if there's only one option, which is the default --> + <j:if test="${h.getRetentionStrategyDescriptors().size() gt 1}"> + <f:dropdownList name="slave.retentionStrategy" title="${%Availability}" + help="/help/system-config/master-slave/availability.html"> + <j:forEach var="d" items="${h.getRetentionStrategyDescriptors()}"> + <j:if test="${d != null}"> + <f:dropdownListBlock value="${d.clazz.name}" name="${d.displayName}" + selected="${it.retentionStrategy.descriptor==d}" + title="${d.displayName}"> + <j:set var="descriptor" value="${d}"/> + <j:set var="instance" + value="${it.retentionStrategy.descriptor==d ? it.retentionStrategy : null}"/> + <tr> + <td> + <input type="hidden" name="stapler-class" value="${d.clazz.name}"/> + </td> + </tr> + <st:include from="${d}" page="${d.configPage}" optional="true"/> + </f:dropdownListBlock> + </j:if> + </j:forEach> + </f:dropdownList> + </j:if> + + <f:entry title="${%Virtual Machines}" description="${%List of virtual machines to be launched as slaves}"> + <f:repeatable field="vms"> + <div> + <table width="100%"> + <st:include page="config.jelly" from="${descriptor}" + class="com.parallels.desktopcloud.ParallelsDesktopVM" /> + </table> + </div> + <div align="right"><f:repeatableDeleteButton/></div> + </f:repeatable> + </f:entry> + + <f:descriptorList title="${%Node Properties}" descriptors="${h.getNodePropertyDescriptors(descriptor.clazz)}" + field="nodeProperties"/> +</j:jelly> diff --git a/src/main/resources/com/parallels/desktopcloud/ParallelsDesktopVM/config.jelly b/src/main/resources/com/parallels/desktopcloud/ParallelsDesktopVM/config.jelly new file mode 100644 index 0000000..68a4ebb --- /dev/null +++ b/src/main/resources/com/parallels/desktopcloud/ParallelsDesktopVM/config.jelly @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +The MIT License + +(c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--> + +<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" + xmlns:t="/lib/hudson" xmlns:f="/lib/form" > + + <f:entry title="${%Virtual Machine ID}" field="vmid"> + <f:textbox /> + </f:entry> + + <f:entry title="${%Labels}" field="labels"> + <f:textbox /> + </f:entry> + + <f:entry title="${%Remote FS root}" field="remoteFS"> + <f:textbox/> + </f:entry> + + <f:dropdownList name="launcher" title="${%Secondary launch method}" + help="${descriptor.getHelpFile('launcher')}"> + <j:forEach var="d" items="${h.getComputerLauncherDescriptors()}" varStatus="loop"> + <f:dropdownListBlock value="${d.clazz.name}" name="${d.displayName}" + selected="${it.delegateLauncher.descriptor==d}" + title="${d.displayName}"> + <j:set var="descriptor" value="${d}"/> + <j:set var="instance" + value="${it.delegateLauncher.descriptor==d ? it.delegateLauncher : null}"/> + <tr> + <td> + <input type="hidden" name="stapler-class" value="${d.clazz.name}"/> + </td> + </tr> + <st:include from="${d}" page="${d.configPage}" optional="true"/> + </f:dropdownListBlock> + </j:forEach> + </f:dropdownList> +</j:jelly> diff --git a/src/main/resources/index.jelly b/src/main/resources/index.jelly new file mode 100644 index 0000000..67fe208 --- /dev/null +++ b/src/main/resources/index.jelly @@ -0,0 +1,27 @@ +<!-- +The MIT License + +(c) 2004-2015. Parallels IP Holdings GmbH. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--> + +<div> + Parallels Desktop Cloud plugin +</div>