Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jenkins plugin instance type search #27

Merged
merged 14 commits into from
Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if retVal == true (has invalid) generate the alert message and print it to log

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