diff --git a/src/main/java/hudson/plugins/spotinst/api/SpotinstApi.java b/src/main/java/hudson/plugins/spotinst/api/SpotinstApi.java index c6e36829..e5b4df51 100644 --- a/src/main/java/hudson/plugins/spotinst/api/SpotinstApi.java +++ b/src/main/java/hudson/plugins/spotinst/api/SpotinstApi.java @@ -6,7 +6,7 @@ import hudson.plugins.spotinst.common.SpotinstContext; import hudson.plugins.spotinst.model.aws.*; import hudson.plugins.spotinst.model.aws.stateful.AwsDeallocateStatefulInstanceRequest; -import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulDeallocationConfig; +import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulDeAllocationConfig; import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulInstance; import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulInstancesResponse; import hudson.plugins.spotinst.model.azure.*; @@ -177,12 +177,12 @@ public static Boolean awsDeallocateInstance(String groupId, String statefulInsta Map queryParams = buildQueryParams(accountId); AwsDeallocateStatefulInstanceRequest request = new AwsDeallocateStatefulInstanceRequest(); - AwsStatefulDeallocationConfig statefulDeallocation = new AwsStatefulDeallocationConfig(); + AwsStatefulDeAllocationConfig statefulDeallocation = new AwsStatefulDeAllocationConfig(); statefulDeallocation.setShouldDeleteImages(true); statefulDeallocation.setShouldDeleteSnapshots(true); statefulDeallocation.setShouldDeleteVolumes(true); statefulDeallocation.setShouldDeleteNetworkInterfaces(true); - request.setStatefulDeallocation(statefulDeallocation); + request.setStatefulDeAllocationConfig(statefulDeallocation); String body = JsonMapper.toJson(request); diff --git a/src/main/java/hudson/plugins/spotinst/cloud/AwsSpotinstCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/AwsSpotinstCloud.java index 46ceebf2..f18574f2 100644 --- a/src/main/java/hudson/plugins/spotinst/cloud/AwsSpotinstCloud.java +++ b/src/main/java/hudson/plugins/spotinst/cloud/AwsSpotinstCloud.java @@ -6,7 +6,8 @@ import hudson.plugins.spotinst.api.infra.JsonMapper; import hudson.plugins.spotinst.common.ConnectionMethodEnum; import hudson.plugins.spotinst.common.SpotAwsInstanceTypesHelper; -import hudson.plugins.spotinst.common.stateful.StatefulInstanceStateEnum; +import hudson.plugins.spotinst.common.stateful.AwsStatefulInstanceStateEnum; +import hudson.plugins.spotinst.common.stateful.StatefulInstanceManager; import hudson.plugins.spotinst.model.aws.*; import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulInstance; import hudson.plugins.spotinst.model.common.BlResponse; @@ -47,15 +48,15 @@ public class AwsSpotinstCloud extends BaseSpotinstCloud { @DataBoundConstructor public AwsSpotinstCloud(String groupId, String labelString, String idleTerminationMinutes, String workspaceDir, List executorsForTypes, SlaveUsageEnum usage, - String tunnel, Boolean shouldUseWebsocket, Boolean shouldRetriggerBuilds, String vmargs, - EnvironmentVariablesNodeProperty environmentVariables, + String tunnel, Boolean shouldUseWebsocket, SpotReTriggerBuilds spotReTriggerBuilds, + String vmargs, EnvironmentVariablesNodeProperty environmentVariables, ToolLocationNodeProperty toolLocations, String accountId, ConnectionMethodEnum connectionMethod, ComputerConnector computerConnector, Boolean shouldUsePrivateIp, SpotGlobalExecutorOverride globalExecutorOverride, Integer pendingThreshold) { super(groupId, labelString, idleTerminationMinutes, workspaceDir, usage, tunnel, shouldUseWebsocket, - shouldRetriggerBuilds, vmargs, environmentVariables, toolLocations, accountId, connectionMethod, + spotReTriggerBuilds, vmargs, environmentVariables, toolLocations, accountId, connectionMethod, computerConnector, shouldUsePrivateIp, globalExecutorOverride, pendingThreshold); this.executorsForTypes = new LinkedList<>(); @@ -106,19 +107,36 @@ List scaleUp(ProvisionRequest request) { return retVal; } - @Override - protected String getSsiId(String instanceId) { - String retVal = null; - AwsStatefulInstance statefulInstance = getStatefulInstance(instanceId); - - if (statefulInstance != null) { - retVal = statefulInstance.getId(); - } - - return retVal; - } + // @Override + // protected String getStatefulInstanceId(String instanceId) { + // String retVal = null; + // + // IAwsGroupRepo awsGroupRepo = RepoManager.getInstance().getAwsGroupRepo(); + // ApiResponse> statefulInstancesResponse = + // awsGroupRepo.getStatefulInstances(groupId, accountId); + // + // if (statefulInstancesResponse.isRequestSucceed()) { + // List groupStatefulInstances = statefulInstancesResponse.getValue(); + // boolean isGroupHasStatefulInstances = CollectionUtils.isNotEmpty(groupStatefulInstances); + // + // if (isGroupHasStatefulInstances) { + // Optional optionalAwsStatefulInstance = groupStatefulInstances.stream() + // .filter(statefulInstance -> instanceId.equals( + // statefulInstance.getInstanceId())) + // .findFirst(); + // boolean isStatefulInstance = optionalAwsStatefulInstance.isPresent(); + // + // if (isStatefulInstance) { + // retVal = optionalAwsStatefulInstance.get().getId(); + // } + // } + // } + // + // return retVal; + // } - private AwsStatefulInstance getStatefulInstance(String instanceId) { + @Override + protected AwsStatefulInstance getStatefulInstance(String instanceId) { AwsStatefulInstance retVal = null; boolean isInstanceStateful = ssiByInstanceId != null && ssiByInstanceId.containsKey(instanceId); @@ -308,6 +326,7 @@ private void syncGroupStatefulInstances() { List statefulInstances = statefulInstancesResponse.getValue(); this.ssiByInstanceId = statefulInstances.stream().collect( Collectors.toMap(AwsStatefulInstance::getInstanceId, statefulInstance -> statefulInstance)); + StatefulInstanceManager.getStatefulInstanceIdsByGroupId().put(groupId, statefulInstances); } else { LOGGER.error(String.format("Failed to get group %s stateful instances. Errors: %s", groupId, @@ -322,7 +341,6 @@ private List handleNewAwsSpots(AwsScaleUpResult scaleUpResult, St LOGGER.info(String.format("%s new spot requests created", scaleUpResult.getNewSpotRequests().size())); for (AwsScaleResultNewSpot spot : scaleUpResult.getNewSpotRequests()) { - SpotinstSlave slave = handleNewAwsInstance(spot.getInstanceId(), spot.getInstanceType(), label); retVal.add(slave); @@ -381,7 +399,7 @@ private Boolean shouldAddNewSlaveInstance(AwsGroupInstance instance) { AwsStatefulInstance statefulInstance = getStatefulInstance(instance.getInstanceId()); boolean isStatefulInstanceReadyForUse = statefulInstance != null && Objects.equals(statefulInstance.getState(), - StatefulInstanceStateEnum.ACTIVE); + AwsStatefulInstanceStateEnum.ACTIVE); retVal = isStatefulInstanceReadyForUse; } else { @@ -506,7 +524,7 @@ private void initExecutorsByInstanceType() { this.executorsByInstanceType.put(type, executors); if (instance.getIsValid() == false) { - LOGGER.error(String.format("Invalid type \'%s\' in group \'%s\'", type, this.getGroupId())); + LOGGER.error(String.format("Invalid type '%s' in group '%s'", type, this.getGroupId())); invalidInstanceTypes.add(type); } } diff --git a/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotCloud.java index bff19404..fffcbd87 100644 --- a/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotCloud.java +++ b/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotCloud.java @@ -9,6 +9,7 @@ import hudson.plugins.spotinst.model.azure.AzureGroupVm; import hudson.plugins.spotinst.model.azure.AzureScaleUpResultNewVm; import hudson.plugins.spotinst.model.azure.AzureVmSizeEnum; +import hudson.plugins.spotinst.model.common.BaseStatefulInstance; import hudson.plugins.spotinst.model.common.BlResponse; import hudson.plugins.spotinst.repos.IAzureVmGroupRepo; import hudson.plugins.spotinst.repos.RepoManager; @@ -41,13 +42,13 @@ public class AzureSpotCloud extends BaseSpotinstCloud { @DataBoundConstructor public AzureSpotCloud(String groupId, String labelString, String idleTerminationMinutes, String workspaceDir, SlaveUsageEnum usage, String tunnel, Boolean shouldUseWebsocket, - Boolean shouldRetriggerBuilds, String vmargs, + SpotReTriggerBuilds spotReTriggerBuilds, String vmargs, EnvironmentVariablesNodeProperty environmentVariables, ToolLocationNodeProperty toolLocations, String accountId, ConnectionMethodEnum connectionMethod, ComputerConnector computerConnector, Boolean shouldUsePrivateIp, SpotGlobalExecutorOverride globalExecutorOverride, Integer pendingThreshold) { super(groupId, labelString, idleTerminationMinutes, workspaceDir, usage, tunnel, shouldUseWebsocket, - shouldRetriggerBuilds, vmargs, environmentVariables, toolLocations, accountId, connectionMethod, + spotReTriggerBuilds, vmargs, environmentVariables, toolLocations, accountId, connectionMethod, computerConnector, shouldUsePrivateIp, globalExecutorOverride, pendingThreshold); } //endregion @@ -92,7 +93,7 @@ protected BlResponse checkIsStatefulGroup() { } @Override - protected String getSsiId(String instanceId) { + protected BaseStatefulInstance getStatefulInstance(String instanceId) { return null;//TODO: implement } diff --git a/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotinstCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotinstCloud.java index 600ecf7a..6946feba 100644 --- a/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotinstCloud.java +++ b/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotinstCloud.java @@ -8,6 +8,7 @@ import hudson.plugins.spotinst.common.Constants; import hudson.plugins.spotinst.model.azure.AzureGroupInstance; import hudson.plugins.spotinst.model.azure.AzureScaleSetSizeEnum; +import hudson.plugins.spotinst.model.common.BaseStatefulInstance; import hudson.plugins.spotinst.model.common.BlResponse; import hudson.plugins.spotinst.repos.IAzureGroupRepo; import hudson.plugins.spotinst.repos.RepoManager; @@ -39,14 +40,14 @@ public class AzureSpotinstCloud extends BaseSpotinstCloud { @DataBoundConstructor public AzureSpotinstCloud(String groupId, String labelString, String idleTerminationMinutes, String workspaceDir, SlaveUsageEnum usage, String tunnel, Boolean shouldUseWebsocket, - Boolean shouldRetriggerBuilds, String vmargs, + SpotReTriggerBuilds spotReTriggerBuilds, String vmargs, EnvironmentVariablesNodeProperty environmentVariables, ToolLocationNodeProperty toolLocations, String accountId, ConnectionMethodEnum connectionMethod, ComputerConnector computerConnector, Boolean shouldUsePrivateIp, SpotGlobalExecutorOverride globalExecutorOverride, Integer pendingThreshold) { super(groupId, labelString, idleTerminationMinutes, workspaceDir, usage, tunnel, shouldUseWebsocket, - shouldRetriggerBuilds, vmargs, environmentVariables, toolLocations, accountId, connectionMethod, + spotReTriggerBuilds, vmargs, environmentVariables, toolLocations, accountId, connectionMethod, computerConnector, shouldUsePrivateIp, globalExecutorOverride, pendingThreshold); } //endregion @@ -82,7 +83,7 @@ protected BlResponse checkIsStatefulGroup() { } @Override - protected String getSsiId(String instanceId) { + protected BaseStatefulInstance getStatefulInstance(String instanceId) { return null;//TODO: implement } diff --git a/src/main/java/hudson/plugins/spotinst/cloud/BaseSpotinstCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/BaseSpotinstCloud.java index f665b2a4..5ddaeb88 100644 --- a/src/main/java/hudson/plugins/spotinst/cloud/BaseSpotinstCloud.java +++ b/src/main/java/hudson/plugins/spotinst/cloud/BaseSpotinstCloud.java @@ -5,6 +5,7 @@ import hudson.model.labels.LabelAtom; import hudson.plugins.spotinst.api.infra.JsonMapper; import hudson.plugins.spotinst.common.*; +import hudson.plugins.spotinst.model.common.BaseStatefulInstance; import hudson.plugins.spotinst.model.common.BlResponse; import hudson.plugins.spotinst.slave.*; import hudson.plugins.sshslaves.SSHConnector; @@ -14,7 +15,6 @@ import hudson.tools.ToolInstallation; import hudson.tools.ToolLocationNodeProperty; import jenkins.model.Jenkins; -import org.apache.commons.lang.BooleanUtils; import org.kohsuke.stapler.DataBoundSetter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,7 +51,7 @@ public abstract class BaseSpotinstCloud extends Cloud { private EnvironmentVariablesNodeProperty environmentVariables; private ToolLocationNodeProperty toolLocations; private Boolean shouldUseWebsocket; - private Boolean shouldRetriggerBuilds; + private SpotReTriggerBuilds spotReTriggerBuilds; private Boolean isSingleTaskNodesEnabled; private ComputerConnector computerConnector; private ConnectionMethodEnum connectionMethod; @@ -65,7 +65,7 @@ public abstract class BaseSpotinstCloud extends Cloud { //region Constructor public BaseSpotinstCloud(String groupId, String labelString, String idleTerminationMinutes, String workspaceDir, SlaveUsageEnum usage, String tunnel, Boolean shouldUseWebsocket, - Boolean shouldRetriggerBuilds, String vmargs, + SpotReTriggerBuilds spotReTriggerBuilds, String vmargs, EnvironmentVariablesNodeProperty environmentVariables, ToolLocationNodeProperty toolLocations, String accountId, ConnectionMethodEnum connectionMethod, ComputerConnector computerConnector, @@ -88,7 +88,13 @@ public BaseSpotinstCloud(String groupId, String labelString, String idleTerminat this.usage = SlaveUsageEnum.NORMAL; } - this.shouldRetriggerBuilds = shouldRetriggerBuilds == null || BooleanUtils.isTrue(shouldRetriggerBuilds); + if (spotReTriggerBuilds != null) { + this.spotReTriggerBuilds = spotReTriggerBuilds; + } + else { + this.spotReTriggerBuilds = new SpotReTriggerBuilds(false, false); + } + this.tunnel = tunnel; this.shouldUseWebsocket = shouldUseWebsocket; this.vmargs = vmargs; @@ -301,7 +307,7 @@ private void connectAgent(SpotinstSlave offlineAgent, String ipForAgent) { try { SpotSSHComputerLauncher launcher = new SpotSSHComputerLauncher(connector.launch(ipForAgent, computerForAgent.getListener()), - this.getShouldRetriggerBuilds()); + this.getShouldReTriggerBuilds()); offlineAgent.setLauncher(launcher); // save this information to disk - so Launcher survives Jenkins restarts offlineAgent.save(); @@ -368,10 +374,10 @@ protected void terminateOfflineSlaves(SpotinstSlave slave, String slaveInstanceI SlaveComputer computer = slave.getComputer(); if (computer != null) { Integer offlineThreshold = getSlaveOfflineThreshold(); - Boolean isSlaveConnecting = computer.isConnecting(); - Boolean isSlaveOffline = computer.isOffline(); + boolean isSlaveConnecting = computer.isConnecting(); + boolean isSlaveOffline = computer.isOffline(); // the computer is actively marked as temporary offline - Boolean temporarilyOffline = computer.isTemporarilyOffline(); + boolean temporarilyOffline = computer.isTemporarilyOffline(); long idleStartMillis = computer.getIdleStartMilliseconds(); long idleInMillis = System.currentTimeMillis() - idleStartMillis; @@ -514,11 +520,12 @@ protected SpotinstSlave buildSpotinstSlave(String id, String instanceType, Strin } List> nodeProperties = buildNodeProperties(); + String ssiId = getSsiId(id); try { ComputerLauncher launcher = buildLauncherForAgent(id); - slave = new SpotinstSlave(id, groupId, id, instanceType, labelString, idleTerminationMinutes, workspaceDir, - numOfExecutors, mode, launcher, nodeProperties); + slave = new SpotinstSlave(id, groupId, id, instanceType, ssiId, labelString, idleTerminationMinutes, + workspaceDir, numOfExecutors, mode, launcher, nodeProperties); } catch (Descriptor.FormException | IOException e) { @@ -531,7 +538,7 @@ protected SpotinstSlave buildSpotinstSlave(String id, String instanceType, Strin private ComputerLauncher buildLauncherForAgent(String instanceId) throws IOException { ComputerLauncher retVal; - Boolean isSshCloud = this.getConnectionMethod().equals(ConnectionMethodEnum.SSH); + boolean isSshCloud = this.getConnectionMethod().equals(ConnectionMethodEnum.SSH); SlaveInstanceDetails instanceDetailsById = getSlaveDetails(instanceId); if (isSshCloud) { @@ -546,7 +553,7 @@ private ComputerLauncher buildLauncherForAgent(String instanceId) throws IOExcep private ComputerLauncher handleJNLPLauncher() { return new SpotinstComputerLauncher(this.getTunnel(), this.getVmargs(), this.getShouldUseWebsocket(), - this.getShouldRetriggerBuilds()); + this.getShouldReTriggerBuilds()); } private ComputerLauncher HandleSSHLauncher(SlaveInstanceDetails instanceDetailsById) throws IOException { @@ -564,7 +571,7 @@ private ComputerLauncher HandleSSHLauncher(SlaveInstanceDetails instanceDetailsB if (ipAddress != null) { try { - Boolean shouldRetriggerBuilds = this.getShouldRetriggerBuilds(); + Boolean shouldRetriggerBuilds = this.getShouldReTriggerBuilds(); retVal = new SpotSSHComputerLauncher( this.getComputerConnector().launch(instanceDetailsById.getPublicIp(), TaskListener.NULL), shouldRetriggerBuilds); @@ -645,7 +652,7 @@ protected PendingExecutorsCounts getPendingExecutors(ProvisionRequest request) { } protected Integer getNumOfExecutors(String instanceType) { - Integer retVal; + int retVal; boolean isSingleTaskNodesEnabled = getIsSingleTaskNodesEnabled(); if (isSingleTaskNodesEnabled) { @@ -753,12 +760,32 @@ public void setShouldUseWebsocket(Boolean shouldUseWebsocket) { this.shouldUseWebsocket = shouldUseWebsocket; } - public Boolean getShouldRetriggerBuilds() { - return shouldRetriggerBuilds; + public SpotReTriggerBuilds getSpotReTriggerBuilds() { + if(spotReTriggerBuilds == null){ + spotReTriggerBuilds = new SpotReTriggerBuilds(false, false); + } + + return spotReTriggerBuilds; } - public void setShouldRetriggerBuilds(Boolean shouldRetriggerBuilds) { - this.shouldRetriggerBuilds = shouldRetriggerBuilds; + public void setSpotReTriggerBuilds(SpotReTriggerBuilds spotReTriggerBuilds) { + this.spotReTriggerBuilds = spotReTriggerBuilds; + } + + public Boolean getShouldReTriggerBuilds() { + return getSpotReTriggerBuilds().getShouldReTriggerBuilds(); + } + + public void setShouldReTriggerBuilds(Boolean shouldReTriggerBuilds) { + getSpotReTriggerBuilds().setShouldReTriggerBuilds(shouldReTriggerBuilds); + } + + public Boolean getStickyNode() { + return getSpotReTriggerBuilds().getStickyNode(); + } + + public void setStickyNode(Boolean stickyNode) { + getSpotReTriggerBuilds().setStickyNode(stickyNode); } public ConnectionMethodEnum getConnectionMethod() { @@ -787,6 +814,7 @@ public boolean getShouldUsePrivateIp() { if (shouldUsePrivateIp == null) { return false; } + return shouldUsePrivateIp; } @@ -894,7 +922,18 @@ public Boolean isStatefulGroup() { return isStatefulGroup; } - protected abstract String getSsiId(String instanceId); + public String getSsiId(String instanceId){ + String retVal = null; + BaseStatefulInstance statefulInstance = getStatefulInstance(instanceId); + + if(statefulInstance != null){ + retVal = statefulInstance.getId(); + } + + return retVal; + } + + protected abstract BaseStatefulInstance getStatefulInstance(String instanceId); protected abstract Boolean detachInstance(String instanceId); diff --git a/src/main/java/hudson/plugins/spotinst/cloud/GcpSpotinstCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/GcpSpotinstCloud.java index 53184cef..ed4a5886 100644 --- a/src/main/java/hudson/plugins/spotinst/cloud/GcpSpotinstCloud.java +++ b/src/main/java/hudson/plugins/spotinst/cloud/GcpSpotinstCloud.java @@ -5,6 +5,7 @@ import hudson.plugins.spotinst.api.infra.ApiResponse; import hudson.plugins.spotinst.api.infra.JsonMapper; import hudson.plugins.spotinst.common.ConnectionMethodEnum; +import hudson.plugins.spotinst.model.common.BaseStatefulInstance; import hudson.plugins.spotinst.model.common.BlResponse; import hudson.plugins.spotinst.model.gcp.GcpGroupInstance; import hudson.plugins.spotinst.model.gcp.GcpMachineType; @@ -43,14 +44,14 @@ public class GcpSpotinstCloud extends BaseSpotinstCloud { @DataBoundConstructor public GcpSpotinstCloud(String groupId, String labelString, String idleTerminationMinutes, String workspaceDir, SlaveUsageEnum usage, String tunnel, Boolean shouldUseWebsocket, - Boolean shouldRetriggerBuilds, String vmargs, + SpotReTriggerBuilds spotReTriggerBuilds, String vmargs, EnvironmentVariablesNodeProperty environmentVariables, ToolLocationNodeProperty toolLocations, String accountId, ConnectionMethodEnum connectionMethod, ComputerConnector computerConnector, Boolean shouldUsePrivateIp, SpotGlobalExecutorOverride globalExecutorOverride, Integer pendingThreshold) { super(groupId, labelString, idleTerminationMinutes, workspaceDir, usage, tunnel, shouldUseWebsocket, - shouldRetriggerBuilds, vmargs, environmentVariables, toolLocations, accountId, connectionMethod, + spotReTriggerBuilds, vmargs, environmentVariables, toolLocations, accountId, connectionMethod, computerConnector, shouldUsePrivateIp, globalExecutorOverride, pendingThreshold); } //endregion @@ -105,7 +106,7 @@ protected BlResponse checkIsStatefulGroup() { } @Override - protected String getSsiId(String instanceId) { + protected BaseStatefulInstance getStatefulInstance(String instanceId) { return null;//TODO: implement } diff --git a/src/main/java/hudson/plugins/spotinst/cloud/SpotReTriggerBuilds.java b/src/main/java/hudson/plugins/spotinst/cloud/SpotReTriggerBuilds.java new file mode 100644 index 00000000..ac9cecfb --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/cloud/SpotReTriggerBuilds.java @@ -0,0 +1,68 @@ +package hudson.plugins.spotinst.cloud; + +import hudson.Extension; +import hudson.model.Describable; +import hudson.model.Descriptor; +import jenkins.model.Jenkins; +import org.apache.commons.lang.BooleanUtils; +import org.kohsuke.stapler.DataBoundConstructor; + +import javax.annotation.Nonnull; + +public class SpotReTriggerBuilds implements Describable { + //region members + private Boolean shouldReTriggerBuilds; + private Boolean stickyNode; + //endregion + + //region Ctor + @DataBoundConstructor + public SpotReTriggerBuilds(Boolean shouldReTriggerBuilds, Boolean stickyNode) { + this.shouldReTriggerBuilds = BooleanUtils.isTrue(shouldReTriggerBuilds); + this.stickyNode = this.shouldReTriggerBuilds && BooleanUtils.isTrue(stickyNode); + } + //endregion + + //region Overrides + @Override + public Descriptor getDescriptor() { + Descriptor retVal = Jenkins.get().getDescriptor(getClass()); + + if (retVal == null) { + throw new RuntimeException("Descriptor of type SpotReTriggerBuilds cannot be null"); + } + + return retVal; + } + //endregion + + //region Classes + @Extension + public static final class DescriptorImpl extends Descriptor { + + @Nonnull + @Override + public String getDisplayName() { + return "Spot ReTrigger Builds"; + } + } + //endregion + + //region getters & setters + public Boolean getShouldReTriggerBuilds() { + return shouldReTriggerBuilds; + } + + public void setShouldReTriggerBuilds(Boolean shouldReTriggerBuilds) { + this.shouldReTriggerBuilds = shouldReTriggerBuilds; + } + + public Boolean getStickyNode() { + return stickyNode; + } + + public void setStickyNode(Boolean stickyNode) { + this.stickyNode = stickyNode; + } + //endregio +} diff --git a/src/main/java/hudson/plugins/spotinst/common/stateful/StatefulInstanceStateEnum.java b/src/main/java/hudson/plugins/spotinst/common/stateful/AwsStatefulInstanceStateEnum.java similarity index 77% rename from src/main/java/hudson/plugins/spotinst/common/stateful/StatefulInstanceStateEnum.java rename to src/main/java/hudson/plugins/spotinst/common/stateful/AwsStatefulInstanceStateEnum.java index 0479bde3..8473c8d3 100644 --- a/src/main/java/hudson/plugins/spotinst/common/stateful/StatefulInstanceStateEnum.java +++ b/src/main/java/hudson/plugins/spotinst/common/stateful/AwsStatefulInstanceStateEnum.java @@ -6,7 +6,7 @@ /** * Created by sitay on 30/08/23. */ -public enum StatefulInstanceStateEnum { +public enum AwsStatefulInstanceStateEnum { //region Enums ACTIVE("ACTIVE"), PAUSE("PAUSE"), @@ -23,12 +23,12 @@ public enum StatefulInstanceStateEnum { //endregion //region Members - private static final Logger LOGGER = LoggerFactory.getLogger(StatefulInstanceStateEnum.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AwsStatefulInstanceStateEnum.class); private final String name; //endregion //region Constructors - StatefulInstanceStateEnum(String name) { + AwsStatefulInstanceStateEnum(String name) { this.name = name; } //endregion @@ -40,10 +40,10 @@ public String getName() { //endregion //region Methods - public static StatefulInstanceStateEnum fromName(String name) { - StatefulInstanceStateEnum retVal = null; + public static AwsStatefulInstanceStateEnum fromName(String name) { + AwsStatefulInstanceStateEnum retVal = null; - for (StatefulInstanceStateEnum deploymentInstanceStatus : StatefulInstanceStateEnum.values()) { + for (AwsStatefulInstanceStateEnum deploymentInstanceStatus : AwsStatefulInstanceStateEnum.values()) { if (name.equalsIgnoreCase(deploymentInstanceStatus.name)) { retVal = deploymentInstanceStatus; break; diff --git a/src/main/java/hudson/plugins/spotinst/common/stateful/StatefulInstanceManager.java b/src/main/java/hudson/plugins/spotinst/common/stateful/StatefulInstanceManager.java new file mode 100644 index 00000000..9d1c2c1a --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/common/stateful/StatefulInstanceManager.java @@ -0,0 +1,204 @@ +package hudson.plugins.spotinst.common.stateful; + +import hudson.model.Executor; +import hudson.model.Node; +import hudson.model.Queue; +import hudson.model.queue.CauseOfBlockage; +import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulInstance; +import hudson.plugins.spotinst.model.common.BaseStatefulInstance; +import hudson.plugins.spotinst.queue.StatefulInterruptedTask; +import hudson.plugins.spotinst.slave.SpotinstSlave; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class StatefulInstanceManager { + //region members + private static final Logger LOGGER = + LoggerFactory.getLogger(StatefulInstanceManager.class); + private static final Map> statefulInstanceIdsByGroupId = + new ConcurrentHashMap<>(); + private static final Map ssiByStatefulTask = + new ConcurrentHashMap<>(); + private static final Map statefulTaskByTask = + new ConcurrentHashMap<>(); + //endregion + + //region methods + public static CauseOfBlockage canNodeTakeStatefulTask(Node node, String statefulTaskSsi) { + CauseOfBlockage retVal = null; + Optional> optionalMatchingGroupStatefulInstances = + statefulInstanceIdsByGroupId.values().stream().filter(statefulInstances -> statefulInstances.stream() + .anyMatch( + statefulInstance -> statefulInstance.getId() + .equals(statefulTaskSsi))) + .findFirst(); + boolean isStatefulInstanceExist = optionalMatchingGroupStatefulInstances.isPresent(); + + if (isStatefulInstanceExist) { + + Optional optionalMatchingStatefulInstance = + optionalMatchingGroupStatefulInstances.get().stream() + .filter(statefulInstance -> statefulInstance.getId() + .equals(statefulTaskSsi)) + .findFirst(); + + if (optionalMatchingStatefulInstance.isPresent()) { + retVal = canStatefulInstanceTakeStatefulTask(node, optionalMatchingStatefulInstance.get(), + statefulTaskSsi); + } + } + else { + LOGGER.info( + "SSI '{}' does not exist any more. reserved tasks for the stateful instance can be taken by any node", + statefulTaskSsi); + } + + return retVal; + } + + public static String getSsiByTask(Queue.Task task, Executor executor) { + String retVal; + String key = generateKey(task, executor); + retVal = ssiByStatefulTask.get(key); + return retVal; + } + + public static void putSsiByTask(Queue.Task task, Executor executor, String ssiId) { + String key = generateKey(task, executor); + ssiByStatefulTask.put(key, ssiId); + } + + public static String removeSsiByTask(Queue.Task task, Executor executor) { + String retVal; + String key = generateKey(task, executor); + retVal = ssiByStatefulTask.remove(key); + return retVal; + } + + public static StatefulInterruptedTask getStatefulTaskByTask(Queue.Task task, Executor executor) { + StatefulInterruptedTask retVal; + String key = generateKey(task, executor); + retVal = statefulTaskByTask.get(key); + return retVal; + } + + public static void putStatefulTaskByTask(Queue.Task task, Executor executor, StatefulInterruptedTask statefulTask) { + String key = generateKey(task, executor); + statefulTaskByTask.put(key, statefulTask); + } + + public static StatefulInterruptedTask removeStatefulTaskByTask(Queue.Task task, Executor executor) { + StatefulInterruptedTask retVal; + String key = generateKey(task, executor); + retVal = statefulTaskByTask.remove(key); + return retVal; + } + //endregion + + //region private methods + private static String generateKey(Queue.Task task, Executor executor) { + return String.format("%s_%s", task, executor.getId()); + } + + private static CauseOfBlockage canStatefulInstanceTakeStatefulTask(Node node, BaseStatefulInstance statefulInstance, + String statefulTaskSsi) { + CauseOfBlockage retVal; + + if (statefulInstance instanceof AwsStatefulInstance) { + retVal = canAwsStatefulInstanceTakeStatefulTask(node, (AwsStatefulInstance) statefulInstance, + statefulTaskSsi); + } + else { + LOGGER.warn("stateful instance of unsupported cloud '{}'", statefulTaskSsi); + retVal = new InconsistentSsiCauseOfBlockage(); + } + + return retVal; + } + + private static CauseOfBlockage canAwsStatefulInstanceTakeStatefulTask(Node node, + AwsStatefulInstance statefulInstance, + String statefulTaskSsi) { + CauseOfBlockage retVal = null; + AwsStatefulInstanceStateEnum ssiState = statefulInstance.getState(); + + switch (ssiState) { + case ACTIVE: + boolean isNodeMatchingTaskSsi = checkMatchingSsis(node, statefulTaskSsi); + + if (isNodeMatchingTaskSsi) { + LOGGER.info("found slave for stateful task with SSI '{}'", statefulTaskSsi); + } + else { + retVal = new InconsistentSsiCauseOfBlockage(); + } + + break; + + case RECYCLE: + case RECYCLING: + case RESUME: + case RESUMING: + LOGGER.info("stateful task awaits for SSI {} to be active. no node can take it", statefulTaskSsi); + retVal = new AwaitingSsiCauseOfBlockage(); + break; + + case PAUSE: + case PAUSING: + case PAUSED: + case DEALLOCATE: + case DEALLOCATING: + case DEALLOCATED: + case ERROR: + LOGGER.info("SSI {} is in state {} and isn't activated, stateful task can run on any node", + statefulTaskSsi, ssiState); + break; + } + + return retVal; + } + + private static Boolean checkMatchingSsis(Node node, String statefulTaskSsi) { + boolean retVal = false; + + if (node instanceof SpotinstSlave) { + SpotinstSlave slave = (SpotinstSlave) node; + String ssi = slave.getSsiId(); + boolean isStatefulSlave = StringUtils.isNotEmpty(ssi); + + if (isStatefulSlave) { + retVal = ssi.equals(statefulTaskSsi); + } + } + + return retVal; + } + + //endregion + + //region getters & setters + public static Map> getStatefulInstanceIdsByGroupId() { + return statefulInstanceIdsByGroupId; + } + //endregion + + //region classes + private static class InconsistentSsiCauseOfBlockage extends CauseOfBlockage { + @Override + public String getShortDescription() { + return "task reserved for slave with ssi different from current node"; + } + } + + private static class AwaitingSsiCauseOfBlockage extends CauseOfBlockage { + @Override + public String getShortDescription() { + return "awaiting for stateful node to recover"; + } + } + //endregion +} diff --git a/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsDeallocateStatefulInstanceRequest.java b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsDeallocateStatefulInstanceRequest.java index fad0a7c3..60f9e927 100644 --- a/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsDeallocateStatefulInstanceRequest.java +++ b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsDeallocateStatefulInstanceRequest.java @@ -8,16 +8,16 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class AwsDeallocateStatefulInstanceRequest { //region members - private AwsStatefulDeallocationConfig statefulDeallocation; + private AwsStatefulDeAllocationConfig statefulDeAllocationConfig; //endregion //region getters & setters - public AwsStatefulDeallocationConfig getStatefulDeallocation() { - return statefulDeallocation; + public AwsStatefulDeAllocationConfig getStatefulDeAllocationConfig() { + return statefulDeAllocationConfig; } - public void setStatefulDeallocation(AwsStatefulDeallocationConfig statefulDeallocation) { - this.statefulDeallocation = statefulDeallocation; + public void setStatefulDeAllocationConfig(AwsStatefulDeAllocationConfig statefulDeAllocationConfig) { + this.statefulDeAllocationConfig = statefulDeAllocationConfig; } //endregion } \ No newline at end of file diff --git a/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulDeallocationConfig.java b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulDeAllocationConfig.java similarity index 96% rename from src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulDeallocationConfig.java rename to src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulDeAllocationConfig.java index 7cb46075..67cf20ba 100644 --- a/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulDeallocationConfig.java +++ b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulDeAllocationConfig.java @@ -6,7 +6,7 @@ * Created by sitay on 30/08/23. */ @JsonIgnoreProperties(ignoreUnknown = true) -public class AwsStatefulDeallocationConfig { +public class AwsStatefulDeAllocationConfig { //region members private Boolean shouldDeleteImages; private Boolean shouldDeleteNetworkInterfaces; diff --git a/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulInstance.java b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulInstance.java index e02bbea1..20b4e560 100644 --- a/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulInstance.java +++ b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulInstance.java @@ -2,29 +2,21 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; -import hudson.plugins.spotinst.common.stateful.StatefulInstanceStateEnum; +import hudson.plugins.spotinst.common.stateful.AwsStatefulInstanceStateEnum; +import hudson.plugins.spotinst.model.common.BaseStatefulInstance; /** * Created by ItayShklar on 07/08/2023. */ @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) -public class AwsStatefulInstance { +public class AwsStatefulInstance extends BaseStatefulInstance { //region members - private String id; - private String instanceId; - private StatefulInstanceStateEnum state; + private String instanceId; + private AwsStatefulInstanceStateEnum state; //endregion //region getters & setters - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - public String getInstanceId() { return instanceId; } @@ -33,11 +25,11 @@ public void setInstanceId(String instanceId) { this.instanceId = instanceId; } - public StatefulInstanceStateEnum getState() { + public AwsStatefulInstanceStateEnum getState() { return state; } - public void setState(StatefulInstanceStateEnum state) { + public void setState(AwsStatefulInstanceStateEnum state) { this.state = state; } //endregion diff --git a/src/main/java/hudson/plugins/spotinst/model/common/BaseStatefulInstance.java b/src/main/java/hudson/plugins/spotinst/model/common/BaseStatefulInstance.java new file mode 100644 index 00000000..10f19b25 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/model/common/BaseStatefulInstance.java @@ -0,0 +1,26 @@ +package hudson.plugins.spotinst.model.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * Created by ItayShklar on 07/08/2023. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BaseStatefulInstance { + //region members + private String id; + //endregion + + //region getters & setters + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + //endregion +} + diff --git a/src/main/java/hudson/plugins/spotinst/queue/SpotQueueStatefulTaskDispatcher.java b/src/main/java/hudson/plugins/spotinst/queue/SpotQueueStatefulTaskDispatcher.java new file mode 100644 index 00000000..d08c020e --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/queue/SpotQueueStatefulTaskDispatcher.java @@ -0,0 +1,42 @@ +package hudson.plugins.spotinst.queue; + +import hudson.Extension; +import hudson.model.Node; +import hudson.model.Queue; +import hudson.model.queue.CauseOfBlockage; +import hudson.model.queue.QueueTaskDispatcher; +import hudson.plugins.spotinst.common.stateful.StatefulInstanceManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.CheckForNull; + + +/** + * Created by sitay on 30/08/2013. + */ +@Extension +public class SpotQueueStatefulTaskDispatcher extends QueueTaskDispatcher { + //region members + private static final Logger LOGGER = LoggerFactory.getLogger(SpotQueueStatefulTaskDispatcher.class); + //endregion + + //region override methods + @Override + @CheckForNull + public CauseOfBlockage canTake(Node node, Queue.BuildableItem item) { + CauseOfBlockage retVal = null; + LOGGER.info("can node {} take item {}", node, item);//TODO: remove + Queue.Task task = item.task; + boolean isTaskReservedForStatefulSlave = task instanceof StatefulInterruptedTask; + + if (isTaskReservedForStatefulSlave) { + StatefulInterruptedTask statefulInterruptedTask = (StatefulInterruptedTask) task; + String statefulTaskSsi = statefulInterruptedTask.getSsi(); + retVal = StatefulInstanceManager.canNodeTakeStatefulTask(node, statefulTaskSsi); + } + + return retVal; + } + //endregion +} diff --git a/src/main/java/hudson/plugins/spotinst/queue/StatefulInterruptedTask.java b/src/main/java/hudson/plugins/spotinst/queue/StatefulInterruptedTask.java new file mode 100644 index 00000000..5719c84a --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/queue/StatefulInterruptedTask.java @@ -0,0 +1,160 @@ +package hudson.plugins.spotinst.queue; + +import hudson.model.Label; +import hudson.model.Node; +import hudson.model.Queue; +import hudson.model.ResourceList; +import hudson.model.queue.CauseOfBlockage; +import hudson.model.queue.SubTask; +import org.acegisecurity.Authentication; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.Collection; + +public class StatefulInterruptedTask implements Queue.Task { + //region members + @Nonnull + private final String ssi; + @Nonnull + private final Queue.Task task; + //endregion + + //region ctor + public StatefulInterruptedTask(@Nonnull String ssi, @Nonnull Queue.Task task) { + this.ssi = ssi; + + if(task instanceof StatefulInterruptedTask){ + this.task = ((StatefulInterruptedTask) task).getTask(); + } + else { + this.task = task; + } + } + //endregion + + //region overrides + @Override + public String getName() { + return task.getName(); + } + + @Override + public ResourceList getResourceList() { + return task.getResourceList(); + } + + @Override + public String getDisplayName() { + return task.getDisplayName(); + } + + @Override + public String getFullDisplayName() { + return task.getFullDisplayName(); + } + + @Override + public String getUrl() { + return task.getUrl(); + } + + @Override + public boolean isConcurrentBuild() { + return task.isConcurrentBuild(); + } + + @Override + public Collection getSubTasks() { + return task.getSubTasks(); + } + + @Override + public String getAffinityKey() { + return task.getAffinityKey(); + } + + @Override + public void checkAbortPermission() { + task.checkAbortPermission(); + } + + @Override + public CauseOfBlockage getCauseOfBlockage() { + return task.getCauseOfBlockage(); + } + + @Override + public boolean isBuildBlocked() { + return task.isBuildBlocked(); + } + + @Override + public String getWhyBlocked() { + return task.getWhyBlocked(); + } + + @Nonnull + @Override + public Authentication getDefaultAuthentication() { + return task.getDefaultAuthentication(); + } + + @Nonnull + @Override + public Authentication getDefaultAuthentication(Queue.Item item) { + return task.getDefaultAuthentication(item); + } + + @Override + public boolean hasAbortPermission() { + return task.hasAbortPermission(); + } + + + @Override + public Label getAssignedLabel() { + return task.getAssignedLabel(); + } + + @Override + public Node getLastBuiltOn() { + return task.getLastBuiltOn(); + } + + @Override + public long getEstimatedDuration() { + return task.getEstimatedDuration(); + } + + @CheckForNull + @Override + public Queue.Executable createExecutable() throws IOException { + return task.createExecutable(); + } + + @Nonnull + @Override + public Queue.Task getOwnerTask() { + return task.getOwnerTask(); + } + + @Override + public Object getSameNodeConstraint() { + return task.getSameNodeConstraint(); + } + //endregion + + //region getters & setters + @Nonnull + public Queue.Task getTask() { + return task; + } + + @Nonnull + public String getSsi() { + return ssi; + } + //endregion +} diff --git a/src/main/java/hudson/plugins/spotinst/slave/SpotLauncherHelper.java b/src/main/java/hudson/plugins/spotinst/slave/SpotLauncherHelper.java index c89c3dc2..699ae6ca 100644 --- a/src/main/java/hudson/plugins/spotinst/slave/SpotLauncherHelper.java +++ b/src/main/java/hudson/plugins/spotinst/slave/SpotLauncherHelper.java @@ -1,29 +1,31 @@ package hudson.plugins.spotinst.slave; import hudson.model.*; +import hudson.model.Queue; import hudson.model.queue.SubTask; +import hudson.plugins.spotinst.common.stateful.StatefulInstanceManager; +import hudson.plugins.spotinst.queue.StatefulInterruptedTask; import hudson.slaves.SlaveComputer; import org.apache.commons.lang.BooleanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.LinkedList; -import java.util.List; +import java.util.*; /** - * Created by Shibel Karmi Mansour on 31/12/2020. - * + * Created by Shibel Karmi Mansour on 31/12/2020.* * A helper class for handling common callbacks (like afterDisconnect) for different types of * ComputerLaunchers. */ class SpotLauncherHelper { //region Members private static final Logger LOGGER = LoggerFactory.getLogger(SpotLauncherHelper.class); + private static final Object lock = new Object(); //endregion //region Methods - static void handleDisconnect(final SlaveComputer computer, Boolean shouldRetriggerBuilds){ + static void handleDisconnect(final SlaveComputer computer, Boolean shouldRetriggerBuilds) { shouldRetriggerBuilds = resolveShouldRetriggerBuilds(shouldRetriggerBuilds); @@ -32,7 +34,6 @@ static void handleDisconnect(final SlaveComputer computer, Boolean shouldRetrigg SpotinstComputer spotinstComputer = (SpotinstComputer) computer; SpotinstSlave slave = spotinstComputer.getNode(); - if (shouldRetriggerBuilds && (slave == null || BooleanUtils.isFalse(slave.isSlavePending()))) { LOGGER.info(String.format("Start retriggering executors for %s", spotinstComputer.getDisplayName())); @@ -53,9 +54,28 @@ static void handleDisconnect(final SlaveComputer computer, Boolean shouldRetrigg actions = ((Actionable) executable).getActions(); } - LOGGER.info(String.format("RETRIGGERING: %s - WITH ACTIONS: %s", task, actions)); - Queue.getInstance().schedule2(task, 10, actions); + synchronized (lock) { + String ssiByTaskName = StatefulInstanceManager.getSsiByTask(task, executor); + + if (ssiByTaskName != null) { + StatefulInterruptedTask statefulInterruptedTask = + StatefulInstanceManager.getStatefulTaskByTask(task, executor); + + if (statefulInterruptedTask == null) { + statefulInterruptedTask = new StatefulInterruptedTask(ssiByTaskName, task); + StatefulInstanceManager.putStatefulTaskByTask(task, executor, statefulInterruptedTask); + } + + LOGGER.info(String.format("RETRIGGERING Stateful Task: %s - WITH ACTIONS: %s on SSI %s", + statefulInterruptedTask.getTask(), actions, ssiByTaskName)); + Queue.getInstance().schedule2(statefulInterruptedTask, 10, actions); + } + else { + LOGGER.info(String.format("RETRIGGERING: %s - WITH ACTIONS: %s", task, actions)); + Queue.getInstance().schedule2(task, 10, actions); + } + } } } diff --git a/src/main/java/hudson/plugins/spotinst/slave/SpotinstComputer.java b/src/main/java/hudson/plugins/spotinst/slave/SpotinstComputer.java index 8c34a1b8..d099fec1 100644 --- a/src/main/java/hudson/plugins/spotinst/slave/SpotinstComputer.java +++ b/src/main/java/hudson/plugins/spotinst/slave/SpotinstComputer.java @@ -4,7 +4,9 @@ import hudson.model.Node; import hudson.model.Queue; import hudson.plugins.spotinst.cloud.BaseSpotinstCloud; +import hudson.plugins.spotinst.common.stateful.StatefulInstanceManager; import hudson.slaves.SlaveComputer; +import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.HttpRedirect; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; @@ -41,8 +43,23 @@ public void taskAccepted(Executor executor, Queue.Task task) { LOGGER.info(msg); this.setAcceptingTasks(false); SpotinstNonLocalizable spotinstNonLocalizable = new SpotinstNonLocalizable(msg); - SpotinstSingleTaskOfflineCause spotinstSingleTaskOfflineCause = new SpotinstSingleTaskOfflineCause(spotinstNonLocalizable); - this.setTemporarilyOffline(true,spotinstSingleTaskOfflineCause); + SpotinstSingleTaskOfflineCause spotinstSingleTaskOfflineCause = + new SpotinstSingleTaskOfflineCause(spotinstNonLocalizable); + this.setTemporarilyOffline(true, spotinstSingleTaskOfflineCause); + } + + if (spotinstCloud.getStickyNode()) { + String spotinstNodeSsiId = spotinstNode.getSsiId(); + + if (StringUtils.isNotEmpty(spotinstNodeSsiId)) { + LOGGER.info("task {} accepted on executor {} and is bound to ssi {}", task, executor.getId(), + spotinstNodeSsiId); + StatefulInstanceManager.putSsiByTask(task, executor, spotinstNodeSsiId); + } + else { + LOGGER.info("task {} accepted on a stateless slave {}. no restrictions occur", task.getName(), + spotinstNode.getNodeName()); + } } } else { @@ -50,12 +67,39 @@ public void taskAccepted(Executor executor, Queue.Task task) { "Node %s has accepted a job but can't determine 'Single Task Nodes' setting because SpotinstNode's SpotinstCloud appears to be null.", spotinstNode.getNodeName())); } - } else { + } + else { LOGGER.error(String.format( - "Executor of Node %s has accepted a job but can't determine 'Single Task Nodes' setting because SpotinstNode is null.", executor.getOwner().getName())); + "Executor of Node %s has accepted a job but can't determine 'Single Task Nodes' setting because SpotinstNode is null.", + executor.getOwner().getName())); } } + @Override + public void taskCompleted(Executor executor, Queue.Task task, long durationMS) { + super.taskCompleted(executor, task, durationMS); + SpotinstSlave spotinstNode = this.getNode(); + + if (spotinstNode != null) { + BaseSpotinstCloud spotinstCloud = spotinstNode.getSpotinstCloud(); + + if (spotinstCloud != null) { + if (spotinstCloud.getStickyNode()) { + String spotinstNodeSsiId = spotinstNode.getSsiId(); + boolean isStatefulNode = StringUtils.isNotEmpty(spotinstNodeSsiId); + + if (isStatefulNode) { + LOGGER.info("task {} accepted on executor {} and is bound to ssi {}", task, executor.getId(), + spotinstNodeSsiId); + String attachedSsi = StatefulInstanceManager.removeSsiByTask(task, executor); + LOGGER.info("unbinding stateful task {} and executor {} from ssi {}", task.getName(), + executor.getId(), attachedSsi); + StatefulInstanceManager.removeStatefulTaskByTask(task, executor); + } + } + } + } + } //endregion //region Constructor @@ -94,7 +138,8 @@ public HttpResponse doDoDelete() throws IOException { } return new HttpRedirect(".."); - } catch (NullPointerException ex) { + } + catch (NullPointerException ex) { return HttpResponses.error(500, ex); } } @@ -114,6 +159,5 @@ public void resyncNode() { this.setNode(node); } } - //endregion } diff --git a/src/main/java/hudson/plugins/spotinst/slave/SpotinstSlave.java b/src/main/java/hudson/plugins/spotinst/slave/SpotinstSlave.java index 6e71c91d..bb4afbb9 100644 --- a/src/main/java/hudson/plugins/spotinst/slave/SpotinstSlave.java +++ b/src/main/java/hudson/plugins/spotinst/slave/SpotinstSlave.java @@ -2,10 +2,12 @@ import hudson.Extension; import hudson.model.*; +import hudson.model.queue.CauseOfBlockage; import hudson.plugins.spotinst.cloud.BaseSpotinstCloud; import hudson.slaves.*; import jenkins.model.Jenkins; import net.sf.json.JSONObject; +import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.StaplerRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +27,7 @@ public class SpotinstSlave extends Slave implements EphemeralNode { private String instanceId; private String instanceType; + private String ssiId; private String elastigroupId; private String workspaceDir; private String groupUrl; @@ -34,9 +37,9 @@ public class SpotinstSlave extends Slave implements EphemeralNode { //endregion //region Constructor - public SpotinstSlave(String name, String elastigroupId, String instanceId, String instanceType, String label, - String idleTerminationMinutes, String workspaceDir, String numOfExecutors, Mode mode, - ComputerLauncher launcher, + public SpotinstSlave(String name, String elastigroupId, String instanceId, String instanceType, String ssiId, + String label, String idleTerminationMinutes, String workspaceDir, String numOfExecutors, + Mode mode, ComputerLauncher launcher, List> nodeProperties) throws Descriptor.FormException, IOException { @@ -45,6 +48,7 @@ public SpotinstSlave(String name, String elastigroupId, String instanceId, Strin this.elastigroupId = elastigroupId; this.instanceType = instanceType; + this.ssiId = ssiId; this.instanceId = instanceId; this.workspaceDir = workspaceDir; this.usage = SlaveUsageEnum.fromMode(mode); @@ -66,6 +70,14 @@ public String getInstanceType() { return instanceType; } + public String getSsiId() { + return ssiId; + } + + public void setSsiId(String ssiId) { + this.ssiId = ssiId; + } + public String getWorkspaceDir() { return workspaceDir; } @@ -170,6 +182,85 @@ public Node reconfigure(StaplerRequest req, JSONObject form) throws Descriptor.F public Node asNode() { return this; } + +// @Override +// public CauseOfBlockage canTake(Queue.BuildableItem item){ +// CauseOfBlockage retVal = super.canTake(item); +// +// if(retVal == null) { +// LOGGER.info("can node {} take item {}", this, item);//TODO: remove +// Queue.Task task = item.task; +// boolean isTaskReservedForStatefulSlave = task instanceof StatefulInterruptedTask; +// +// if (isTaskReservedForStatefulSlave) { +// StatefulInterruptedTask statefulInterruptedTask = (StatefulInterruptedTask) task; +// String statefulTaskSsi = statefulInterruptedTask.getSsi(); +// BaseSpotinstCloud slaveSpotinstCloud = this.getSpotinstCloud(); +// retVal = slaveSpotinstCloud.canTakeStatefulTask(this.getInstanceId(), statefulTaskSsi); +// AwsStatefulInstance +// matchingStatefulInstance = AwsStatefulInstancesManager.getStatefulInstanceBySSi(statefulTaskSsi); +// boolean isSsiExist = matchingStatefulInstance != null; +// +// if (isSsiExist) { +// AwsStatefulInstanceStateEnum ssiState = matchingStatefulInstance.getState(); +// +// switch (ssiState) { +// case PAUSE: +// case PAUSING: +// case PAUSED: +// case DEALLOCATE: +// case DEALLOCATING: +// case DEALLOCATED: +// case ERROR: +// LOGGER.info("SSI {} is in state {} and isn't activated, task {} can run on any node", +// statefulTaskSsi, ssiState, task.getName()); +// break; +// +// case ACTIVE: +// retVal = checkMatchingSsis(statefulTaskSsi); +// break; +// +// case RECYCLE: +// case RECYCLING: +// case RESUME: +// case RESUMING: +// default: +// LOGGER.info("task {} awaits for SSI {} to be active. no node can take it", task.getName(), +// statefulTaskSsi); +// retVal = new AwaitingSsiCauseOfBlockage(); +// break; +// } +// } +// } +// } +// +// return retVal; +// } + + private CauseOfBlockage checkMatchingSsis(String statefulTaskSsi) { + CauseOfBlockage retVal = null; + String slaveSsi = getSsiId(); + boolean isStatefulSlave = StringUtils.isNotEmpty(slaveSsi); + + if (isStatefulSlave) { + boolean canNodeTakeTask = slaveSsi.equals(statefulTaskSsi); + + if (canNodeTakeTask) { + LOGGER.info("node {} with ssi {} can take task", getNodeName(), slaveSsi); + } + else { + LOGGER.info("task reserved for stateful slave with SSI {} different from current slave's SSI {}", + statefulTaskSsi, slaveSsi); + retVal = new InconsistentSsiCauseOfBlockage(); + } + } + else { + LOGGER.info("task reserved for stateful slave with SSI {}. current slave is not a stateful", statefulTaskSsi); + retVal = new InconsistentSsiCauseOfBlockage(); + } + + return retVal; + } //endregion //region Public Methods @@ -268,5 +359,19 @@ public boolean isInstantiable() { return false; } } + + private static class InconsistentSsiCauseOfBlockage extends CauseOfBlockage { + @Override + public String getShortDescription() { + return "task reserved for slave with ssi different from current node"; + } + } + + private static class AwaitingSsiCauseOfBlockage extends CauseOfBlockage { + @Override + public String getShortDescription() { + return "awaiting for stateful node to recover"; + } + } //endregion } diff --git a/src/main/resources/hudson/plugins/spotinst/cloud/BaseSpotinstCloud/config.jelly b/src/main/resources/hudson/plugins/spotinst/cloud/BaseSpotinstCloud/config.jelly index 6f39a422..bf795e42 100644 --- a/src/main/resources/hudson/plugins/spotinst/cloud/BaseSpotinstCloud/config.jelly +++ b/src/main/resources/hudson/plugins/spotinst/cloud/BaseSpotinstCloud/config.jelly @@ -1,5 +1,5 @@ - + @@ -25,7 +25,7 @@ - + ${it.name()} @@ -64,14 +64,8 @@ - - - - - - - - + + @@ -129,7 +123,7 @@ Enabling this setting effectively means: override the Default executor count setting and ignore any instance-weight overrides if those are set. - + diff --git a/src/main/resources/hudson/plugins/spotinst/cloud/SpotReTriggerBuilds/config.jelly b/src/main/resources/hudson/plugins/spotinst/cloud/SpotReTriggerBuilds/config.jelly new file mode 100644 index 00000000..d4d2e056 --- /dev/null +++ b/src/main/resources/hudson/plugins/spotinst/cloud/SpotReTriggerBuilds/config.jelly @@ -0,0 +1,10 @@ + + + + + + Restrict re-triggered builds on the original ssi + In case of a stateful group. The rebuilds will use recycled resources according to stateful configuration. + + + \ No newline at end of file diff --git a/src/test/java/hudson/plugins/spotinst/SpotinstCloudTest.java b/src/test/java/hudson/plugins/spotinst/SpotinstCloudTest.java index 34470051..70d26bbc 100644 --- a/src/test/java/hudson/plugins/spotinst/SpotinstCloudTest.java +++ b/src/test/java/hudson/plugins/spotinst/SpotinstCloudTest.java @@ -42,7 +42,8 @@ public void setUp() { IAzureGroupRepo azureGroupRepo = Mockito.mock(IAzureGroupRepo.class); IAzureVmGroupRepo azureVmGroupRepo = Mockito.mock(IAzureVmGroupRepo.class); - Mockito.when(awsGroupRepo.getGroup(Mockito.anyString(), Mockito.anyString())).thenReturn(new ApiResponse<>(false)); + Mockito.when(awsGroupRepo.getGroup(Mockito.anyString(), Mockito.anyString())) + .thenReturn(new ApiResponse<>(false)); RepoManager.getInstance().setAwsGroupRepo(awsGroupRepo); RepoManager.getInstance().setGcpGroupRepo(gcpGroupRepo); @@ -89,8 +90,9 @@ private PendingInstance buildPendingInstance(String id, Integer executors) { public void testAwsProvision_whenThereArePendingInsatcnesForAllExecutors_thenShouldNotSacleUp() { String groupId = "sig-1"; BaseSpotinstCloud spotinstCloud = - new AwsSpotinstCloud(groupId, "", "20", "/tmp", null, SlaveUsageEnum.NORMAL, "", false, true, "", null, - null, null, null, null, null, null, null); + new AwsSpotinstCloud(groupId, "", "20", "/tmp", null, SlaveUsageEnum.NORMAL, "", false, + new SpotReTriggerBuilds(true, false), "", null, null, null, null, null, null, null, + null); Map pendingInstances = new HashMap<>(); pendingInstances.put("sir-1", buildPendingInstance("sir-1", 2)); spotinstCloud.setPendingInstances(pendingInstances); @@ -104,8 +106,9 @@ public void testAwsProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_t String groupId = "sig-1"; String accountId = "act-111"; AwsSpotinstCloud spotinstCloud = - new AwsSpotinstCloud(groupId, "", "20", "/tmp", null, SlaveUsageEnum.NORMAL, "", false, true, "", null, - null, accountId, null, null, null, null, null); + new AwsSpotinstCloud(groupId, "", "20", "/tmp", null, SlaveUsageEnum.NORMAL, "", false, + new SpotReTriggerBuilds(true, false), "", null, null, accountId, null, null, null, + null, null); jenkinsRule.jenkins.clouds.add(spotinstCloud); Map pendingInstances = new HashMap<>(); @@ -117,7 +120,7 @@ public void testAwsProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_t newSpot.setInstanceId("i-dqwadq"); newSpot.setAvailabilityZone("us-east-1a"); newSpot.setInstanceType(AwsInstanceTypeEnum.C4Large.getValue()); - result.setNewSpotRequests(Collections.singletonList(newSpot)); + result.setNewSpotRequests(Arrays.asList(newSpot)); Mockito.when(RepoManager.getInstance().getAwsGroupRepo() .scaleUp(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString())) .thenReturn(new ApiResponse<>(result)); @@ -162,7 +165,7 @@ public void testAwCloud_whenNoConnectionMethodIsProvided_thenDefaultIsJNLP() { for (Node node : allNodes) { SpotinstComputer computer = (SpotinstComputer) node.toComputer(); - assertNotNull(computer); + assert computer != null; assertEquals(computer.getLauncher().getClass(), SpotinstComputerLauncher.class); } @@ -210,7 +213,7 @@ public void testAwsCloud_whenSshConnectionMethod_andIpIsAvailable_thenCreateSshL spotCloud.monitorInstances(); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(incomingInstance.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getLauncher().getClass(), SpotSSHComputerLauncher.class); } @@ -259,7 +262,7 @@ public void testAwsCloud_whenSshConnectionMethod_andIpIsNotAvailable_thenDoNotCo SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode("i-2"); - assertNotNull(agent); + assert agent != null; assertNotEquals(agent.getLauncher().getClass(), SpotSSHComputerLauncher.class); // but neither create our JNLP launcher assertNotEquals(agent.getLauncher().getClass(), SpotinstComputerLauncher.class); @@ -302,8 +305,8 @@ public void testAwCloud_whenUsePrivateIpIsFalse_thenUsePublicIp() { public void testGcpProvision_whenThereArePendingInsatcnesForAllExecutors_thenShouldNotSacleUp() { String groupId = "sig-1"; BaseSpotinstCloud spotinstCloud = - new GcpSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, true, "", null, null, null, null, null, - null, null, null); + new GcpSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, new SpotReTriggerBuilds(true, false), + "", null, null, null, null, null, null, null, null); Map pendingInstances = new HashMap<>(); pendingInstances.put("sin-1", buildPendingInstance("sin-1", 2)); spotinstCloud.setPendingInstances(pendingInstances); @@ -317,8 +320,8 @@ public void testGcpProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_t String groupId = "sig-1"; String accountId = "act-111"; GcpSpotinstCloud spotinstCloud = - new GcpSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, true, "", null, null, accountId, null, - null, null, null, null); + new GcpSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, new SpotReTriggerBuilds(true, false), + "", null, null, accountId, null, null, null, null, null); jenkinsRule.jenkins.clouds.add(spotinstCloud); Map pendingInstances = new HashMap<>(); pendingInstances.put("sin-1", buildPendingInstance("sin-1", 2)); @@ -329,7 +332,7 @@ public void testGcpProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_t newInstance.setInstanceName("sin-2"); newInstance.setZone("us-east-1a"); newInstance.setMachineType(GcpMachineType.F1Micro.getName()); - result.setNewInstances(Collections.singletonList(newInstance)); + result.setNewInstances(Arrays.asList(newInstance)); Mockito.when(RepoManager.getInstance().getGcpGroupRepo() .scaleUp(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString())) .thenReturn(new ApiResponse<>(result)); @@ -350,9 +353,9 @@ public void testGcpProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_t @Test public void testAzureProvision_whenThereArePendingInsatcnesForAllExecutors_thenShouldNotSacleUp() { String groupId = "sig-1"; - BaseSpotinstCloud spotinstCloud = - new AzureSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, false, "", null, null, null, null, - null, null, null, null); + BaseSpotinstCloud spotinstCloud = new AzureSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, + new SpotReTriggerBuilds(false, false), "", null, null, + null, null, null, null, null, null); Map pendingInstances = new HashMap<>(); pendingInstances.put("q3213", buildPendingInstance(groupId, 1)); pendingInstances.put("41234", buildPendingInstance(groupId, 1)); @@ -366,9 +369,9 @@ public void testAzureProvision_whenThereArePendingInsatcnesForAllExecutors_thenS public void testAzureProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_thenShouldSacleUpTheRest() { String groupId = "sig-1"; String accountId = "act-111"; - AzureSpotinstCloud spotinstCloud = - new AzureSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, false, "", null, null, accountId, - null, null, null, null, null); + AzureSpotinstCloud spotinstCloud = new AzureSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, + new SpotReTriggerBuilds(false, false), "", null, null, + accountId, null, null, null, null, null); Map pendingInstances = new HashMap<>(); pendingInstances.put("asda", buildPendingInstance(groupId, 1)); pendingInstances.put("ada", buildPendingInstance(groupId, 1)); @@ -395,8 +398,8 @@ public void testAzureProvision_whenThereArePendingInsatcnesForPartOfTheExecutors public void testAzureV3Provision_whenThereArePendingInstancesForAllExecutors_thenShouldNotScaleUp() { String groupId = "sig-1"; BaseSpotinstCloud spotinstCloud = - new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, true, "", null, null, null, null, null, - null, null, null); + new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, new SpotReTriggerBuilds(true, false), "", + null, null, null, null, null, null, null, null); Map pendingInstances = new HashMap<>(); pendingInstances.put("vm-1", buildPendingInstance("vm-1", 2)); @@ -412,8 +415,8 @@ public void testAzureV3Provision_whenThereArePendingInstancesForPartOfTheExecuto String groupId = "sig-1"; String accountId = "act-111"; BaseSpotinstCloud spotinstCloud = - new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, true, "", null, null, accountId, null, - null, null, null, null); + new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, new SpotReTriggerBuilds(true, false), "", + null, null, accountId, null, null, null, null, null); jenkinsRule.jenkins.clouds.add(spotinstCloud); Map pendingInstances = new HashMap<>(); pendingInstances.put("vm-1", buildPendingInstance("vm-1", 2)); @@ -447,8 +450,8 @@ public void testAzureV3Provision_whenUnrecognizedVmSize_thenDefaultTo1Executor() String groupId = "sig-1"; String accountId = "act-111"; BaseSpotinstCloud spotinstCloud = - new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, true, "", null, null, accountId, null, - null, null, null, null); + new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, new SpotReTriggerBuilds(true, false), "", + null, null, accountId, null, null, null, null, null); jenkinsRule.jenkins.clouds.add(spotinstCloud); AzureScaleUpResultNewVm newSpot = new AzureScaleUpResultNewVm(); newSpot.setVmName("vm-2"); @@ -464,7 +467,8 @@ public void testAzureV3Provision_whenUnrecognizedVmSize_thenDefaultTo1Executor() spotinstCloud.provision(null, 4); Node node = Jenkins.get().getNode("vm-2"); - assertNotNull(node); + assertNotEquals(node, null); + assert node != null; assertEquals(1, node.getNumExecutors()); } @@ -473,8 +477,8 @@ public void testAzureV3Provision_whenNewInstancesAreLaunched_thenTheirSizeIsAcco String groupId = "sig-1"; String accountId = "act-111"; BaseSpotinstCloud spotinstCloud = - new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, true, "", null, null, accountId, null, - null, null, null, null); + new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, new SpotReTriggerBuilds(true, false), "", + null, null, accountId, null, null, null, null, null); jenkinsRule.jenkins.clouds.add(spotinstCloud); AzureVmSizeEnum vmSizeBasicA1 = AzureVmSizeEnum.BASIC_A1; AzureVmSizeEnum vmSizeBasicA2 = AzureVmSizeEnum.BASIC_A2; @@ -510,8 +514,8 @@ public void testAzureV3Provision_whenNewInstancesAreLaunched_thenTheirSizeIsAcco String groupId = "sig-1"; String accountId = "act-111"; BaseSpotinstCloud spotinstCloud = - new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, true, "", null, null, accountId, null, - null, null, null, null); + new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, new SpotReTriggerBuilds(true, false), "", + null, null, accountId, null, null, null, null, null); jenkinsRule.jenkins.clouds.add(spotinstCloud); Map pendingInstances = new HashMap<>(); pendingInstances.put("vm-1", buildPendingInstance("vm-1", 2)); @@ -549,8 +553,8 @@ public void testAzureV3Provision_whenNewInstancesAreLaunched_thenTheirSizeIsAcco public void testAzureV3Cloud_whenNoConnectionMethodIsProvided_thenDefaultIsJNLP() { String groupId = "sig-1"; BaseSpotinstCloud spotCloud = - new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, true, "", null, null, null, null, null, - null, null, null); + new AzureSpotCloud(groupId, "", "20", "/tmp", null, "", false, new SpotReTriggerBuilds(true, false), "", + null, null, null, null, null, null, null, null); jenkinsRule.jenkins.clouds.add(spotCloud); assertEquals(spotCloud.getConnectionMethod(), ConnectionMethodEnum.JNLP); @@ -573,7 +577,7 @@ public void testAzureV3Cloud_whenNoConnectionMethodIsProvided_thenDefaultIsJNLP( for (Node node : allNodes) { SpotinstComputer computer = (SpotinstComputer) node.toComputer(); - assertNotNull(computer); + assert computer != null; assertEquals(computer.getLauncher().getClass(), SpotinstComputerLauncher.class); } @@ -633,7 +637,7 @@ public void testGlobalExecutorOverride_whenIsEnabledAndInstanceTypeIsMatchedInEn cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), globalOverride.getExecutors().intValue()); } @@ -674,7 +678,7 @@ public void testGlobalExecutorOverride_whenIsEnabledAndInstanceTypeIsNotMatchedI cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), globalOverride.getExecutors().intValue()); } @@ -721,7 +725,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -764,7 +768,7 @@ public void testGlobalExecutorOverride_whenIsDisabledAndInstanceTypeIsNotMatched cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), 1); } @@ -805,7 +809,7 @@ public void testGlobalExecutorOverride_whenIsDisabledAndInstanceTypeIsMatchedInE cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), AwsInstanceTypeEnum.C4Large.getExecutors().intValue()); } @@ -853,7 +857,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -895,7 +899,7 @@ public void testGlobalExecutorOverride_whenIsInvalidNegativeAndInstanceTypeIsMat cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), AwsInstanceTypeEnum.C4Large.getExecutors().intValue()); } @@ -936,7 +940,7 @@ public void testGlobalExecutorOverride_whenIsInvalidNegativeAndInstanceTypeIsNot cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), 1); } @@ -984,7 +988,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -1025,7 +1029,7 @@ public void testGlobalExecutorOverride_whenIsInvalidNullAndInstanceTypeIsMatched cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), AwsInstanceTypeEnum.C4Large.getExecutors().intValue()); } @@ -1066,7 +1070,7 @@ public void testGlobalExecutorOverride_whenIsInvalidNullAndInstanceTypeIsNotMatc cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), 1); } @@ -1114,7 +1118,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -1156,7 +1160,7 @@ public void testGlobalExecutorOverride_whenIsNullAndInstanceTypeIsMatchedInEnum_ cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), AwsInstanceTypeEnum.C4Large.getExecutors().intValue()); } @@ -1196,7 +1200,7 @@ public void testGlobalExecutorOverride_whenIsNullAndInstanceTypeIsNotMatchedInEn cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), 1); } @@ -1244,7 +1248,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); - assertNotNull(agent); + assert agent != null; assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -1276,8 +1280,8 @@ public void testSpotinstSlaveTermination_ifAgentInPendingInstances_thenAgentIsRe Mockito.when( RepoManager.getInstance().getAwsGroupRepo().getGroupInstances(Mockito.anyString(), Mockito.anyString())) .thenReturn(new ApiResponse<>(result)); - Mockito.when( - RepoManager.getInstance().getAwsGroupRepo().getStatefulInstances(Mockito.anyString(), Mockito.anyString())) + Mockito.when(RepoManager.getInstance().getAwsGroupRepo() + .getStatefulInstances(Mockito.anyString(), Mockito.anyString())) .thenReturn(new ApiResponse<>(new LinkedList<>())); List spots = Collections.singletonList(newSpot); @@ -1298,7 +1302,7 @@ public void testSpotinstSlaveTermination_ifAgentInPendingInstances_thenAgentIsRe SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); assertEquals(cloud.isInstancePending(newSpot.getInstanceId()), true); - assertNotNull(agent); + assert agent != null; agent.terminate(); assertEquals(cloud.isInstancePending(newSpot.getInstanceId()), false); @@ -1314,9 +1318,9 @@ public void testSpotinstSlaveTermination_ifAgentInPendingInstances_thenAgentIsRe public void testAzureSpotinstCloud_DescriptorReturnsAzureSpotinstCloudString() { String groupId = "sig-1"; String accountId = "act-111"; - BaseSpotinstCloud spotinstCloud = - new AzureSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, false, "", null, null, accountId, - null, null, null, null, null); + BaseSpotinstCloud spotinstCloud = new AzureSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, + new SpotReTriggerBuilds(false, false), "", null, null, + accountId, null, null, null, null, null); assertTrue(spotinstCloud.getDescriptor().toString().contains("AzureSpotinstCloud")); } @@ -1324,7 +1328,8 @@ public void testAzureSpotinstCloud_DescriptorReturnsAzureSpotinstCloudString() { //region Helper Methods public SSHConnector getSSHConnector() { - return new SSHConnector(22, "testCredentials"); + SSHConnector retVal = new SSHConnector(22, "testCredentials"); + return retVal; } //endregion } \ No newline at end of file