diff --git a/src/main/java/org/jenkinsci/plugins/vSphereCloudSlaveTemplate.java b/src/main/java/org/jenkinsci/plugins/vSphereCloudSlaveTemplate.java index 781987fc..cfe66c4c 100644 --- a/src/main/java/org/jenkinsci/plugins/vSphereCloudSlaveTemplate.java +++ b/src/main/java/org/jenkinsci/plugins/vSphereCloudSlaveTemplate.java @@ -18,6 +18,7 @@ import static org.jenkinsci.plugins.vsphere.tools.PermissionUtils.throwUnlessUserHasPermissionToConfigureCloud; +import com.vmware.vim25.VirtualMachineConfigSpec; import hudson.DescriptorExtensionList; import hudson.EnvVars; import hudson.Extension; @@ -49,7 +50,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -61,6 +61,7 @@ import org.jenkinsci.plugins.vsphere.VSphereConnectionConfig; import org.jenkinsci.plugins.vsphere.VSphereGuestInfoProperty; import org.jenkinsci.plugins.vsphere.builders.Messages; +import org.jenkinsci.plugins.vsphere.builders.ReconfigureStep; import org.jenkinsci.plugins.vsphere.tools.VSphere; import org.jenkinsci.plugins.vsphere.tools.VSphereDuplicateException; import org.jenkinsci.plugins.vsphere.tools.VSphereException; @@ -124,6 +125,7 @@ public class vSphereCloudSlaveTemplate implements Describable guestInfoProperties; private ComputerLauncher launcher; private RetentionStrategy retentionStrategy; + private final List reconfigureSteps; private transient Set labelSet; protected transient vSphereCloud parent; @@ -156,7 +158,8 @@ public vSphereCloudSlaveTemplate(final String cloneNamePrefix, final ComputerLauncher launcher, final RetentionStrategy retentionStrategy, final List> nodeProperties, - final List guestInfoProperties) { + final List guestInfoProperties, + final List reconfigureSteps) { this.configVersion = CURRENT_CONFIG_VERSION; this.cloneNamePrefix = cloneNamePrefix; this.masterImageName = masterImageName; @@ -186,6 +189,7 @@ public vSphereCloudSlaveTemplate(final String cloneNamePrefix, this.guestInfoProperties = Util.fixNull(guestInfoProperties); this.launcher = launcher; this.retentionStrategy = retentionStrategy; + this.reconfigureSteps = Util.fixNull(reconfigureSteps); readResolve(); } @@ -319,6 +323,10 @@ public RetentionStrategy getRetentionStrategy() { return this.retentionStrategy; } + public List getReconfigureSteps() { + return this.reconfigureSteps; + } + protected Object readResolve() { this.labelSet = Label.parse(labelString); if(this.templateInstanceCap == 0) { @@ -395,20 +403,19 @@ protected Object readResolve() { } public vSphereCloudProvisionedSlave provision(final String cloneName, final TaskListener listener) throws VSphereException, FormException, IOException, InterruptedException { - final PrintStream logger = listener.getLogger(); final Map resolvedExtraConfigParameters = calculateExtraConfigParameters(cloneName, listener); final VSphere vSphere = getParent().vSphereInstance(); final vSphereCloudProvisionedSlave slave; try { - slave = provision(cloneName, logger, resolvedExtraConfigParameters, vSphere); + slave = provision(cloneName, listener, resolvedExtraConfigParameters, vSphere); } finally { vSphere.disconnect(); } return slave; } - private vSphereCloudProvisionedSlave provision(final String cloneName, final PrintStream logger, final Map resolvedExtraConfigParameters, final VSphere vSphere) throws VSphereException, FormException, IOException { - final boolean POWER_ON = true; + private vSphereCloudProvisionedSlave provision(final String cloneName, final TaskListener listener, final Map resolvedExtraConfigParameters, final VSphere vSphere) throws VSphereException, FormException, IOException { + final PrintStream logger = listener.getLogger(); final boolean useCurrentSnapshot; final String snapshotToUse; if (getUseSnapshot()) { @@ -425,8 +432,24 @@ private vSphereCloudProvisionedSlave provision(final String cloneName, final Pri snapshotToUse = null; } try { - vSphere.cloneOrDeployVm(cloneName, this.masterImageName, this.linkedClone, this.resourcePool, this.cluster, this.datastore, this.folder, useCurrentSnapshot, snapshotToUse, POWER_ON, resolvedExtraConfigParameters, this.customizationSpec, logger); + final boolean willReconfigure = reconfigureSteps != null && !reconfigureSteps.isEmpty(); + vSphere.cloneOrDeployVm(cloneName, this.masterImageName, this.linkedClone, this.resourcePool, this.cluster, this.datastore, this.folder, useCurrentSnapshot, snapshotToUse, !willReconfigure, resolvedExtraConfigParameters, this.customizationSpec, logger); LOGGER.log(Level.FINE, "Created new VM {0} from image {1}", new Object[]{ cloneName, this.masterImageName }); + if(willReconfigure) { + final VirtualMachine vm = vSphere.getVmByName(cloneName); + final VirtualMachineConfigSpec spec = new VirtualMachineConfigSpec(); + final EnvVars env = new EnvVars(); + for (ReconfigureStep globalStep : reconfigureSteps) { + // Do not mutate global steps to perform reconfiguration - use a clone + ReconfigureStep actionStep = (ReconfigureStep)Jenkins.XSTREAM2.fromXML(Jenkins.XSTREAM2.toXML(globalStep)); + actionStep.setVsphere(vSphere); + actionStep.setVM(vm); + actionStep.setVirtualMachineConfigSpec(spec); + actionStep.perform(env, listener); + } + vSphere.reconfigureVm(cloneName, spec); + vSphere.startVm(cloneName, 120); + } } catch (VSphereDuplicateException ex) { final String vmJenkinsUrl = findWhichJenkinsThisVMBelongsTo(vSphere, cloneName); if ( vmJenkinsUrl==null ) { diff --git a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureAnnotation.java b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureAnnotation.java index b2af697e..9ded8867 100644 --- a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureAnnotation.java +++ b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureAnnotation.java @@ -55,6 +55,11 @@ public boolean getAppend() { return append; } + @Override + public void perform(@Nonnull EnvVars env, @Nonnull TaskListener listener) throws VSphereException { + reconfigureAnnotation(env, listener); + } + @Override public void perform(@Nonnull Run run, @Nonnull FilePath filePath, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException { try { @@ -77,19 +82,14 @@ public boolean perform(final AbstractBuild build, final Launcher launcher, } public boolean reconfigureAnnotation(final Run run, final Launcher launcher, final TaskListener listener) throws VSphereException { + EnvVars env = extractEnvironment(run, listener); + + return reconfigureAnnotation(env, listener); + } + private boolean reconfigureAnnotation(final EnvVars env, final TaskListener listener) throws VSphereException { final PrintStream jLogger = listener.getLogger(); - String expandedText = getAnnotation(); - final EnvVars env; - try { - env = run.getEnvironment(listener); - } catch (Exception e) { - throw new VSphereException(e); - } - if (run instanceof AbstractBuild) { - env.overrideAll(((AbstractBuild) run).getBuildVariables()); // Add in matrix axes.. - expandedText = env.expand(expandedText); - } + String expandedText = env.expand(getAnnotation()); VSphereLogger.vsLogger(jLogger, "Preparing reconfigure: Annotation"); if (getAppend()) { diff --git a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureCpu.java b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureCpu.java index ca7993a7..6d351679 100644 --- a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureCpu.java +++ b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureCpu.java @@ -49,6 +49,11 @@ public String getCoresPerSocket() { return coresPerSocket; } + @Override + public void perform(@Nonnull EnvVars env, @Nonnull TaskListener listener) throws VSphereException { + reconfigureCPU(env, listener); + } + @Override public void perform(@Nonnull Run run, @Nonnull FilePath filePath, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException { try { @@ -70,32 +75,23 @@ public boolean perform(final AbstractBuild build, final Launcher launcher, //TODO throw AbortException instead of returning value } - public boolean reconfigureCPU (final Run run, final Launcher launcher, final TaskListener listener) throws VSphereException { + public boolean reconfigureCPU(final Run run, final Launcher launcher, final TaskListener listener) throws VSphereException { + EnvVars env = extractEnvironment(run, listener); - PrintStream jLogger = listener.getLogger(); - String expandedCPUCores = cpuCores; - String expandedCoresPerSocket = coresPerSocket; - EnvVars env; - try { - env = run.getEnvironment(listener); - } catch (Exception e) { - throw new VSphereException(e); - } + return reconfigureCPU(env, listener); + } - if (run instanceof AbstractBuild) { - env.overrideAll(((AbstractBuild) run).getBuildVariables()); // Add in matrix axes.. - expandedCPUCores = env.expand(cpuCores); - expandedCoresPerSocket = env.expand(coresPerSocket); - } + private boolean reconfigureCPU(final EnvVars env, final TaskListener listener) throws VSphereException { + PrintStream jLogger = listener.getLogger(); + String expandedCPUCores = env.expand(cpuCores); + String expandedCoresPerSocket = env.expand(coresPerSocket); VSphereLogger.vsLogger(jLogger, "Preparing reconfigure: CPU"); spec.setNumCPUs(Integer.valueOf(expandedCPUCores)); spec.setNumCoresPerSocket(Integer.valueOf(expandedCoresPerSocket)); - VSphereLogger.vsLogger(jLogger, "Finished!"); return true; - } - + } @Extension public static final class ReconfigureCpuDescriptor extends ReconfigureStepDescriptor { diff --git a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureDisk.java b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureDisk.java index 512ed96a..b6dab17c 100644 --- a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureDisk.java +++ b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureDisk.java @@ -64,6 +64,11 @@ public String getDataStore() { return datastore; } + @Override + public void perform(@Nonnull EnvVars env, @Nonnull TaskListener listener) throws VSphereException { + reconfigureDisk(env, listener); + } + @Override public void perform(@Nonnull Run run, @Nonnull FilePath filePath, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException { try { @@ -86,20 +91,18 @@ public boolean perform(final AbstractBuild build, final Launcher launcher, } public boolean reconfigureDisk(final Run run, final Launcher launcher, final TaskListener listener) throws VSphereException { + EnvVars env = extractEnvironment(run, listener); + return reconfigureDisk(env, listener); + } + + private boolean reconfigureDisk(final EnvVars env, final TaskListener listener) throws VSphereException { PrintStream jLogger = listener.getLogger(); - int diskSize = Integer.parseInt(this.diskSize); - EnvVars env; + int diskSize = Integer.parseInt(env.expand(this.diskSize)); try { - env = run.getEnvironment(listener); - if (run instanceof AbstractBuild) { - env.overrideAll(((AbstractBuild) run).getBuildVariables()); // Add in matrix axes.. - diskSize = Integer.parseInt(env.expand(this.diskSize)); - } VirtualDeviceConfigSpec vdiskSpec = createAddDiskConfigSpec(vm, diskSize, jLogger); VirtualDeviceConfigSpec [] vdiskSpecArray = {vdiskSpec}; - spec.setDeviceChange(vdiskSpecArray); VSphereLogger.vsLogger(jLogger, "Configuration done"); } catch (Exception e) { diff --git a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureMemory.java b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureMemory.java index 40340b35..9c1d86f0 100644 --- a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureMemory.java +++ b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureMemory.java @@ -43,6 +43,11 @@ public String getMemorySize() { return memorySize; } + @Override + public void perform(@Nonnull EnvVars env, @Nonnull TaskListener listener) throws VSphereException { + reconfigureMemory(env, listener); + } + @Override public void perform(@Nonnull Run run, @Nonnull FilePath filePath, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException { try { @@ -65,24 +70,19 @@ public boolean perform(final AbstractBuild build, final Launcher launcher, } public boolean reconfigureMemory(final Run run, final Launcher launcher, final TaskListener listener) throws VSphereException { + EnvVars env = extractEnvironment(run, listener); - PrintStream jLogger = listener.getLogger(); - String expandedMemorySize = memorySize; - EnvVars env; - try { - env = run.getEnvironment(listener); - } catch (Exception e) { - throw new VSphereException(e); - } - if (run instanceof AbstractBuild) { - env.overrideAll(((AbstractBuild) run).getBuildVariables()); // Add in matrix axes.. - expandedMemorySize = env.expand(memorySize); - } + return reconfigureMemory(env, listener); + } + + private boolean reconfigureMemory(final EnvVars env, final TaskListener listener) throws VSphereException { + PrintStream jLogger = listener.getLogger(); + String expandedMemorySize = env.expand(memorySize); - VSphereLogger.vsLogger(jLogger, "Preparing reconfigure: Memory"); - spec.setMemoryMB(Long.valueOf(expandedMemorySize)); - VSphereLogger.vsLogger(jLogger, "Finished!"); - return true; + VSphereLogger.vsLogger(jLogger, "Preparing reconfigure: Memory"); + spec.setMemoryMB(Long.valueOf(expandedMemorySize)); + VSphereLogger.vsLogger(jLogger, "Finished!"); + return true; } @Extension diff --git a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureNetworkAdapters.java b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureNetworkAdapters.java index e4b06615..60542ff4 100644 --- a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureNetworkAdapters.java +++ b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureNetworkAdapters.java @@ -93,6 +93,11 @@ public String getDistributedPortId() { return distributedPortId; } + @Override + public void perform(@Nonnull EnvVars env, @Nonnull TaskListener listener) throws VSphereException { + reconfigureNetwork(env, listener); + } + @Override public void perform(@Nonnull Run run, @Nonnull FilePath filePath, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException { try { @@ -114,27 +119,20 @@ public boolean perform(final AbstractBuild build, final Launcher launcher, //TODO throw AbortException instead of returning value } - public boolean reconfigureNetwork(final Run run, final Launcher launcher, final TaskListener listener) throws VSphereException { + public boolean reconfigureNetwork(final Run run, final Launcher launcher, final TaskListener listener) throws VSphereException { + EnvVars env = extractEnvironment(run, listener); + + return reconfigureNetwork(env, listener); + } + + private boolean reconfigureNetwork(final EnvVars env, final TaskListener listener) throws VSphereException { PrintStream jLogger = listener.getLogger(); - String expandedDeviceLabel = deviceLabel; - String expandedMacAddress = macAddress; - String expandedPortGroup = portGroup; - String expandedDistributedPortGroup = distributedPortGroup; - String expandedDistributedPortId = distributedPortId; - EnvVars env; - try { - env = run.getEnvironment(listener); - } catch (Exception e) { - throw new VSphereException(e); - } - if (run instanceof AbstractBuild) { - env.overrideAll(((AbstractBuild) run).getBuildVariables()); // Add in matrix axes.. - expandedDeviceLabel = env.expand(deviceLabel); - expandedMacAddress = env.expand(macAddress); - expandedPortGroup = env.expand(portGroup); - expandedDistributedPortGroup = env.expand(distributedPortGroup); - expandedDistributedPortId = env.expand(distributedPortId); - } + String expandedDeviceLabel = env.expand(deviceLabel); + String expandedMacAddress = env.expand(macAddress); + String expandedPortGroup = env.expand(portGroup); + String expandedDistributedPortGroup = env.expand(distributedPortGroup); + String expandedDistributedPortId = env.expand(distributedPortId); + VSphereLogger.vsLogger(jLogger, "Preparing reconfigure: "+ deviceAction.getLabel() +" Network Adapter \"" + expandedDeviceLabel + "\""); VirtualEthernetCard vEth = null; if (deviceAction == DeviceAction.ADD) { diff --git a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureStep.java b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureStep.java index 66638cbe..25abec61 100644 --- a/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureStep.java +++ b/src/main/java/org/jenkinsci/plugins/vsphere/builders/ReconfigureStep.java @@ -69,6 +69,8 @@ public static List all() { public abstract void perform(@Nonnull Run run, FilePath filePath, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException; + public abstract void perform(@Nonnull EnvVars env, @Nonnull TaskListener listener) throws VSphereException; + protected VirtualDevice findDeviceByLabel(VirtualDevice[] devices, String label) { for(VirtualDevice d : devices) { if(d.getDeviceInfo().getLabel().contentEquals(label)) { @@ -78,6 +80,20 @@ protected VirtualDevice findDeviceByLabel(VirtualDevice[] devices, String label) return null; } + protected EnvVars extractEnvironment(final Run run, final TaskListener listener) throws VSphereException { + try { + EnvVars env = run.getEnvironment(listener); + + if (run instanceof AbstractBuild) { + env.overrideAll(((AbstractBuild) run).getBuildVariables()); // Add in matrix axes.. + } + + return env; + } catch (Exception e) { + throw new VSphereException(e); + } + } + public static abstract class ReconfigureStepDescriptor extends Descriptor { protected ReconfigureStepDescriptor() { } diff --git a/src/main/resources/org/jenkinsci/plugins/vSphereCloudSlaveTemplate/config.jelly b/src/main/resources/org/jenkinsci/plugins/vSphereCloudSlaveTemplate/config.jelly index ab4f7095..1fa02e80 100644 --- a/src/main/resources/org/jenkinsci/plugins/vSphereCloudSlaveTemplate/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/vSphereCloudSlaveTemplate/config.jelly @@ -125,6 +125,11 @@ + + + + diff --git a/src/main/resources/org/jenkinsci/plugins/vSphereCloudSlaveTemplate/help-reconfigureSteps.html b/src/main/resources/org/jenkinsci/plugins/vSphereCloudSlaveTemplate/help-reconfigureSteps.html new file mode 100644 index 00000000..2287da2c --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/vSphereCloudSlaveTemplate/help-reconfigureSteps.html @@ -0,0 +1,3 @@ +
+ Reconfigure the virtual machine hardware. Allows adjusting RAM, CPU, etc. +
diff --git a/src/test/java/org/jenkinsci/plugins/vsphere/tools/CloudProvisioningAlgorithmTest.java b/src/test/java/org/jenkinsci/plugins/vsphere/tools/CloudProvisioningAlgorithmTest.java index b48ec3c7..7b3f2c31 100644 --- a/src/test/java/org/jenkinsci/plugins/vsphere/tools/CloudProvisioningAlgorithmTest.java +++ b/src/test/java/org/jenkinsci/plugins/vsphere/tools/CloudProvisioningAlgorithmTest.java @@ -317,7 +317,7 @@ private CloudProvisioningRecord createInstance(int capacity, int provisioned, in private static vSphereCloudSlaveTemplate stubTemplate(String prefix, int templateInstanceCap) { return new vSphereCloudSlaveTemplate(prefix, "", null, null, false, null, null, null, null, null, null, templateInstanceCap, 1, null, null, null, false, false, 0, 0, false, null, null, null, new JNLPLauncher(), - RetentionStrategy.NOOP, null, null); + RetentionStrategy.NOOP, null, null, null); } private static String toHexString(byte[] bytes) { diff --git a/src/test/java/org/jenkinsci/plugins/vsphere/tools/CloudProvisioningStateTest.java b/src/test/java/org/jenkinsci/plugins/vsphere/tools/CloudProvisioningStateTest.java index f0446fa6..d0aed4da 100644 --- a/src/test/java/org/jenkinsci/plugins/vsphere/tools/CloudProvisioningStateTest.java +++ b/src/test/java/org/jenkinsci/plugins/vsphere/tools/CloudProvisioningStateTest.java @@ -31,6 +31,7 @@ import org.jenkinsci.plugins.vSphereCloudSlaveTemplate; import org.jenkinsci.plugins.vsphere.VSphereConnectionConfig; import org.jenkinsci.plugins.vsphere.VSphereGuestInfoProperty; +import org.jenkinsci.plugins.vsphere.builders.ReconfigureStep; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -491,7 +492,7 @@ private CloudProvisioningRecord createRecord(CloudProvisioningState instance) { null, "snapshotName", false, "cluster", "resourcePool", "datastore", "folder", "customizationSpec", "templateDescription", 0, 1, "remoteFS", "", Mode.NORMAL, false, false, 0, 0, false, "targetResourcePool", "targetHost", null, new JNLPLauncher(), RetentionStrategy.NOOP, Collections.> emptyList(), - Collections. emptyList()); + Collections. emptyList(), Collections.emptyList()); stubVSphereCloudTemplates.add(template); final List templates = new ArrayList(); templates.add(template); diff --git a/src/test/resources/org/jenkinsci/plugins/vsphere/tools/configuration-as-code.yml b/src/test/resources/org/jenkinsci/plugins/vsphere/tools/configuration-as-code.yml index 1e2ab34d..d6337b0a 100644 --- a/src/test/resources/org/jenkinsci/plugins/vsphere/tools/configuration-as-code.yml +++ b/src/test/resources/org/jenkinsci/plugins/vsphere/tools/configuration-as-code.yml @@ -26,6 +26,12 @@ jenkins: masterImageName: "windows-server-2019" mode: EXCLUSIVE numberOfExecutors: 1 + reconfigureSteps: + - reconfigureMemory: + memorySize: "8192" + - reconfigureCpu: + coresPerSocket: "4" + cpuCores: "8" remoteFS: "C:/jenkins" resourcePool: "Resources" retentionStrategy: diff --git a/src/test/resources/org/jenkinsci/plugins/vsphere/tools/expected_output.yml b/src/test/resources/org/jenkinsci/plugins/vsphere/tools/expected_output.yml index 25974603..36b7684c 100644 --- a/src/test/resources/org/jenkinsci/plugins/vsphere/tools/expected_output.yml +++ b/src/test/resources/org/jenkinsci/plugins/vsphere/tools/expected_output.yml @@ -20,6 +20,12 @@ masterImageName: "windows-server-2019" mode: EXCLUSIVE numberOfExecutors: 1 + reconfigureSteps: + - reconfigureMemory: + memorySize: "8192" + - reconfigureCpu: + coresPerSocket: "4" + cpuCores: "8" remoteFS: "C:/jenkins" resourcePool: "Resources" retentionStrategy: