diff --git a/JenkinsWiki.adoc b/JenkinsWiki.adoc index 6fab2fa5..a01aedd9 100644 --- a/JenkinsWiki.adoc +++ b/JenkinsWiki.adoc @@ -40,6 +40,12 @@ termination" [SpotinstPlugin-Versionhistory] == Version history +[SpotinstPlugin-Version2.2.14(Sep21,2023)] +=== Version 2.2.14 (Sep 21, 2023) + +* AWS stateful groups: deallocate stateful instances resources when removed from Jenkins +* Fixed : monitor alert won't show up for Spotinst Clouds Communication Monitor when all groups are properly connected + [SpotinstPlugin-Version2.2.13(Apr19,2023)] === Version 2.2.13 (Apr 19, 2023) diff --git a/src/main/java/hudson/plugins/spotinst/api/SpotinstApi.java b/src/main/java/hudson/plugins/spotinst/api/SpotinstApi.java index 0517e02e..c6e36829 100644 --- a/src/main/java/hudson/plugins/spotinst/api/SpotinstApi.java +++ b/src/main/java/hudson/plugins/spotinst/api/SpotinstApi.java @@ -5,6 +5,10 @@ import hudson.plugins.spotinst.api.infra.*; 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.AwsStatefulInstance; +import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulInstancesResponse; import hudson.plugins.spotinst.model.azure.*; import hudson.plugins.spotinst.model.gcp.*; import hudson.plugins.spotinst.model.redis.DeleteGroupControllerResponse; @@ -72,6 +76,23 @@ else if (response.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { } //region AWS + public static AwsGroup getAwsGroup(String groupId, String accountId) throws ApiException { + AwsGroup retVal = null; + Map headers = buildHeaders(); + Map queryParams = buildQueryParams(accountId); + + RestResponse response = + RestClient.sendGet(SPOTINST_API_HOST + "/aws/ec2/group/" + groupId, headers, queryParams); + + AwsGroupResponse instancesResponse = getCastedResponse(response, AwsGroupResponse.class); + + if (instancesResponse.getResponse().getItems().size() > 0) { + retVal = instancesResponse.getResponse().getItems().get(0); + } + + return retVal; + } + public static List getAwsGroupInstances(String groupId, String accountId) throws ApiException { List retVal = new LinkedList<>(); Map headers = buildHeaders(); @@ -89,6 +110,27 @@ public static List getAwsGroupInstances(String groupId, String return retVal; } + + public static List getAwsStatefulInstances(String groupId, + String accountId) throws ApiException { + List retVal = new LinkedList<>(); + Map headers = buildHeaders(); + Map queryParams = buildQueryParams(accountId); + + RestResponse response = + RestClient.sendGet(SPOTINST_API_HOST + "/aws/ec2/group/" + groupId + "/statefulInstance", headers, + queryParams); + + AwsStatefulInstancesResponse instancesResponse = + getCastedResponse(response, AwsStatefulInstancesResponse.class); + + if (instancesResponse.getResponse().getItems().size() > 0) { + retVal = instancesResponse.getResponse().getItems(); + } + + return retVal; + } + public static AwsScaleUpResult awsScaleUp(String groupId, int adjustment, String accountId) throws ApiException { AwsScaleUpResult retVal = null; Map headers = buildHeaders(); @@ -128,6 +170,32 @@ public static Boolean awsDetachInstance(String instanceId, String accountId) thr return retVal; } + + public static Boolean awsDeallocateInstance(String groupId, String statefulInstanceId, + String accountId) throws ApiException { + Map headers = buildHeaders(); + Map queryParams = buildQueryParams(accountId); + + AwsDeallocateStatefulInstanceRequest request = new AwsDeallocateStatefulInstanceRequest(); + AwsStatefulDeallocationConfig statefulDeallocation = new AwsStatefulDeallocationConfig(); + statefulDeallocation.setShouldDeleteImages(true); + statefulDeallocation.setShouldDeleteSnapshots(true); + statefulDeallocation.setShouldDeleteVolumes(true); + statefulDeallocation.setShouldDeleteNetworkInterfaces(true); + request.setStatefulDeallocation(statefulDeallocation); + + String body = JsonMapper.toJson(request); + + RestResponse response = RestClient.sendPut( + SPOTINST_API_HOST + "/aws/ec2/group/" + groupId + "/statefulInstance/" + statefulInstanceId + + "/deallocate", body, headers, queryParams); + + getCastedResponse(response, ApiEmptyResponse.class); + Boolean retVal = true; + + return retVal; + } + public static List getAllAwsInstanceTypes(String accountId) throws ApiException { List retVal; Map headers = buildHeaders(); diff --git a/src/main/java/hudson/plugins/spotinst/cloud/AwsSpotinstCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/AwsSpotinstCloud.java index 6ece8bde..46ceebf2 100644 --- a/src/main/java/hudson/plugins/spotinst/cloud/AwsSpotinstCloud.java +++ b/src/main/java/hudson/plugins/spotinst/cloud/AwsSpotinstCloud.java @@ -6,7 +6,10 @@ 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.model.aws.*; +import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulInstance; +import hudson.plugins.spotinst.model.common.BlResponse; import hudson.plugins.spotinst.repos.IAwsGroupRepo; import hudson.plugins.spotinst.repos.RepoManager; import hudson.plugins.spotinst.slave.*; @@ -14,6 +17,8 @@ import hudson.slaves.EnvironmentVariablesNodeProperty; import hudson.tools.ToolLocationNodeProperty; import jenkins.model.Jenkins; +import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,6 +26,7 @@ import javax.annotation.Nonnull; import java.io.IOException; import java.util.*; +import java.util.stream.Collectors; /** * Created by ohadmuchnik on 20/03/2017. @@ -34,6 +40,7 @@ public class AwsSpotinstCloud extends BaseSpotinstCloud { protected Map executorsByInstanceType; private List executorsForTypes; private List invalidInstanceTypes; + private Map ssiByInstanceId; //endregion //region Constructor @@ -100,7 +107,50 @@ List scaleUp(ProvisionRequest request) { } @Override - public Boolean detachInstance(String instanceId) { + protected String getSsiId(String instanceId) { + String retVal = null; + AwsStatefulInstance statefulInstance = getStatefulInstance(instanceId); + + if (statefulInstance != null) { + retVal = statefulInstance.getId(); + } + + return retVal; + } + + private AwsStatefulInstance getStatefulInstance(String instanceId) { + AwsStatefulInstance retVal = null; + boolean isInstanceStateful = ssiByInstanceId != null && ssiByInstanceId.containsKey(instanceId); + + if (isInstanceStateful) { + retVal = ssiByInstanceId.get(instanceId); + } + + return retVal; + } + + @Override + public Boolean deallocateInstance(String statefulInstanceId) { + boolean retVal = false; + + IAwsGroupRepo awsGroupRepo = RepoManager.getInstance().getAwsGroupRepo(); + ApiResponse detachInstanceResponse = + awsGroupRepo.deallocateInstance(groupId, statefulInstanceId, accountId); + + if (detachInstanceResponse.isRequestSucceed()) { + LOGGER.info(String.format("Stateful Instance %s deallocated", statefulInstanceId)); + retVal = true; + } + else { + LOGGER.error(String.format("Failed to deallocate instance %s. Errors: %s", statefulInstanceId, + detachInstanceResponse.getErrors())); + } + + return retVal; + } + + @Override + protected Boolean detachInstance(String instanceId) { Boolean retVal = false; IAwsGroupRepo awsGroupRepo = RepoManager.getInstance().getAwsGroupRepo(); @@ -119,7 +169,7 @@ public Boolean detachInstance(String instanceId) { } @Override - protected void internalSyncGroupInstances() { + protected void syncGroupInstances() { IAwsGroupRepo awsGroupRepo = RepoManager.getInstance().getAwsGroupRepo(); ApiResponse> instancesResponse = awsGroupRepo.getGroupInstances(groupId, this.accountId); @@ -136,6 +186,10 @@ protected void internalSyncGroupInstances() { this.slaveInstancesDetailsByInstanceId = new HashMap<>(slaveInstancesDetailsByInstanceId); + if (isStatefulGroup()) { + syncGroupStatefulInstances(); + } + addNewSlaveInstances(instances); removeOldSlaveInstances(instances); } @@ -145,7 +199,6 @@ protected void internalSyncGroupInstances() { } } - @Override public Map getInstanceIpsById() { Map retVal = new HashMap<>(); @@ -214,6 +267,55 @@ protected int getOverriddenNumberOfExecutors(String instanceType) { return retVal; } + @Override + protected BlResponse checkIsStatefulGroup() { + BlResponse retVal; + IAwsGroupRepo awsGroupRepo = RepoManager.getInstance().getAwsGroupRepo(); + ApiResponse groupResponse = awsGroupRepo.getGroup(groupId, this.accountId); + + if (groupResponse.isRequestSucceed()) { + Boolean result = false; + AwsGroup awsGroup = groupResponse.getValue(); + AwsGroupStrategy groupStrategy = awsGroup.getStrategy(); + + if (groupStrategy != null) { + AwsGroupPersistence groupPersistence = groupStrategy.getPersistence(); + + if (groupPersistence != null) { + result = BooleanUtils.isTrue(groupPersistence.getShouldPersistPrivateIp()) || + BooleanUtils.isTrue(groupPersistence.getShouldPersistBlockDevices()) || + BooleanUtils.isTrue(groupPersistence.getShouldPersistRootDevice()) || + StringUtils.isNotEmpty(groupPersistence.getBlockDevicesMode()); + } + } + + retVal = new BlResponse<>(result); + } + else { + LOGGER.error(String.format("Failed to get group %s. Errors: %s", groupId, groupResponse.getErrors())); + retVal = new BlResponse<>(false); + } + + return retVal; + } + + private void syncGroupStatefulInstances() { + IAwsGroupRepo awsGroupRepo = RepoManager.getInstance().getAwsGroupRepo(); + ApiResponse> statefulInstancesResponse = + awsGroupRepo.getStatefulInstances(groupId, this.accountId); + + if (statefulInstancesResponse.isRequestSucceed()) { + List statefulInstances = statefulInstancesResponse.getValue(); + this.ssiByInstanceId = statefulInstances.stream().collect( + Collectors.toMap(AwsStatefulInstance::getInstanceId, statefulInstance -> statefulInstance)); + } + else { + LOGGER.error(String.format("Failed to get group %s stateful instances. Errors: %s", groupId, + statefulInstancesResponse.getErrors())); + } + + } + private List handleNewAwsSpots(AwsScaleUpResult scaleUpResult, String label) { List retVal = new LinkedList<>(); @@ -253,9 +355,9 @@ private SpotinstSlave handleNewAwsInstance(String instanceId, String instanceTyp private void addNewSlaveInstances(List elastigroupInstances) { if (elastigroupInstances.size() > 0) { for (AwsGroupInstance instance : elastigroupInstances) { - Boolean isSlaveExist = isSlaveExistForInstance(instance); + boolean shouldAddSlaveInstance = shouldAddNewSlaveInstance(instance); - if (isSlaveExist == false) { + if (shouldAddSlaveInstance) { LOGGER.info(String.format("Instance: %s of group: %s doesn't have slave , adding new one", JsonMapper.toJson(instance), groupId)); addSpotinstSlave(instance); @@ -267,6 +369,29 @@ private void addNewSlaveInstances(List elastigroupInstances) { } } + private Boolean shouldAddNewSlaveInstance(AwsGroupInstance instance) { + boolean retVal; + boolean isSlaveExist = isSlaveExistForInstance(instance); + + if (isSlaveExist) { + retVal = false; + } + else { + if (isStatefulGroup()) { + AwsStatefulInstance statefulInstance = getStatefulInstance(instance.getInstanceId()); + boolean isStatefulInstanceReadyForUse = statefulInstance != null && + Objects.equals(statefulInstance.getState(), + StatefulInstanceStateEnum.ACTIVE); + retVal = isStatefulInstanceReadyForUse; + } + else { + retVal = true; + } + } + + return retVal; + } + private void removeOldSlaveInstances(List elastigroupInstances) { List allGroupsSlaves = getAllSpotinstSlaves(); @@ -326,7 +451,7 @@ private List getGroupInstanceAndSpotIds(List elastigro } private Boolean isSlaveExistForInstance(AwsGroupInstance instance) { - Boolean retVal = false; + boolean retVal = false; Node node; String instanceId = instance.getInstanceId(); diff --git a/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotCloud.java index 4e03cba2..bff19404 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.BlResponse; import hudson.plugins.spotinst.repos.IAzureVmGroupRepo; import hudson.plugins.spotinst.repos.RepoManager; import hudson.plugins.spotinst.slave.SlaveInstanceDetails; @@ -86,7 +87,22 @@ List scaleUp(ProvisionRequest request) { } @Override - public Boolean detachInstance(String instanceId) { + protected BlResponse checkIsStatefulGroup() { + return new BlResponse<>(false); + } + + @Override + protected String getSsiId(String instanceId) { + return null;//TODO: implement + } + + @Override + protected Boolean deallocateInstance(String statefulInstanceId) { + return false;//TODO: implement + } + + @Override + protected Boolean detachInstance(String instanceId) { Boolean retVal = false; IAzureVmGroupRepo azureVmGroupRepo = RepoManager.getInstance().getAzureVmGroupRepo(); ApiResponse detachVmResponse = azureVmGroupRepo.detachVM(groupId, instanceId, this.accountId); @@ -109,7 +125,7 @@ public String getCloudUrl() { } @Override - protected void internalSyncGroupInstances() { + protected void syncGroupInstances() { IAzureVmGroupRepo azureVmGroupRepo = RepoManager.getInstance().getAzureVmGroupRepo(); ApiResponse> instancesResponse = azureVmGroupRepo.getGroupVms(groupId, this.accountId); diff --git a/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotinstCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/AzureSpotinstCloud.java index e15de293..600ecf7a 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.BlResponse; import hudson.plugins.spotinst.repos.IAzureGroupRepo; import hudson.plugins.spotinst.repos.RepoManager; import hudson.plugins.spotinst.slave.SlaveInstanceDetails; @@ -76,7 +77,22 @@ List scaleUp(ProvisionRequest request) { } @Override - public Boolean detachInstance(String instanceId) { + protected BlResponse checkIsStatefulGroup() { + return new BlResponse<>(false); + } + + @Override + protected String getSsiId(String instanceId) { + return null;//TODO: implement + } + + @Override + protected Boolean deallocateInstance(String statefulInstanceId) { + return false;//TODO: implement + } + + @Override + protected Boolean detachInstance(String instanceId) { Boolean retVal = false; IAzureGroupRepo azureGroupRepo = RepoManager.getInstance().getAzureGroupRepo(); ApiResponse detachInstanceResponse = azureGroupRepo.detachInstance(groupId, instanceId, accountId); @@ -94,12 +110,12 @@ public Boolean detachInstance(String instanceId) { } @Override - public void syncGroupInstances() { + public void syncGroup() { } @Override - protected void internalSyncGroupInstances() { + protected void syncGroupInstances() { } diff --git a/src/main/java/hudson/plugins/spotinst/cloud/BaseSpotinstCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/BaseSpotinstCloud.java index 48557f2e..f665b2a4 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.BlResponse; import hudson.plugins.spotinst.slave.*; import hudson.plugins.sshslaves.SSHConnector; import hudson.slaves.*; @@ -58,6 +59,7 @@ public abstract class BaseSpotinstCloud extends Cloud { private SpotGlobalExecutorOverride globalExecutorOverride; protected Integer pendingThreshold; private GroupLockingManager groupLockingManager; + private boolean isStatefulGroup; //endregion //region Constructor @@ -128,6 +130,16 @@ public BaseSpotinstCloud(String groupId, String labelString, String idleTerminat groupLockingManager = new GroupLockingManager(groupId, accountId); groupLockingManager.syncGroupController(); + + BlResponse checkIsStatefulGroupResponse = checkIsStatefulGroup(); + + if (checkIsStatefulGroupResponse.isSucceed()) { + this.isStatefulGroup = checkIsStatefulGroupResponse.getResult(); + } + else { + LOGGER.warn("failed to get the group's details, currently referring to it as stateless"); + this.isStatefulGroup = false; + } } //endregion @@ -853,22 +865,61 @@ public void setIsSingleTaskNodesEnabled(Boolean isSingleTaskNodesEnabled) { //region Abstract Methods abstract List scaleUp(ProvisionRequest request); - public abstract Boolean detachInstance(String instanceId); + public Boolean removeInstance(String instanceId) { + boolean retVal; + + if (isStatefulGroup()) { + String statefulInstanceId = getSsiId(instanceId); + + if (statefulInstanceId != null) { + retVal = deallocateInstance(statefulInstanceId); + } + else { + LOGGER.warn( + "Instance '{}' is not in the group's stateful instances, and currently cannot be deallocated", + instanceId); + retVal = false; + } + } + else { + retVal = detachInstance(instanceId); + } + + return retVal; + } + + protected abstract BlResponse checkIsStatefulGroup(); + + public Boolean isStatefulGroup() { + return isStatefulGroup; + } + + protected abstract String getSsiId(String instanceId); + + protected abstract Boolean detachInstance(String instanceId); + + protected abstract Boolean deallocateInstance(String statefulInstanceId); public abstract String getCloudUrl(); - public void syncGroupInstances() { + public void syncGroup() { boolean isCloudReadyForGroupCommunication = isCloudReadyForGroupCommunication(); if (isCloudReadyForGroupCommunication) { - internalSyncGroupInstances(); + BlResponse isStatefulResponse = checkIsStatefulGroup(); + + if (isStatefulResponse.isSucceed()) { + this.isStatefulGroup = isStatefulResponse.getResult(); + } + + syncGroupInstances(); } else { LOGGER.error(SKIPPED_METHOD_GROUP_IS_NIT_READY_ERROR_LOGGER_FORMAT, "syncGroupInstances", groupId); } } - protected abstract void internalSyncGroupInstances(); + protected abstract void syncGroupInstances(); public abstract Map getInstanceIpsById(); diff --git a/src/main/java/hudson/plugins/spotinst/cloud/GcpSpotinstCloud.java b/src/main/java/hudson/plugins/spotinst/cloud/GcpSpotinstCloud.java index d7835e71..53184cef 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.BlResponse; import hudson.plugins.spotinst.model.gcp.GcpGroupInstance; import hudson.plugins.spotinst.model.gcp.GcpMachineType; import hudson.plugins.spotinst.model.gcp.GcpResultNewInstance; @@ -99,7 +100,22 @@ List scaleUp(ProvisionRequest request) { } @Override - public Boolean detachInstance(String instanceId) { + protected BlResponse checkIsStatefulGroup() { + return new BlResponse<>(false); + } + + @Override + protected String getSsiId(String instanceId) { + return null;//TODO: implement + } + + @Override + protected Boolean deallocateInstance(String statefulInstanceId) { + return false;//TODO: implement + } + + @Override + protected Boolean detachInstance(String instanceId) { Boolean retVal = false; IGcpGroupRepo gcpGroupRepo = RepoManager.getInstance().getGcpGroupRepo(); ApiResponse detachInstanceResponse = gcpGroupRepo.detachInstance(groupId, instanceId, this.accountId); @@ -117,7 +133,7 @@ public Boolean detachInstance(String instanceId) { } @Override - protected void internalSyncGroupInstances() { + protected void syncGroupInstances() { IGcpGroupRepo gcpGroupRepo = RepoManager.getInstance().getGcpGroupRepo(); ApiResponse> instancesResponse = gcpGroupRepo.getGroupInstances(groupId, this.accountId); diff --git a/src/main/java/hudson/plugins/spotinst/common/GroupLockingManager.java b/src/main/java/hudson/plugins/spotinst/common/GroupLockingManager.java index b2ad61a0..9ed0856e 100644 --- a/src/main/java/hudson/plugins/spotinst/common/GroupLockingManager.java +++ b/src/main/java/hudson/plugins/spotinst/common/GroupLockingManager.java @@ -261,8 +261,9 @@ private void handleInitializingFailureTimeout(String errorDescription) { TimeUtils.isTimePassedInSeconds(initializingStateStartTimeStamp, INITIALIZING_PERIOD_IN_SECONDS); if (isTimeout) { - LOGGER.error("Initialization time has expired, error description: {}", errorDescription); - setFailedState(errorDescription); + String timeoutErrorDescription = + String.format("Initialization time has expired, error description: %s", errorDescription); + setFailedState(timeoutErrorDescription); } else { LOGGER.warn( @@ -282,6 +283,7 @@ private void setReadyState() { } public void setFailedState(String description) { + LOGGER.error("Cloud failed to communicate with the group. {}", description); setCloudCommunicationState(SpotinstCloudCommunicationState.FAILED); setErrorDescription(description); } diff --git a/src/main/java/hudson/plugins/spotinst/common/stateful/StatefulInstanceStateEnum.java b/src/main/java/hudson/plugins/spotinst/common/stateful/StatefulInstanceStateEnum.java new file mode 100644 index 00000000..0479bde3 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/common/stateful/StatefulInstanceStateEnum.java @@ -0,0 +1,61 @@ +package hudson.plugins.spotinst.common.stateful; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by sitay on 30/08/23. + */ +public enum StatefulInstanceStateEnum { + //region Enums + ACTIVE("ACTIVE"), + PAUSE("PAUSE"), + PAUSING("PAUSING"), + PAUSED("PAUSED"), + RESUME("RESUME"), + RESUMING("RESUMING"), + RECYCLE("RECYCLE"), + RECYCLING("RECYCLING"), + DEALLOCATE("DEALLOCATE"), + DEALLOCATING("DEALLOCATING"), + DEALLOCATED("DEALLOCATED"), + ERROR("ERROR"); + //endregion + + //region Members + private static final Logger LOGGER = LoggerFactory.getLogger(StatefulInstanceStateEnum.class); + private final String name; + //endregion + + //region Constructors + StatefulInstanceStateEnum(String name) { + this.name = name; + } + //endregion + + //region Getters & Setters + public String getName() { + return name; + } + //endregion + + //region Methods + public static StatefulInstanceStateEnum fromName(String name) { + StatefulInstanceStateEnum retVal = null; + + for (StatefulInstanceStateEnum deploymentInstanceStatus : StatefulInstanceStateEnum.values()) { + if (name.equalsIgnoreCase(deploymentInstanceStatus.name)) { + retVal = deploymentInstanceStatus; + break; + } + } + + if (retVal == null) { + LOGGER.error( + "Tried to create stateful instance state Enum for: " + name + ", but we don't support such type "); + } + + return retVal; + } + //endregion +} diff --git a/src/main/java/hudson/plugins/spotinst/jobs/SpotinstSyncInstances.java b/src/main/java/hudson/plugins/spotinst/jobs/SpotinstSyncGroups.java similarity index 89% rename from src/main/java/hudson/plugins/spotinst/jobs/SpotinstSyncInstances.java rename to src/main/java/hudson/plugins/spotinst/jobs/SpotinstSyncGroups.java index c20b9e21..658033b0 100644 --- a/src/main/java/hudson/plugins/spotinst/jobs/SpotinstSyncInstances.java +++ b/src/main/java/hudson/plugins/spotinst/jobs/SpotinstSyncGroups.java @@ -17,16 +17,16 @@ * Created by ohadmuchnik on 25/05/2016. */ @Extension -public class SpotinstSyncInstances extends AsyncPeriodicWork { +public class SpotinstSyncGroups extends AsyncPeriodicWork { //region Members - private static final Logger LOGGER = LoggerFactory.getLogger(SpotinstSyncInstances.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SpotinstSyncGroups.class); public static final Integer JOB_INTERVAL_IN_MINUTES = 1; final long recurrencePeriod; //endregion //region Constructor - public SpotinstSyncInstances() { + public SpotinstSyncGroups() { super("Sync Instances"); recurrencePeriod = TimeUnit.MINUTES.toMillis(JOB_INTERVAL_IN_MINUTES); } @@ -44,7 +44,7 @@ protected void execute(TaskListener taskListener) { BaseSpotinstCloud spotinstCloud = (BaseSpotinstCloud) cloud; try { - spotinstCloud.syncGroupInstances(); + spotinstCloud.syncGroup(); } catch (Exception e) { LOGGER.error(String.format("Failed to sync group: %s instances", spotinstCloud.getGroupId()), diff --git a/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroup.java b/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroup.java new file mode 100644 index 00000000..10ec0767 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroup.java @@ -0,0 +1,54 @@ +package hudson.plugins.spotinst.model.aws; + +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 AwsGroup { + //region members + private String id; + private String name; + private String description; + private AwsGroupStrategy strategy; + //endregion + + //region getters & setters + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public AwsGroupStrategy getStrategy() { + return strategy; + } + + public void setStrategy(AwsGroupStrategy strategy) { + this.strategy = strategy; + } + + //endregion +} diff --git a/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroupPersistence.java b/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroupPersistence.java new file mode 100644 index 00000000..1ff6db75 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroupPersistence.java @@ -0,0 +1,53 @@ +package hudson.plugins.spotinst.model.aws; + +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 AwsGroupPersistence { + //region members + private Boolean shouldPersistPrivateIp; + private Boolean shouldPersistBlockDevices; + private Boolean shouldPersistRootDevice; + private String blockDevicesMode; + //endregion + + //region getters & setters + + public Boolean getShouldPersistPrivateIp() { + return shouldPersistPrivateIp; + } + + public void setShouldPersistPrivateIp(Boolean shouldPersistPrivateIp) { + this.shouldPersistPrivateIp = shouldPersistPrivateIp; + } + + public Boolean getShouldPersistBlockDevices() { + return shouldPersistBlockDevices; + } + + public void setShouldPersistBlockDevices(Boolean shouldPersistBlockDevices) { + this.shouldPersistBlockDevices = shouldPersistBlockDevices; + } + + public Boolean getShouldPersistRootDevice() { + return shouldPersistRootDevice; + } + + public void setShouldPersistRootDevice(Boolean shouldPersistRootDevice) { + this.shouldPersistRootDevice = shouldPersistRootDevice; + } + + public String getBlockDevicesMode() { + return blockDevicesMode; + } + + public void setBlockDevicesMode(String blockDevicesMode) { + this.blockDevicesMode = blockDevicesMode; + } + //endregion +} diff --git a/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroupResponse.java b/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroupResponse.java new file mode 100644 index 00000000..9f6faa98 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroupResponse.java @@ -0,0 +1,6 @@ +package hudson.plugins.spotinst.model.aws; + +import hudson.plugins.spotinst.api.infra.BaseItemsResponse; + +public class AwsGroupResponse extends BaseItemsResponse { +} diff --git a/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroupStrategy.java b/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroupStrategy.java new file mode 100644 index 00000000..41896f62 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/model/aws/AwsGroupStrategy.java @@ -0,0 +1,25 @@ +package hudson.plugins.spotinst.model.aws; + +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 AwsGroupStrategy { + //region members + private AwsGroupPersistence persistence; + //endregion + + //region getters & setters + public AwsGroupPersistence getPersistence() { + return persistence; + } + + public void setPersistence(AwsGroupPersistence persistence) { + this.persistence = persistence; + } + //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 new file mode 100644 index 00000000..fad0a7c3 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsDeallocateStatefulInstanceRequest.java @@ -0,0 +1,23 @@ +package hudson.plugins.spotinst.model.aws.stateful; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Created by sitay on 30/08/23. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AwsDeallocateStatefulInstanceRequest { + //region members + private AwsStatefulDeallocationConfig statefulDeallocation; + //endregion + + //region getters & setters + public AwsStatefulDeallocationConfig getStatefulDeallocation() { + return statefulDeallocation; + } + + public void setStatefulDeallocation(AwsStatefulDeallocationConfig statefulDeallocation) { + this.statefulDeallocation = statefulDeallocation; + } + //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 new file mode 100644 index 00000000..7cb46075 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulDeallocationConfig.java @@ -0,0 +1,50 @@ +package hudson.plugins.spotinst.model.aws.stateful; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Created by sitay on 30/08/23. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AwsStatefulDeallocationConfig { + //region members + private Boolean shouldDeleteImages; + private Boolean shouldDeleteNetworkInterfaces; + private Boolean shouldDeleteVolumes; + private Boolean shouldDeleteSnapshots; + //endregion + + //region getters & setters + public Boolean getShouldDeleteImages() { + return shouldDeleteImages; + } + + public void setShouldDeleteImages(Boolean shouldDeleteImages) { + this.shouldDeleteImages = shouldDeleteImages; + } + + public Boolean getShouldDeleteNetworkInterfaces() { + return shouldDeleteNetworkInterfaces; + } + + public void setShouldDeleteNetworkInterfaces(Boolean shouldDeleteNetworkInterfaces) { + this.shouldDeleteNetworkInterfaces = shouldDeleteNetworkInterfaces; + } + + public Boolean getShouldDeleteVolumes() { + return shouldDeleteVolumes; + } + + public void setShouldDeleteVolumes(Boolean shouldDeleteVolumes) { + this.shouldDeleteVolumes = shouldDeleteVolumes; + } + + public Boolean getShouldDeleteSnapshots() { + return shouldDeleteSnapshots; + } + + public void setShouldDeleteSnapshots(Boolean shouldDeleteSnapshots) { + this.shouldDeleteSnapshots = shouldDeleteSnapshots; + } + //endregion +} 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 new file mode 100644 index 00000000..e02bbea1 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulInstance.java @@ -0,0 +1,44 @@ +package hudson.plugins.spotinst.model.aws.stateful; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import hudson.plugins.spotinst.common.stateful.StatefulInstanceStateEnum; + +/** + * Created by ItayShklar on 07/08/2023. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AwsStatefulInstance { + //region members + private String id; + private String instanceId; + private StatefulInstanceStateEnum state; + //endregion + + //region getters & setters + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getInstanceId() { + return instanceId; + } + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public StatefulInstanceStateEnum getState() { + return state; + } + + public void setState(StatefulInstanceStateEnum state) { + this.state = state; + } + //endregion +} diff --git a/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulInstancesResponse.java b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulInstancesResponse.java new file mode 100644 index 00000000..325f5e04 --- /dev/null +++ b/src/main/java/hudson/plugins/spotinst/model/aws/stateful/AwsStatefulInstancesResponse.java @@ -0,0 +1,6 @@ +package hudson.plugins.spotinst.model.aws.stateful; + +import hudson.plugins.spotinst.api.infra.BaseItemsResponse; + +public class AwsStatefulInstancesResponse extends BaseItemsResponse { +} diff --git a/src/main/java/hudson/plugins/spotinst/repos/AwsGroupRepo.java b/src/main/java/hudson/plugins/spotinst/repos/AwsGroupRepo.java index 59d23c95..d853d461 100644 --- a/src/main/java/hudson/plugins/spotinst/repos/AwsGroupRepo.java +++ b/src/main/java/hudson/plugins/spotinst/repos/AwsGroupRepo.java @@ -4,9 +4,11 @@ import hudson.plugins.spotinst.api.infra.ApiException; import hudson.plugins.spotinst.api.infra.ApiResponse; import hudson.plugins.spotinst.api.infra.ExceptionHelper; +import hudson.plugins.spotinst.model.aws.AwsGroup; import hudson.plugins.spotinst.model.aws.AwsInstanceType; import hudson.plugins.spotinst.model.aws.AwsGroupInstance; import hudson.plugins.spotinst.model.aws.AwsScaleUpResult; +import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulInstance; import java.util.List; @@ -15,6 +17,23 @@ */ public class AwsGroupRepo implements IAwsGroupRepo { + @Override + public ApiResponse getGroup(String groupId, String accountId) { + ApiResponse retVal; + + try { + AwsGroup instances = SpotinstApi.getAwsGroup(groupId, accountId); + + retVal = new ApiResponse<>(instances); + + } + catch (ApiException e) { + retVal = ExceptionHelper.handleDalException(e); + } + + return retVal; + } + @Override public ApiResponse> getGroupInstances(String groupId, String accountId) { ApiResponse> retVal; @@ -32,6 +51,23 @@ public ApiResponse> getGroupInstances(String groupId, Str return retVal; } + @Override + public ApiResponse> getStatefulInstances(String groupId, String accountId) { + ApiResponse> retVal; + + try { + List instances = SpotinstApi.getAwsStatefulInstances(groupId, accountId); + + retVal = new ApiResponse<>(instances); + + } + catch (ApiException e) { + retVal = ExceptionHelper.handleDalException(e); + } + + return retVal; + } + @Override public ApiResponse detachInstance(String instanceId, String accountId) { ApiResponse retVal; @@ -49,6 +85,23 @@ public ApiResponse detachInstance(String instanceId, String accountId) return retVal; } + @Override + public ApiResponse deallocateInstance(String groupId, String statefulInstanceId, String accountId) { + ApiResponse retVal; + + try { + Boolean isDetached = SpotinstApi.awsDeallocateInstance(groupId, statefulInstanceId, accountId); + + retVal = new ApiResponse<>(isDetached); + + } + catch (ApiException e) { + retVal = ExceptionHelper.handleDalException(e); + } + + return retVal; + } + @Override public ApiResponse scaleUp(String groupId, Integer adjustment, String accountId) { ApiResponse retVal; diff --git a/src/main/java/hudson/plugins/spotinst/repos/IAwsGroupRepo.java b/src/main/java/hudson/plugins/spotinst/repos/IAwsGroupRepo.java index 1f54eb91..e25fdbf2 100644 --- a/src/main/java/hudson/plugins/spotinst/repos/IAwsGroupRepo.java +++ b/src/main/java/hudson/plugins/spotinst/repos/IAwsGroupRepo.java @@ -1,9 +1,11 @@ package hudson.plugins.spotinst.repos; import hudson.plugins.spotinst.api.infra.ApiResponse; +import hudson.plugins.spotinst.model.aws.AwsGroup; import hudson.plugins.spotinst.model.aws.AwsInstanceType; import hudson.plugins.spotinst.model.aws.AwsGroupInstance; import hudson.plugins.spotinst.model.aws.AwsScaleUpResult; +import hudson.plugins.spotinst.model.aws.stateful.AwsStatefulInstance; import java.util.List; @@ -11,10 +13,16 @@ * Created by ohadmuchnik on 05/11/2018. */ public interface IAwsGroupRepo { + ApiResponse getGroup(String groupId, String accountId); + ApiResponse> getGroupInstances(String groupId, String accountId); + ApiResponse> getStatefulInstances(String groupId, String accountId); + ApiResponse detachInstance(String instanceId, String accountId); + ApiResponse deallocateInstance(String groupId, String statefulInstanceId, String accountId); + ApiResponse scaleUp(String groupId, Integer adjustment, String accountId); ApiResponse> getAllInstanceTypes(String accountId); diff --git a/src/main/java/hudson/plugins/spotinst/slave/SpotinstSlave.java b/src/main/java/hudson/plugins/spotinst/slave/SpotinstSlave.java index 62ae7951..6e71c91d 100644 --- a/src/main/java/hudson/plugins/spotinst/slave/SpotinstSlave.java +++ b/src/main/java/hudson/plugins/spotinst/slave/SpotinstSlave.java @@ -177,7 +177,7 @@ public void terminate() { boolean isGroupManagedByThisController = getSpotinstCloud().isCloudReadyForGroupCommunication(); if (isGroupManagedByThisController) { - Boolean isTerminated = getSpotinstCloud().detachInstance(instanceId); + Boolean isTerminated = getSpotinstCloud().removeInstance(instanceId); if (isTerminated) { LOGGER.info(String.format("Instance: %s terminated successfully", getInstanceId())); @@ -204,7 +204,7 @@ public Boolean forceTerminate() { boolean isGroupManagedByThisController = getSpotinstCloud().isCloudReadyForGroupCommunication(); if (isGroupManagedByThisController) { - Boolean isTerminated = getSpotinstCloud().detachInstance(instanceId); + Boolean isTerminated = getSpotinstCloud().removeInstance(instanceId); if (isTerminated) { LOGGER.info(String.format("Instance: %s terminated successfully", getInstanceId())); diff --git a/src/test/java/hudson/plugins/spotinst/SpotinstCloudTest.java b/src/test/java/hudson/plugins/spotinst/SpotinstCloudTest.java index 621b8521..34470051 100644 --- a/src/test/java/hudson/plugins/spotinst/SpotinstCloudTest.java +++ b/src/test/java/hudson/plugins/spotinst/SpotinstCloudTest.java @@ -42,6 +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)); + RepoManager.getInstance().setAwsGroupRepo(awsGroupRepo); RepoManager.getInstance().setGcpGroupRepo(gcpGroupRepo); RepoManager.getInstance().setAzureGroupRepo(azureGroupRepo); @@ -72,12 +74,12 @@ private void setUpLockRepo() { RepoManager.getInstance().setLockRepo(lockRepo); } - private PendingInstance buildPendingInstance(String id, PendingInstance.StatusEnum status, Integer executors) { + private PendingInstance buildPendingInstance(String id, Integer executors) { PendingInstance pendingInstance = new PendingInstance(); pendingInstance.setCreatedAt(new Date()); pendingInstance.setNumOfExecutors(executors); pendingInstance.setId(id); - pendingInstance.setStatus(status); + pendingInstance.setStatus(PendingInstance.StatusEnum.PENDING); return pendingInstance; } @@ -90,7 +92,7 @@ public void testAwsProvision_whenThereArePendingInsatcnesForAllExecutors_thenSho new AwsSpotinstCloud(groupId, "", "20", "/tmp", null, SlaveUsageEnum.NORMAL, "", false, true, "", null, null, null, null, null, null, null, null); Map pendingInstances = new HashMap<>(); - pendingInstances.put("sir-1", buildPendingInstance("sir-1", PendingInstance.StatusEnum.PENDING, 2)); + pendingInstances.put("sir-1", buildPendingInstance("sir-1", 2)); spotinstCloud.setPendingInstances(pendingInstances); spotinstCloud.provision(null, 2); Mockito.verify(RepoManager.getInstance().getAwsGroupRepo(), Mockito.never()) @@ -107,7 +109,7 @@ public void testAwsProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_t jenkinsRule.jenkins.clouds.add(spotinstCloud); Map pendingInstances = new HashMap<>(); - pendingInstances.put("sir-1", buildPendingInstance("sir-1", PendingInstance.StatusEnum.PENDING, 2)); + pendingInstances.put("sir-1", buildPendingInstance("sir-1", 2)); spotinstCloud.setPendingInstances(pendingInstances); AwsScaleUpResult result = new AwsScaleUpResult(); AwsScaleResultNewSpot newSpot = new AwsScaleResultNewSpot(); @@ -115,7 +117,7 @@ public void testAwsProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_t newSpot.setInstanceId("i-dqwadq"); newSpot.setAvailabilityZone("us-east-1a"); newSpot.setInstanceType(AwsInstanceTypeEnum.C4Large.getValue()); - result.setNewSpotRequests(Arrays.asList(newSpot)); + result.setNewSpotRequests(Collections.singletonList(newSpot)); Mockito.when(RepoManager.getInstance().getAwsGroupRepo() .scaleUp(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString())) .thenReturn(new ApiResponse<>(result)); @@ -160,6 +162,7 @@ public void testAwCloud_whenNoConnectionMethodIsProvided_thenDefaultIsJNLP() { for (Node node : allNodes) { SpotinstComputer computer = (SpotinstComputer) node.toComputer(); + assertNotNull(computer); assertEquals(computer.getLauncher().getClass(), SpotinstComputerLauncher.class); } @@ -207,6 +210,7 @@ public void testAwsCloud_whenSshConnectionMethod_andIpIsAvailable_thenCreateSshL spotCloud.monitorInstances(); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(incomingInstance.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getLauncher().getClass(), SpotSSHComputerLauncher.class); } @@ -255,6 +259,7 @@ public void testAwsCloud_whenSshConnectionMethod_andIpIsNotAvailable_thenDoNotCo SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode("i-2"); + assertNotNull(agent); assertNotEquals(agent.getLauncher().getClass(), SpotSSHComputerLauncher.class); // but neither create our JNLP launcher assertNotEquals(agent.getLauncher().getClass(), SpotinstComputerLauncher.class); @@ -268,7 +273,7 @@ public void testAwCloud_whenUsePrivateIpIsNull_thenUsePublicIp() { new AwsSpotinstCloud(groupId, "", "20", "/tmp", null, null, "", true, null, null, null, null, null, null, null, null, null, null); - assertEquals(spotCloud.getShouldUsePrivateIp(), false); + assertFalse(spotCloud.getShouldUsePrivateIp()); } @Test @@ -278,7 +283,7 @@ public void testAwCloud_whenUsePrivateIpIsTrue_thenUsePrivateIp() { new AwsSpotinstCloud(groupId, "", "20", "/tmp", null, null, "", true, null, null, null, null, null, null, null, true, null, null); - assertEquals(spotCloud.getShouldUsePrivateIp(), true); + assertTrue(spotCloud.getShouldUsePrivateIp()); } @Test @@ -288,7 +293,7 @@ public void testAwCloud_whenUsePrivateIpIsFalse_thenUsePublicIp() { new AwsSpotinstCloud(groupId, "", "20", "/tmp", null, null, "", true, null, null, null, null, null, null, null, false, null, null); - assertEquals(spotCloud.getShouldUsePrivateIp(), false); + assertFalse(spotCloud.getShouldUsePrivateIp()); } //endregion @@ -300,7 +305,7 @@ public void testGcpProvision_whenThereArePendingInsatcnesForAllExecutors_thenSho new GcpSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, true, "", null, null, null, null, null, null, null, null); Map pendingInstances = new HashMap<>(); - pendingInstances.put("sin-1", buildPendingInstance("sin-1", PendingInstance.StatusEnum.PENDING, 2)); + pendingInstances.put("sin-1", buildPendingInstance("sin-1", 2)); spotinstCloud.setPendingInstances(pendingInstances); spotinstCloud.provision(null, 2); Mockito.verify(RepoManager.getInstance().getGcpGroupRepo(), Mockito.never()) @@ -316,7 +321,7 @@ public void testGcpProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_t null, null, null, null); jenkinsRule.jenkins.clouds.add(spotinstCloud); Map pendingInstances = new HashMap<>(); - pendingInstances.put("sin-1", buildPendingInstance("sin-1", PendingInstance.StatusEnum.PENDING, 2)); + pendingInstances.put("sin-1", buildPendingInstance("sin-1", 2)); spotinstCloud.setPendingInstances(pendingInstances); GcpScaleUpResult result = new GcpScaleUpResult(); @@ -324,7 +329,7 @@ public void testGcpProvision_whenThereArePendingInsatcnesForPartOfTheExecutors_t newInstance.setInstanceName("sin-2"); newInstance.setZone("us-east-1a"); newInstance.setMachineType(GcpMachineType.F1Micro.getName()); - result.setNewInstances(Arrays.asList(newInstance)); + result.setNewInstances(Collections.singletonList(newInstance)); Mockito.when(RepoManager.getInstance().getGcpGroupRepo() .scaleUp(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString())) .thenReturn(new ApiResponse<>(result)); @@ -349,8 +354,8 @@ public void testAzureProvision_whenThereArePendingInsatcnesForAllExecutors_thenS new AzureSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, false, "", null, null, null, null, null, null, null, null); Map pendingInstances = new HashMap<>(); - pendingInstances.put("q3213", buildPendingInstance(groupId, PendingInstance.StatusEnum.PENDING, 1)); - pendingInstances.put("41234", buildPendingInstance(groupId, PendingInstance.StatusEnum.PENDING, 1)); + pendingInstances.put("q3213", buildPendingInstance(groupId, 1)); + pendingInstances.put("41234", buildPendingInstance(groupId, 1)); spotinstCloud.setPendingInstances(pendingInstances); spotinstCloud.provision(null, 2); Mockito.verify(RepoManager.getInstance().getAzureGroupRepo(), Mockito.never()) @@ -365,13 +370,13 @@ public void testAzureProvision_whenThereArePendingInsatcnesForPartOfTheExecutors new AzureSpotinstCloud(groupId, "", "20", "/tmp", null, "", false, false, "", null, null, accountId, null, null, null, null, null); Map pendingInstances = new HashMap<>(); - pendingInstances.put("asda", buildPendingInstance(groupId, PendingInstance.StatusEnum.PENDING, 1)); - pendingInstances.put("ada", buildPendingInstance(groupId, PendingInstance.StatusEnum.PENDING, 1)); + pendingInstances.put("asda", buildPendingInstance(groupId, 1)); + pendingInstances.put("ada", buildPendingInstance(groupId, 1)); spotinstCloud.setPendingInstances(pendingInstances); Mockito.when(RepoManager.getInstance().getAzureGroupRepo() .scaleUp(Mockito.anyString(), Mockito.anyInt(), Mockito.anyString())) - .thenReturn(new ApiResponse(new Boolean(true))); + .thenReturn(new ApiResponse<>(Boolean.TRUE)); spotinstCloud.provision(null, 4); @@ -394,7 +399,7 @@ public void testAzureV3Provision_whenThereArePendingInstancesForAllExecutors_the null, null, null); Map pendingInstances = new HashMap<>(); - pendingInstances.put("vm-1", buildPendingInstance("vm-1", PendingInstance.StatusEnum.PENDING, 2)); + pendingInstances.put("vm-1", buildPendingInstance("vm-1", 2)); spotinstCloud.setPendingInstances(pendingInstances); spotinstCloud.provision(null, 2); @@ -411,7 +416,7 @@ public void testAzureV3Provision_whenThereArePendingInstancesForPartOfTheExecuto null, null, null, null); jenkinsRule.jenkins.clouds.add(spotinstCloud); Map pendingInstances = new HashMap<>(); - pendingInstances.put("vm-1", buildPendingInstance("vm-1", PendingInstance.StatusEnum.PENDING, 2)); + pendingInstances.put("vm-1", buildPendingInstance("vm-1", 2)); spotinstCloud.setPendingInstances(pendingInstances); AzureScaleUpResultNewVm newSpot = new AzureScaleUpResultNewVm(); @@ -459,7 +464,7 @@ public void testAzureV3Provision_whenUnrecognizedVmSize_thenDefaultTo1Executor() spotinstCloud.provision(null, 4); Node node = Jenkins.get().getNode("vm-2"); - assertNotEquals(node, null); + assertNotNull(node); assertEquals(1, node.getNumExecutors()); } @@ -509,7 +514,7 @@ public void testAzureV3Provision_whenNewInstancesAreLaunched_thenTheirSizeIsAcco null, null, null, null); jenkinsRule.jenkins.clouds.add(spotinstCloud); Map pendingInstances = new HashMap<>(); - pendingInstances.put("vm-1", buildPendingInstance("vm-1", PendingInstance.StatusEnum.PENDING, 2)); + pendingInstances.put("vm-1", buildPendingInstance("vm-1", 2)); spotinstCloud.setPendingInstances(pendingInstances); AzureVmSizeEnum vmSizeBasicA1 = AzureVmSizeEnum.BASIC_A1; @@ -568,6 +573,7 @@ public void testAzureV3Cloud_whenNoConnectionMethodIsProvided_thenDefaultIsJNLP( for (Node node : allNodes) { SpotinstComputer computer = (SpotinstComputer) node.toComputer(); + assertNotNull(computer); assertEquals(computer.getLauncher().getClass(), SpotinstComputerLauncher.class); } @@ -627,6 +633,7 @@ public void testGlobalExecutorOverride_whenIsEnabledAndInstanceTypeIsMatchedInEn cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), globalOverride.getExecutors().intValue()); } @@ -667,6 +674,7 @@ public void testGlobalExecutorOverride_whenIsEnabledAndInstanceTypeIsNotMatchedI cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), globalOverride.getExecutors().intValue()); } @@ -713,6 +721,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -755,6 +764,7 @@ public void testGlobalExecutorOverride_whenIsDisabledAndInstanceTypeIsNotMatched cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), 1); } @@ -795,6 +805,7 @@ public void testGlobalExecutorOverride_whenIsDisabledAndInstanceTypeIsMatchedInE cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), AwsInstanceTypeEnum.C4Large.getExecutors().intValue()); } @@ -842,6 +853,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -883,6 +895,7 @@ public void testGlobalExecutorOverride_whenIsInvalidNegativeAndInstanceTypeIsMat cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), AwsInstanceTypeEnum.C4Large.getExecutors().intValue()); } @@ -923,6 +936,7 @@ public void testGlobalExecutorOverride_whenIsInvalidNegativeAndInstanceTypeIsNot cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), 1); } @@ -970,6 +984,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -1010,6 +1025,7 @@ public void testGlobalExecutorOverride_whenIsInvalidNullAndInstanceTypeIsMatched cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), AwsInstanceTypeEnum.C4Large.getExecutors().intValue()); } @@ -1050,6 +1066,7 @@ public void testGlobalExecutorOverride_whenIsInvalidNullAndInstanceTypeIsNotMatc cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), 1); } @@ -1097,6 +1114,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -1138,6 +1156,7 @@ public void testGlobalExecutorOverride_whenIsNullAndInstanceTypeIsMatchedInEnum_ cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), AwsInstanceTypeEnum.C4Large.getExecutors().intValue()); } @@ -1177,6 +1196,7 @@ public void testGlobalExecutorOverride_whenIsNullAndInstanceTypeIsNotMatchedInEn cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), 1); } @@ -1224,6 +1244,7 @@ null, accountId, ConnectionMethodEnum.SSH, getSSHConnector(), false, cloud.provision(null, 1); SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); + assertNotNull(agent); assertEquals(agent.getNumExecutors(), expectedExecutors); } @@ -1255,7 +1276,9 @@ 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())) + .thenReturn(new ApiResponse<>(new LinkedList<>())); List spots = Collections.singletonList(newSpot); AwsScaleUpResult scaleUpResult = new AwsScaleUpResult(); @@ -1275,6 +1298,7 @@ public void testSpotinstSlaveTermination_ifAgentInPendingInstances_thenAgentIsRe SpotinstSlave agent = (SpotinstSlave) Jenkins.get().getNode(newSpot.getInstanceId()); assertEquals(cloud.isInstancePending(newSpot.getInstanceId()), true); + assertNotNull(agent); agent.terminate(); assertEquals(cloud.isInstancePending(newSpot.getInstanceId()), false); @@ -1300,8 +1324,7 @@ public void testAzureSpotinstCloud_DescriptorReturnsAzureSpotinstCloudString() { //region Helper Methods public SSHConnector getSSHConnector() { - SSHConnector retVal = new SSHConnector(22, "testCredentials"); - return retVal; + return new SSHConnector(22, "testCredentials"); } //endregion } \ No newline at end of file