Skip to content

Commit

Permalink
Jenkins plugin instance type search (#27)
Browse files Browse the repository at this point in the history
Added capability to search for instances by instance types
  • Loading branch information
Lironrad authored Dec 29, 2022
1 parent 409a78e commit de0b5be
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 30 deletions.
5 changes: 5 additions & 0 deletions JenkinsWiki.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ termination"

[SpotinstPlugin-Versionhistory]
== Version history
[SpotinstPlugin-Version2.2.9(Dec29,2022)]
=== Version 2.2.9 (Dec 29, 2022)

* Added Search Instance Type by Input feature

[SpotinstPlugin-Version2.2.8(Jul14,2022)]
=== Version 2.2.8 (Jul 14, 2022)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class AwsSpotinstCloud extends BaseSpotinstCloud {
private static final String CLOUD_URL = "aws/ec2";
protected Map<String, Integer> executorsByInstanceType;
private List<? extends SpotinstInstanceWeight> executorsForTypes;
private List<String> invalidInstanceTypes;
//endregion

//region Constructor
Expand Down Expand Up @@ -370,13 +371,19 @@ private void addSpotinstSlave(AwsGroupInstance instance) {

private void initExecutorsByInstanceType() {
this.executorsByInstanceType = new HashMap<>();
this.invalidInstanceTypes = new LinkedList<>();

if (this.executorsForTypes != null) {
for (SpotinstInstanceWeight instance : this.executorsForTypes) {
if (instance.getExecutors() != null) {
Integer executors = instance.getExecutors();
String type = instance.getAwsInstanceTypeFromAPI();
String type = instance.getAwsInstanceTypeFromAPIInput();
this.executorsByInstanceType.put(type, executors);

if(instance.getIsValid() == false){
LOGGER.error(String.format("Invalid type \'%s\' in group \'%s\'", type, this.getGroupId()));
invalidInstanceTypes.add(type);
}
}
}
}
Expand All @@ -387,6 +394,10 @@ private void initExecutorsByInstanceType() {
public List<? extends SpotinstInstanceWeight> getExecutorsForTypes() {
return executorsForTypes;
}

public List<String> getInvalidInstanceTypes() {
return this.invalidInstanceTypes;
}
//endregion

//region Classes
Expand Down
174 changes: 148 additions & 26 deletions src/main/java/hudson/plugins/spotinst/cloud/SpotinstInstanceWeight.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package hudson.plugins.spotinst.cloud;

import hudson.Extension;
import hudson.model.AutoCompletionCandidates;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.plugins.spotinst.common.AwsInstanceTypeEnum;
import hudson.plugins.spotinst.common.AwsInstanceTypeSelectMethodEnum;
import hudson.plugins.spotinst.common.SpotAwsInstanceTypesHelper;
import hudson.plugins.spotinst.common.SpotinstContext;
import hudson.plugins.spotinst.model.aws.AwsInstanceType;
Expand All @@ -12,8 +14,10 @@
import jenkins.model.Jenkins;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

import java.util.List;
import java.util.stream.Stream;

import static hudson.plugins.spotinst.api.SpotinstApi.validateToken;

Expand All @@ -22,10 +26,13 @@
*/
public class SpotinstInstanceWeight implements Describable<SpotinstInstanceWeight> {
//region Members
private Integer executors;
private String awsInstanceTypeFromAPI;
private Integer executors;
private String awsInstanceTypeFromAPI;
private String awsInstanceTypeFromAPISearch;
private AwsInstanceTypeSelectMethodEnum selectMethod;
private boolean isValid;
//Deprecated
private AwsInstanceTypeEnum awsInstanceType;
private AwsInstanceTypeEnum awsInstanceType;
//endregion

//region Constructors
Expand All @@ -47,6 +54,67 @@ public Descriptor<SpotinstInstanceWeight> getDescriptor() {

return retVal;
}

@Override
public String toString() {
return "SpotinstInstanceWeight:{ " + "Pick: " + this.awsInstanceTypeFromAPI + ", " + "Search: " +
this.awsInstanceTypeFromAPISearch + ", " + "type: " + this.awsInstanceType + ", " + "executors: " +
this.executors + " }";

}
//endregion

//region Methods
public String getAwsInstanceTypeFromAPIInput() {
String type;
AwsInstanceTypeSelectMethodEnum selectMethod = getSelectMethod();

if (selectMethod == AwsInstanceTypeSelectMethodEnum.SEARCH) {
type = getAwsInstanceTypeFromAPISearch();
}
else {
type = getAwsInstanceTypeFromAPI();
}

return type;
}
//endregion

//region Private Methods
private String getAwsInstanceTypeByName(String awsInstanceTypeFromAPIName) {
String retVal = null;

if (awsInstanceTypeFromAPIName != null) {

/*
If the user Previously chosen was a type that not exist in the hard coded list
and did not configure the token right, we will present the chosen type and set the default vCPU to 1
The descriptor of this class will show a warning message will note the user that something is wrong,
and point to authentication fix before saving this configuration.
*/
List<AwsInstanceType> types = SpotAwsInstanceTypesHelper.getAllInstanceTypes();
isValid = types.stream().anyMatch(i -> i.getInstanceType().equals(awsInstanceTypeFromAPIName));

if (isValid == false) {
if (getSelectMethod() != AwsInstanceTypeSelectMethodEnum.SEARCH) {
AwsInstanceType instanceType = new AwsInstanceType();
instanceType.setInstanceType(awsInstanceTypeFromAPIName);
instanceType.setvCPU(1);
SpotinstContext.getInstance().getAwsInstanceTypes().add(instanceType);
}
}

retVal = awsInstanceTypeFromAPIName;

}
else {
if (awsInstanceType != null) {
retVal = awsInstanceType.getValue();
}
}

return retVal;
}
//endregion

//region Classes
Expand All @@ -71,13 +139,36 @@ public ListBoxModel doFillAwsInstanceTypeFromAPIItems() {
return retVal;
}

public AutoCompletionCandidates doAutoCompleteAwsInstanceTypeFromAPISearch(@QueryParameter String value) {
AutoCompletionCandidates retVal = new AutoCompletionCandidates();
List<AwsInstanceType> allAwsInstanceTypes = SpotAwsInstanceTypesHelper.getAllInstanceTypes();
Stream<String> allTypes =
allAwsInstanceTypes.stream().map(AwsInstanceType::getInstanceType);
Stream<String> matchingTypes = allTypes.filter(type -> type.startsWith(value));
matchingTypes.forEach(retVal::add);

return retVal;
}

public FormValidation doCheckAwsInstanceTypeFromAPI() {
FormValidation retVal = CheckAccountIdAndToken();

return retVal;
}

public FormValidation doCheckAwsInstanceTypeFromAPISearch() {
FormValidation retVal = CheckAccountIdAndToken();

return retVal;
}

private FormValidation CheckAccountIdAndToken() {
FormValidation retVal = null;

String accountId = SpotinstContext.getInstance().getAccountId();
String token = SpotinstContext.getInstance().getSpotinstToken();
int isValid = validateToken(token, accountId);
Boolean isInstanceTypesListUpdate = SpotAwsInstanceTypesHelper.isInstanceTypesListUpdate();
boolean isInstanceTypesListUpdate = SpotAwsInstanceTypesHelper.isInstanceTypesListUpdate();

if (isValid != 0 || isInstanceTypesListUpdate == false) {
retVal = FormValidation.error(
Expand All @@ -99,42 +190,73 @@ public AwsInstanceTypeEnum getAwsInstanceType() {
return awsInstanceType;
}

public String getAwsInstanceTypeFromAPI() {
String retVal;

if (selectMethod != AwsInstanceTypeSelectMethodEnum.SEARCH) {
retVal = getAwsInstanceTypeByName(this.awsInstanceTypeFromAPI);
}
else {
retVal = this.awsInstanceTypeFromAPI;
}

return retVal;
}

@DataBoundSetter
public void setAwsInstanceTypeFromAPI(String awsInstanceTypeFromAPI) {
this.awsInstanceTypeFromAPI = awsInstanceTypeFromAPI;

if (selectMethod != AwsInstanceTypeSelectMethodEnum.SEARCH) {
this.awsInstanceTypeFromAPISearch = awsInstanceTypeFromAPI;
}
}

public String getAwsInstanceTypeFromAPI() {
String retVal = null;
public String getAwsInstanceTypeFromAPISearch() {
String retVal;

if (this.awsInstanceTypeFromAPI != null) {
if (selectMethod == AwsInstanceTypeSelectMethodEnum.SEARCH) {
retVal = getAwsInstanceTypeByName(this.awsInstanceTypeFromAPISearch);
}
else {
retVal = this.awsInstanceTypeFromAPISearch;
}

/*
If the user Previously chosen was a type that not exist in the hard coded list
and did not configure the token right, we will present the chosen type and set the default vCPU to 1
The descriptor of this class will show a warning message will note the user that something is wrong,
and point to authentication fix before saving this configuration.
*/
List<AwsInstanceType> types = SpotAwsInstanceTypesHelper.getAllInstanceTypes();
boolean isTypeInList = types.stream().anyMatch(i -> i.getInstanceType().equals(this.awsInstanceTypeFromAPI));
return retVal;
}

if (isTypeInList == false) {
AwsInstanceType instanceType = new AwsInstanceType();
instanceType.setInstanceType(awsInstanceTypeFromAPI);
instanceType.setvCPU(1);
SpotinstContext.getInstance().getAwsInstanceTypes().add(instanceType);
}
@DataBoundSetter
public void setAwsInstanceTypeFromAPISearch(String awsInstanceTypeFromAPISearch) {
this.awsInstanceTypeFromAPISearch = awsInstanceTypeFromAPISearch;

if (selectMethod == AwsInstanceTypeSelectMethodEnum.SEARCH) {
this.awsInstanceTypeFromAPI = awsInstanceTypeFromAPISearch;
}
}

retVal = awsInstanceTypeFromAPI;
public AwsInstanceTypeSelectMethodEnum getSelectMethod() {
AwsInstanceTypeSelectMethodEnum retVal = AwsInstanceTypeSelectMethodEnum.PICK;

if (selectMethod != null) {
retVal = selectMethod;
}

return retVal;
}

@DataBoundSetter
public void setSelectMethod(AwsInstanceTypeSelectMethodEnum selectMethod) {

if (selectMethod == null) {
this.selectMethod = AwsInstanceTypeSelectMethodEnum.PICK;
}
else {
if(awsInstanceType != null){
retVal = awsInstanceType.getValue();
}
this.selectMethod = selectMethod;
}
}

return retVal;
public boolean getIsValid() {
return this.isValid;
}
//endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package hudson.plugins.spotinst.cloud.monitor;

import hudson.Extension;
import hudson.model.AdministrativeMonitor;
import hudson.plugins.spotinst.cloud.AwsSpotinstCloud;
import hudson.slaves.Cloud;
import jenkins.model.Jenkins;
import org.apache.commons.collections.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Extension
public class AwsSpotinstCloudInstanceTypeMonitor extends AdministrativeMonitor {
//region members
Map<String, List<String>> invalidInstancesByGroupId;
//endregion

//region Overrides
@Override
public boolean isActivated() {
boolean retVal;
initInvalidInstances();
retVal = hasInvalidInstanceType();
return retVal;
}

@Override
public String getDisplayName() {
return "Aws Spotinst Cloud Instance Type Monitor";
}
//endregion

//region Methods
public boolean hasInvalidInstanceType() {
return invalidInstancesByGroupId.isEmpty() == false;
}
//endregion

//region getters & setters
public String getInvalidInstancesByGroupId() {
Stream<String> invalidInstancesForOutput =
invalidInstancesByGroupId.keySet().stream().map(this::generateAlertMessage);
String retVal = invalidInstancesForOutput.collect(Collectors.joining(", "));

return retVal;
}
//endregion

//region private Methods
private void initInvalidInstances() {
invalidInstancesByGroupId = new HashMap<>();
Jenkins jenkinsInstance = Jenkins.getInstance();
List<Cloud> clouds = jenkinsInstance != null ? jenkinsInstance.clouds : new LinkedList<>();
List<AwsSpotinstCloud> awsClouds = clouds.stream().filter(cloud -> cloud instanceof AwsSpotinstCloud)
.map(awsCloud -> (AwsSpotinstCloud) awsCloud)
.collect(Collectors.toList());

awsClouds.forEach(awsCloud -> {
String elastigroupId = awsCloud.getGroupId();
List<String> invalidTypes = awsCloud.getInvalidInstanceTypes();

if (CollectionUtils.isEmpty(invalidTypes) == false) {
invalidInstancesByGroupId.put(elastigroupId, invalidTypes);
}
});
}

private String generateAlertMessage(String group) {
StringBuilder retVal = new StringBuilder();
retVal.append('\'').append(group).append('\'').append(": [");

List<String> InvalidInstancesByGroup = invalidInstancesByGroupId.get(group);
Stream<String> InvalidInstancesForAlert =
InvalidInstancesByGroup.stream().map(invalidInstance -> '\'' + invalidInstance + '\'');

String instances = InvalidInstancesForAlert.collect(Collectors.joining(", "));
retVal.append(instances).append(']');
return retVal.toString();
}
//region
}
Loading

0 comments on commit de0b5be

Please sign in to comment.