diff --git a/docs/CONFIGURATION-AS-CODE.md b/docs/CONFIGURATION-AS-CODE.md index fdc2c501..3f918d90 100644 --- a/docs/CONFIGURATION-AS-CODE.md +++ b/docs/CONFIGURATION-AS-CODE.md @@ -8,32 +8,32 @@ [Definition](https://github.com/jenkinsci/ec2-fleet-plugin/blob/master/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetCloud.java#L156-L179) -| Property | Type | Required | Description | -|----------------------------|---------|------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| -| name | string | yes, default ```""``` | A unique name for Jenkins cloud. "" signals the plugin to generate a unique default name for the Cloud. e.g. FleetCloud-jBGChqOP | -| awsCredentialsId | string | no, default ```null``` | [Leave blank to use AWS EC2 instance role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html) | -| computerConnector | object | yes | for example ```sshConnector``` | -| region | string | yes | ```us-east-2```, full [list](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) | -| fleet | string | yes | my-fleet | -| endpoint | string | no | Set only if you need to use custome endpoint ```http://a.com``` | -| fsRoot | string | no | my-root | -| privateIpUsed | boolean | no, default ```false``` | connect to EC2 instance by private id instead of public | -| alwaysReconnect | boolean | no, default ```false``` || -| labelString | string | yes || -| idleMinutes | int | no, default ```0``` || -| minSize | int | no, default ```0``` || -| maxSize | int | no, default ```0``` || -| minSpareSize | int | no, default ```0``` || minimum number of instances allowed to be idle, ready to pickup work. maxSize overrides minSpareSize. Such instances are exempted from 'Max Idle Minutes Before Scaledown' config. -| maxTotalUses | int | no, default ```-1``` i.e. unlimited uses || maximum number of times a node can be used. Overrides minSize and minSpareSize, if set. -| numExecutors | int | no, default ```1``` || -| addNodeOnlyIfRunning | boolean | no, default ```false``` || -| restrictUsage | boolean | no, default ```false``` | if ```true``` fleet nodes will executed only jobs with same label | -| scaleExecutorsByWeight | boolean | no, default ```false``` || -| disableTaskResubmit | boolean | no, default ```false``` || -| initOnlineTimeoutSec | int | no, default ```180``` || -| initOnlineCheckIntervalSec | int | no, default ```15``` || -| cloudStatusIntervalSec | int | no, default ```10``` || -| noDelayProvision | boolean | no, default ```false``` || +| Property | Type | Required | Description | +|----------------------------|---------|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| +| name | string | yes, default ```"FleetCloud-XXXXXXXX"``` | A unique name for Jenkins cloud. "" signals the plugin to generate a unique default name for the Cloud. e.g. FleetCloud-jBGChqOP | +| awsCredentialsId | string | no, default ```null``` | [Leave blank to use AWS EC2 instance role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html) | +| computerConnector | object | yes | for example ```sshConnector``` | +| region | string | yes | ```us-east-2```, full [list](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) | +| fleet | string | yes | my-fleet | +| endpoint | string | no | Set only if you need to use custom endpoint ```http://a.com``` | +| fsRoot | string | no | my-root | +| privateIpUsed | boolean | no, default ```false``` | connect to EC2 instance by private id instead of public | +| alwaysReconnect | boolean | no, default ```false``` | | +| labelString | string | yes | | +| idleMinutes | int | no, default ```0``` | | +| minSize | int | no, default ```0``` | | +| maxSize | int | no, default ```1``` | | +| minSpareSize | int | no, default ```0``` | | minimum number of instances allowed to be idle, ready to pickup work. maxSize overrides minSpareSize. Such instances are exempted from 'Max Idle Minutes Before Scaledown' config. +| maxTotalUses | int | no, default ```-1``` i.e. unlimited uses | | maximum number of times a node can be used. Overrides minSize and minSpareSize, if set. +| numExecutors | int | no, default ```1``` | | +| addNodeOnlyIfRunning | boolean | no, default ```false``` | | +| restrictUsage | boolean | no, default ```false``` | if ```true``` fleet nodes will executed only jobs with same label | +| scaleExecutorsByWeight | boolean | no, default ```false``` | | +| disableTaskResubmit | boolean | no, default ```false``` | | +| initOnlineTimeoutSec | int | no, default ```180``` | | +| initOnlineCheckIntervalSec | int | no, default ```15``` | | +| cloudStatusIntervalSec | int | no, default ```10``` | | +| noDelayProvision | boolean | no, default ```false``` | | ## EC2FleetLabelCloud @@ -41,6 +41,29 @@ More about this type [here](LABEL-BASED-CONFIGURATION.md) [Definition](https://github.com/jenkinsci/ec2-fleet-plugin/blob/master/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetLabelCloud.java#L123-L145) +| Property | Type | Required | Description | +|----------------------------|---------|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| +| name | string | yes, default ```"FleetCloudLabel-XXXXXXXX"``` | A unique name for Jenkins cloud. "" signals the plugin to generate a unique default name for the Cloud. e.g. `FleetCloudLabel-jBGChqOP` | +| awsCredentialsId | string | no, default ```null``` | [Leave blank to use AWS EC2 instance role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html) | +| computerConnector | object | yes | for example ```sshConnector``` | +| region | string | yes | ```us-east-2```, full [list](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html) | +| endpoint | string | no | Set only if you need to use custom endpoint ```http://a.com``` | +| fsRoot | string | no | my-root | +| privateIpUsed | boolean | no, default ```false``` | connect to EC2 instance by private id instead of public | +| alwaysReconnect | boolean | no, default ```false``` | | +| idleMinutes | int | no, default ```0``` | | +| minSize | int | no, default ```0``` | | +| maxSize | int | no, default ```1``` | | +| maxTotalUses | int | no, default ```-1``` i.e. unlimited uses | | maximum number of times a node can be used. Overrides minSize and minSpareSize, if set. +| numExecutors | int | no, default ```1``` | | +| restrictUsage | boolean | no, default ```false``` | if ```true``` fleet nodes will executed only jobs with same label | +| disableTaskResubmit | boolean | no, default ```false``` | | +| initOnlineTimeoutSec | int | no, default ```180``` | | +| initOnlineCheckIntervalSec | int | no, default ```15``` | | +| cloudStatusIntervalSec | int | no, default ```10``` | | +| noDelayProvision | boolean | no, default ```false``` | | +| ec2KeyPairName | string | yes | AWS EC2 SSH Key-Pair Name | + ## Examples ### EC2FleetCloud (min set of properties) @@ -49,7 +72,7 @@ More about this type [here](LABEL-BASED-CONFIGURATION.md) jenkins: clouds: - ec2Fleet: - name: ec2-fleet + name: "" computerConnector: sshConnector: credentialsId: cred @@ -57,8 +80,7 @@ jenkins: NonVerifyingKeyVerificationStrategy region: us-east-2 fleet: my-fleet - minSize: 1 - maxSize: 10 + labelString: "" ``` ### EC2FleetCloud (All properties) @@ -81,6 +103,7 @@ jenkins: labelString: myLabel idleMinutes: 33 minSize: 15 + minSpareSize: 20 maxSize: 90 numExecutors: 12 addNodeOnlyIfRunning: true @@ -92,3 +115,48 @@ jenkins: disableTaskResubmit: true noDelayProvision: true ``` + +### EC2FleetLabelCloud (min set of properties) + +```yaml +jenkins: + clouds: + - ec2FleetLabel: + name: "" + computerConnector: + sshConnector: + credentialsId: cred + sshHostKeyVerificationStrategy: + NonVerifyingKeyVerificationStrategy + region: us-east-2 + ec2KeyPairName: ec2KeyPair +``` + +### EC2FleetLabelCloud (All properties) + +```yaml +jenkins: + clouds: + - ec2FleetLabel: + name: ec2-fleet-label + awsCredentialsId: xx + computerConnector: + sshConnector: + credentialsId: cred + region: us-east-2 + endpoint: http://a.com + fsRoot: my-root + privateIpUsed: true + alwaysReconnect: true + idleMinutes: 33 + minSize: 15 + maxSize: 90 + numExecutors: 12 + restrictUsage: true + initOnlineTimeoutSec: 181 + initOnlineCheckIntervalSec: 13 + cloudStatusIntervalSec: 11 + disableTaskResubmit: true + noDelayProvision: true + ec2KeyPairName: ec2KeyPair +``` diff --git a/src/main/java/com/amazon/jenkins/ec2fleet/CloudConstants.java b/src/main/java/com/amazon/jenkins/ec2fleet/CloudConstants.java new file mode 100644 index 00000000..651a01f3 --- /dev/null +++ b/src/main/java/com/amazon/jenkins/ec2fleet/CloudConstants.java @@ -0,0 +1,33 @@ +package com.amazon.jenkins.ec2fleet; + +class CloudConstants { + + static final String EC2_INSTANCE_TAG_NAMESPACE = "ec2-fleet-plugin"; + static final String EC2_INSTANCE_CLOUD_NAME_TAG = EC2_INSTANCE_TAG_NAMESPACE + ":cloud-name"; + static final boolean DEFAULT_PRIVATE_IP_USED = false; + + static final boolean DEFAULT_ALWAYS_RECONNECT = false; + + static final int DEFAULT_IDLE_MINUTES = 0; + + static final int DEFAULT_MIN_SIZE = 0; + + static final int DEFAULT_MAX_SIZE = 1; + + static final int DEFAULT_MIN_SPARE_SIZE = 0; + + static final int DEFAULT_NUM_EXECUTORS = 1; + + static final boolean DEFAULT_ADD_NODE_ONLY_IF_RUNNING = false; + + static final boolean DEFAULT_RESTRICT_USAGE = false; + + static final boolean DEFAULT_SCALE_EXECUTORS_BY_WEIGHT = false; + + static final int DEFAULT_CLOUD_STATUS_INTERVAL_SEC = 10; + + static final int DEFAULT_INIT_ONLINE_TIMEOUT_SEC = 3 * 60; + static final int DEFAULT_INIT_ONLINE_CHECK_INTERVAL_SEC = 15; + + static final int DEFAULT_MAX_TOTAL_USES = -1; +} diff --git a/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetCloud.java b/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetCloud.java index f19db6a9..285a818b 100644 --- a/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetCloud.java +++ b/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetCloud.java @@ -28,6 +28,7 @@ import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; @@ -55,6 +56,8 @@ import java.util.logging.SimpleFormatter; import java.util.stream.Collectors; +import static com.amazon.jenkins.ec2fleet.CloudConstants.*; + /** * The {@link EC2FleetCloud} contains the main configuration values used while creating Jenkins nodes. * EC2FleetCloud can represent either an AWS EC2 Spot Fleet or an AWS AutoScalingGroup. @@ -70,18 +73,8 @@ @SuppressWarnings({"unused", "WeakerAccess"}) public class EC2FleetCloud extends AbstractEC2FleetCloud { - public static final String EC2_INSTANCE_TAG_NAMESPACE = "ec2-fleet-plugin"; - public static final String EC2_INSTANCE_CLOUD_NAME_TAG = EC2_INSTANCE_TAG_NAMESPACE + ":cloud-name"; - public static final String BASE_DEFAULT_FLEET_CLOUD_ID = "FleetCloud"; - public static final int DEFAULT_CLOUD_STATUS_INTERVAL_SEC = 10; - - private static final int DEFAULT_INIT_ONLINE_TIMEOUT_SEC = 3 * 60; - private static final int DEFAULT_INIT_ONLINE_CHECK_INTERVAL_SEC = 15; - - private static final int DEFAULT_MAX_TOTAL_USES = -1; - private static final SimpleFormatter sf = new SimpleFormatter(); private static final Logger LOGGER = Logger.getLogger(EC2FleetCloud.class.getName()); private static final ScheduledExecutorService EXECUTOR = Executors.newSingleThreadScheduledExecutor(); @@ -101,43 +94,43 @@ public class EC2FleetCloud extends AbstractEC2FleetCloud { * Will be deleted in future when usage for old version <= 1.1.9 will be totally dropped. */ @Deprecated - private final String credentialsId; + private String credentialsId; - private final String awsCredentialsId; + private String awsCredentialsId; private final String region; - private final String endpoint; + private String endpoint; /** * In fact fleet ID, not name or anything else */ private final String fleet; - private final String fsRoot; + private String fsRoot; private final ComputerConnector computerConnector; - private final boolean privateIpUsed; - private final boolean alwaysReconnect; + private boolean privateIpUsed = DEFAULT_PRIVATE_IP_USED; + private boolean alwaysReconnect = DEFAULT_ALWAYS_RECONNECT; private final String labelString; - private final Integer idleMinutes; - private final int minSize; - private final int maxSize; - private final int minSpareSize; - private final int numExecutors; - private final boolean addNodeOnlyIfRunning; - private final boolean restrictUsage; - private final boolean scaleExecutorsByWeight; - private final Integer initOnlineTimeoutSec; - private final Integer initOnlineCheckIntervalSec; - private final Integer cloudStatusIntervalSec; - private final Integer maxTotalUses; + private int idleMinutes = DEFAULT_IDLE_MINUTES; + private int minSize = DEFAULT_MIN_SIZE; + private int maxSize = DEFAULT_MAX_SIZE; + private int minSpareSize = DEFAULT_MIN_SPARE_SIZE; + private int numExecutors = DEFAULT_NUM_EXECUTORS; + private boolean addNodeOnlyIfRunning = DEFAULT_ADD_NODE_ONLY_IF_RUNNING; + private boolean restrictUsage = DEFAULT_RESTRICT_USAGE; + private boolean scaleExecutorsByWeight = DEFAULT_SCALE_EXECUTORS_BY_WEIGHT; + private Integer initOnlineTimeoutSec = DEFAULT_INIT_ONLINE_TIMEOUT_SEC; + private Integer initOnlineCheckIntervalSec = DEFAULT_INIT_ONLINE_CHECK_INTERVAL_SEC; + private Integer cloudStatusIntervalSec = DEFAULT_CLOUD_STATUS_INTERVAL_SEC; + private Integer maxTotalUses = DEFAULT_MAX_TOTAL_USES; /** * @see EC2FleetAutoResubmitComputerLauncher */ - private final boolean disableTaskResubmit; + private boolean disableTaskResubmit; /** * @see NoDelayProvisionStrategy */ - private final boolean noDelayProvision; + private boolean noDelayProvision; /** * {@link EC2FleetCloud#update()} updating this field, this is one thread @@ -163,6 +156,28 @@ public class EC2FleetCloud extends AbstractEC2FleetCloud { private transient AtomicInteger plannedNodeCounter = new AtomicInteger(1); @DataBoundConstructor + public EC2FleetCloud(@Nonnull final String name, + final String awsCredentialsId, + final String region, + final String fleet, + final ComputerConnector computerConnector, + final String labelString) { + super(StringUtils.isNotBlank(name) ? name : CloudNames.generateUnique(BASE_DEFAULT_FLEET_CLOUD_ID)); + init(); + //TODO: eventually move this out of this constructor since it is not technically a required field + setAwsCredentialsId(awsCredentialsId); + this.region = region; + this.fleet = fleet; + this.computerConnector = computerConnector; + this.labelString = labelString; + + if (fleet != null) { + this.stats = EC2Fleets.get(fleet).getState( + getAwsCredentialsId(), region, endpoint, getFleet()); + } + } + + @Deprecated public EC2FleetCloud(@Nonnull final String name, final String awsCredentialsId, final @Deprecated String credentialsId, @@ -174,7 +189,7 @@ public EC2FleetCloud(@Nonnull final String name, final ComputerConnector computerConnector, final boolean privateIpUsed, final boolean alwaysReconnect, - final Integer idleMinutes, + final int idleMinutes, final int minSize, final int maxSize, final int minSpareSize, @@ -188,35 +203,26 @@ public EC2FleetCloud(@Nonnull final String name, final boolean scaleExecutorsByWeight, final Integer cloudStatusIntervalSec, final boolean noDelayProvision) { - super(StringUtils.isNotBlank(name) ? name : CloudNames.generateUnique(BASE_DEFAULT_FLEET_CLOUD_ID)); - init(); + this(name, awsCredentialsId, region, fleet, computerConnector, labelString); this.credentialsId = credentialsId; - this.awsCredentialsId = awsCredentialsId; - this.region = region; - this.endpoint = endpoint; - this.fleet = fleet; - this.fsRoot = fsRoot; - this.computerConnector = computerConnector; - this.labelString = labelString; - this.idleMinutes = idleMinutes; - this.privateIpUsed = privateIpUsed; - this.alwaysReconnect = alwaysReconnect; - if (minSize < 0) { - warning("Cloud parameter 'minSize' can't be less than 0, setting to 0"); - } - this.minSize = Math.max(0, minSize); - this.maxSize = maxSize; - this.minSpareSize = Math.max(0, minSpareSize); - this.maxTotalUses = StringUtils.isBlank(maxTotalUses) ? DEFAULT_MAX_TOTAL_USES : Integer.parseInt(maxTotalUses); - this.numExecutors = Math.max(numExecutors, 1); - this.addNodeOnlyIfRunning = addNodeOnlyIfRunning; - this.restrictUsage = restrictUsage; - this.scaleExecutorsByWeight = scaleExecutorsByWeight; - this.disableTaskResubmit = disableTaskResubmit; - this.initOnlineTimeoutSec = initOnlineTimeoutSec; - this.initOnlineCheckIntervalSec = initOnlineCheckIntervalSec; - this.cloudStatusIntervalSec = cloudStatusIntervalSec; - this.noDelayProvision = noDelayProvision; + setEndpoint(endpoint); + setFsRoot(fsRoot); + setPrivateIpUsed(privateIpUsed); + setAlwaysReconnect(alwaysReconnect); + setIdleMinutes(idleMinutes); + setMinSize(minSize); + setMaxSize(maxSize); + setMinSpareSize(minSpareSize); + setMaxTotalUses(maxTotalUses); + setNumExecutors(numExecutors); + setAddNodeOnlyIfRunning(addNodeOnlyIfRunning); + setRestrictUsage(restrictUsage); + setScaleExecutorsByWeight(scaleExecutorsByWeight); + setDisableTaskResubmit(disableTaskResubmit); + setInitOnlineTimeoutSec(initOnlineTimeoutSec); + setInitOnlineCheckIntervalSec(initOnlineCheckIntervalSec); + setCloudStatusIntervalSec(cloudStatusIntervalSec); + setNoDelayProvision(noDelayProvision); if (fleet != null) { this.stats = EC2Fleets.get(fleet).getState( @@ -228,6 +234,11 @@ public boolean isNoDelayProvision() { return noDelayProvision; } + @DataBoundSetter + public void setNoDelayProvision(boolean noDelayProvision) { + this.noDelayProvision = noDelayProvision; + } + /** * Deprecated.Use {@link EC2FleetCloud#awsCredentialsId} * @@ -240,12 +251,36 @@ public String getAwsCredentialsId() { return StringUtils.isNotBlank(awsCredentialsId) ? awsCredentialsId : credentialsId; } + @DataBoundSetter + public void setAwsCredentialsId(String awsCredentialsId) { + this.awsCredentialsId = awsCredentialsId; + } + + public String getCredentialsId() { + return credentialsId; + } + + @DataBoundSetter + public void setCredentialsId(String credentialsId) { + this.credentialsId = credentialsId; + } + public boolean isDisableTaskResubmit() { return disableTaskResubmit; } + @DataBoundSetter + public void setDisableTaskResubmit(boolean disableTaskResubmit) { + this.disableTaskResubmit = disableTaskResubmit; + } + public int getInitOnlineTimeoutSec() { - return initOnlineTimeoutSec == null ? DEFAULT_INIT_ONLINE_TIMEOUT_SEC : initOnlineTimeoutSec; + return initOnlineTimeoutSec; + } + + @DataBoundSetter + public void setInitOnlineTimeoutSec(Integer initOnlineTimeoutSec) { + this.initOnlineTimeoutSec = initOnlineTimeoutSec; } public int getScheduledFutureTimeoutSec() { @@ -254,11 +289,21 @@ public int getScheduledFutureTimeoutSec() { } public int getCloudStatusIntervalSec() { - return cloudStatusIntervalSec == null ? DEFAULT_CLOUD_STATUS_INTERVAL_SEC : cloudStatusIntervalSec; + return cloudStatusIntervalSec; + } + + @DataBoundSetter + public void setCloudStatusIntervalSec(Integer cloudStatusIntervalSec) { + this.cloudStatusIntervalSec = cloudStatusIntervalSec; } public int getInitOnlineCheckIntervalSec() { - return initOnlineCheckIntervalSec == null ? DEFAULT_INIT_ONLINE_CHECK_INTERVAL_SEC : initOnlineCheckIntervalSec; + return initOnlineCheckIntervalSec; + } + + @DataBoundSetter + public void setInitOnlineCheckIntervalSec(Integer initOnlineCheckIntervalSec) { + this.initOnlineCheckIntervalSec = initOnlineCheckIntervalSec; } public String getRegion() { @@ -269,6 +314,11 @@ public String getEndpoint() { return endpoint; } + @DataBoundSetter + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + public String getFleet() { return fleet; } @@ -277,10 +327,20 @@ public String getFsRoot() { return fsRoot; } + @DataBoundSetter + public void setFsRoot(String fsRoot) { + this.fsRoot = fsRoot; + } + public boolean isAddNodeOnlyIfRunning() { return addNodeOnlyIfRunning; } + @DataBoundSetter + public void setAddNodeOnlyIfRunning(boolean addNodeOnlyIfRunning) { + this.addNodeOnlyIfRunning = addNodeOnlyIfRunning; + } + public ComputerConnector getComputerConnector() { return computerConnector; } @@ -289,42 +349,105 @@ public boolean isPrivateIpUsed() { return privateIpUsed; } + @DataBoundSetter + public void setPrivateIpUsed(boolean privateIpUsed) { + this.privateIpUsed = privateIpUsed; + } + public boolean isAlwaysReconnect() { return alwaysReconnect; } + @DataBoundSetter + public void setAlwaysReconnect(boolean alwaysReconnect) { + this.alwaysReconnect = alwaysReconnect; + } + public String getLabelString() { return labelString; } public int getIdleMinutes() { - return (idleMinutes != null) ? idleMinutes : 0; + return idleMinutes; + } + + @DataBoundSetter + public void setIdleMinutes(int idleMinutes) { + this.idleMinutes = Math.max(0, idleMinutes); } public int getMaxSize() { return maxSize; } + @DataBoundSetter + public void setMaxSize(int maxSize) { + if (maxSize < minSize) { + int newMaxSize = Math.max(1, minSize); + warning("Cloud parameter 'maxSize' can't be less than 'minSize' or 1, setting to %d", newMaxSize); + maxSize = newMaxSize; + } + this.maxSize = Math.max(1, maxSize); + } + public int getMinSize() { return minSize; } - public int getMinSpareSize() { + @DataBoundSetter + public void setMinSize(int minSize) { + if (minSize < 0) { + warning("Cloud parameter 'minSize' can't be less than 0, setting to 0"); + } + minSize = Math.max(0, minSize); + //TODO: This validation is only in place for unit tests since constructor is run twice on CasC load but not + // for unit tests + if (minSize > maxSize) { + warning("Cloud parameter 'minSize' cannot be greater than 'maxSize', setting 'maxSize' to %d. " + + "Ignore this if caused after a CasC load.", minSize); + this.maxSize = minSize; + } + this.minSize = minSize; + } + + public synchronized int getMinSpareSize() { return minSpareSize; } + @DataBoundSetter + public synchronized void setMinSpareSize(int minSpareSize) { + this.minSpareSize = Math.max(0, minSpareSize); + } + public int getMaxTotalUses() { return maxTotalUses; } + @DataBoundSetter + public void setMaxTotalUses(String maxTotalUses) { + if (StringUtils.isNotBlank(maxTotalUses)) { + this.maxTotalUses = Integer.parseInt(maxTotalUses); + } + } + public int getNumExecutors() { return numExecutors; } + @DataBoundSetter + public void setNumExecutors(int numExecutors) { + this.numExecutors = Math.max(numExecutors, 1); + } + public boolean isScaleExecutorsByWeight() { return scaleExecutorsByWeight; } + @DataBoundSetter + public void setScaleExecutorsByWeight(boolean scaleExecutorsByWeight) { + this.scaleExecutorsByWeight = scaleExecutorsByWeight; + } + public String getJvmSettings() { return ""; } @@ -333,6 +456,11 @@ public boolean isRestrictUsage() { return restrictUsage; } + @DataBoundSetter + public void setRestrictUsage(boolean restrictUsage) { + this.restrictUsage = restrictUsage; + } + // Visible for testing synchronized Set getPlannedNodesCache() { return plannedNodesCache; diff --git a/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetLabelCloud.java b/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetLabelCloud.java index 24cf4c5e..9a409cf2 100644 --- a/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetLabelCloud.java +++ b/src/main/java/com/amazon/jenkins/ec2fleet/EC2FleetLabelCloud.java @@ -32,6 +32,7 @@ import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; @@ -54,58 +55,64 @@ import java.util.logging.SimpleFormatter; import java.util.stream.Collectors; +import static com.amazon.jenkins.ec2fleet.CloudConstants.*; + /** * @see CloudNanny */ @SuppressWarnings({"unused", "WeakerAccess"}) public class EC2FleetLabelCloud extends AbstractEC2FleetCloud { - public static final String EC2_INSTANCE_TAG_NAMESPACE = "ec2-fleet-plugin"; - public static final String EC2_INSTANCE_CLOUD_NAME_TAG = EC2_INSTANCE_TAG_NAMESPACE + ":cloud-name"; - public static final String BASE_DEFAULT_FLEET_CLOUD_ID = "FleetCloudLabel"; - public static final int DEFAULT_CLOUD_STATUS_INTERVAL_SEC = 10; - - private static final int DEFAULT_INIT_ONLINE_TIMEOUT_SEC = 3 * 60; - private static final int DEFAULT_INIT_ONLINE_CHECK_INTERVAL_SEC = 15; - // private static final String NEW_EC2_KEY_PAIR_VALUE = "- New Key Pair -"; private static final SimpleFormatter sf = new SimpleFormatter(); private static final Logger LOGGER = Logger.getLogger(EC2FleetLabelCloud.class.getName()); - private final String awsCredentialsId; + private String awsCredentialsId; private final String region; - private final String endpoint; + private String endpoint; - private final String fsRoot; + private String fsRoot; private final ComputerConnector computerConnector; - private final boolean privateIpUsed; - private final boolean alwaysReconnect; - private final Integer idleMinutes; - private final Integer minSize; - private final Integer maxSize; - private final Integer numExecutors; - private final boolean restrictUsage; - private final Integer initOnlineTimeoutSec; - private final Integer initOnlineCheckIntervalSec; - private final Integer cloudStatusIntervalSec; + private boolean privateIpUsed = DEFAULT_PRIVATE_IP_USED; + private boolean alwaysReconnect = DEFAULT_ALWAYS_RECONNECT; + private int idleMinutes = DEFAULT_IDLE_MINUTES; + private int minSize = DEFAULT_MIN_SIZE; + private int maxSize = DEFAULT_MAX_SIZE; + private int numExecutors = DEFAULT_NUM_EXECUTORS; + private boolean restrictUsage = DEFAULT_RESTRICT_USAGE; + private Integer initOnlineTimeoutSec = DEFAULT_INIT_ONLINE_TIMEOUT_SEC; + private Integer initOnlineCheckIntervalSec = DEFAULT_INIT_ONLINE_CHECK_INTERVAL_SEC; + private Integer cloudStatusIntervalSec = DEFAULT_CLOUD_STATUS_INTERVAL_SEC; private final String ec2KeyPairName; /** * @see EC2FleetAutoResubmitComputerLauncher */ - private final boolean disableTaskResubmit; + private boolean disableTaskResubmit; /** * @see NoDelayProvisionStrategy */ - private final boolean noDelayProvision; + private boolean noDelayProvision; private transient Map states; @DataBoundConstructor + public EC2FleetLabelCloud(@Nonnull final String name, + final String region, + final ComputerConnector computerConnector, + final String ec2KeyPairName) { + super(StringUtils.isNotBlank(name) ? name : CloudNames.generateUnique(BASE_DEFAULT_FLEET_CLOUD_ID)); + init(); + this.region = region; + this.computerConnector = computerConnector; + this.ec2KeyPairName = ec2KeyPairName; + } + + @Deprecated public EC2FleetLabelCloud(final String name, final String awsCredentialsId, final String region, @@ -114,7 +121,7 @@ public EC2FleetLabelCloud(final String name, final ComputerConnector computerConnector, final boolean privateIpUsed, final boolean alwaysReconnect, - final Integer idleMinutes, + final int idleMinutes, final Integer minSize, final Integer maxSize, final Integer numExecutors, @@ -125,26 +132,22 @@ public EC2FleetLabelCloud(final String name, final Integer cloudStatusIntervalSec, final boolean noDelayProvision, final String ec2KeyPairName) { - super(StringUtils.isNotBlank(name) ? name : CloudNames.generateUnique(BASE_DEFAULT_FLEET_CLOUD_ID)); - init(); - this.awsCredentialsId = awsCredentialsId; - this.region = region; - this.endpoint = endpoint; - this.fsRoot = fsRoot; - this.computerConnector = computerConnector; - this.idleMinutes = idleMinutes; - this.privateIpUsed = privateIpUsed; - this.alwaysReconnect = alwaysReconnect; - this.minSize = minSize; - this.maxSize = maxSize; - this.numExecutors = numExecutors; - this.restrictUsage = restrictUsage; - this.disableTaskResubmit = disableTaskResubmit; - this.initOnlineTimeoutSec = initOnlineTimeoutSec; - this.initOnlineCheckIntervalSec = initOnlineCheckIntervalSec; - this.cloudStatusIntervalSec = cloudStatusIntervalSec; - this.noDelayProvision = noDelayProvision; - this.ec2KeyPairName = ec2KeyPairName; + this(name, region, computerConnector, ec2KeyPairName); + setAwsCredentialsId(awsCredentialsId); + setEndpoint(endpoint); + setFsRoot(fsRoot); + setPrivateIpUsed(privateIpUsed); + setAlwaysReconnect(alwaysReconnect); + setIdleMinutes(idleMinutes); + setMinSize(minSize); + setMaxSize(maxSize); + setNumExecutors(numExecutors); + setRestrictUsage(restrictUsage); + setDisableTaskResubmit(disableTaskResubmit); + setInitOnlineTimeoutSec(initOnlineTimeoutSec); + setInitOnlineCheckIntervalSec(initOnlineCheckIntervalSec); + setCloudStatusIntervalSec(cloudStatusIntervalSec); + setNoDelayProvision(noDelayProvision); } public String getEc2KeyPairName() { @@ -155,24 +158,54 @@ public boolean isNoDelayProvision() { return noDelayProvision; } + @DataBoundSetter + public void setNoDelayProvision(boolean noDelayProvision) { + this.noDelayProvision = noDelayProvision; + } + public String getAwsCredentialsId() { return awsCredentialsId; } + @DataBoundSetter + public void setAwsCredentialsId(String awsCredentialsId) { + this.awsCredentialsId = awsCredentialsId; + } + public boolean isDisableTaskResubmit() { return disableTaskResubmit; } + @DataBoundSetter + public void setDisableTaskResubmit(boolean disableTaskResubmit) { + this.disableTaskResubmit = disableTaskResubmit; + } + public int getInitOnlineTimeoutSec() { - return initOnlineTimeoutSec == null ? DEFAULT_INIT_ONLINE_TIMEOUT_SEC : initOnlineTimeoutSec; + return initOnlineTimeoutSec; + } + + @DataBoundSetter + public void setInitOnlineTimeoutSec(Integer initOnlineTimeoutSec) { + this.initOnlineTimeoutSec = initOnlineTimeoutSec; } public int getCloudStatusIntervalSec() { - return cloudStatusIntervalSec == null ? DEFAULT_CLOUD_STATUS_INTERVAL_SEC : cloudStatusIntervalSec; + return cloudStatusIntervalSec; + } + + @DataBoundSetter + public void setCloudStatusIntervalSec(Integer cloudStatusIntervalSec) { + this.cloudStatusIntervalSec = cloudStatusIntervalSec; } public int getInitOnlineCheckIntervalSec() { - return initOnlineCheckIntervalSec == null ? DEFAULT_INIT_ONLINE_CHECK_INTERVAL_SEC : initOnlineCheckIntervalSec; + return initOnlineCheckIntervalSec; + } + + @DataBoundSetter + public void setInitOnlineCheckIntervalSec(Integer initOnlineCheckIntervalSec) { + this.initOnlineCheckIntervalSec = initOnlineCheckIntervalSec; } public String getRegion() { @@ -183,10 +216,20 @@ public String getEndpoint() { return endpoint; } + @DataBoundSetter + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + public String getFsRoot() { return fsRoot; } + @DataBoundSetter + public void setFsRoot(String fsRoot) { + this.fsRoot = fsRoot; + } + public ComputerConnector getComputerConnector() { return computerConnector; } @@ -195,26 +238,72 @@ public boolean isPrivateIpUsed() { return privateIpUsed; } + @DataBoundSetter + public void setPrivateIpUsed(boolean privateIpUsed) { + this.privateIpUsed = privateIpUsed; + } + public boolean isAlwaysReconnect() { return alwaysReconnect; } + @DataBoundSetter + public void setAlwaysReconnect(boolean alwaysReconnect) { + this.alwaysReconnect = alwaysReconnect; + } + public int getIdleMinutes() { - return (idleMinutes != null) ? idleMinutes : 0; + return idleMinutes; + } + + @DataBoundSetter + public void setIdleMinutes(int idleMinutes) { + this.idleMinutes = Math.max(0, idleMinutes); } - public Integer getMaxSize() { + public int getMaxSize() { return maxSize; } - public Integer getMinSize() { + @DataBoundSetter + public void setMaxSize(int maxSize) { + if (maxSize < minSize) { + int newMaxSize = Math.max(1, minSize); + warning("Cloud parameter 'maxSize' can't be less than 'minSize' or 1, setting to %d", newMaxSize); + maxSize = newMaxSize; + } + this.maxSize = Math.max(1, maxSize); + } + + public int getMinSize() { return minSize; } - public Integer getNumExecutors() { + @DataBoundSetter + public void setMinSize(int minSize) { + if (minSize < 0) { + warning("Cloud parameter 'minSize' can't be less than 0, setting to 0"); + } + minSize = Math.max(0, minSize); + //TODO: This validation is only in place for unit tests since constructor is run twice on CasC load but not + // for unit tests + if (minSize > maxSize) { + warning("Cloud parameter 'minSize' cannot be greater than 'maxSize', setting 'maxSize' to %d. " + + "Ignore this if caused after a CasC load.", minSize); + this.maxSize = minSize; + } + this.minSize = minSize; + } + + public int getNumExecutors() { return numExecutors; } + @DataBoundSetter + public void setNumExecutors(int numExecutors) { + this.numExecutors = Math.max(numExecutors, 1); + } + public String getJvmSettings() { return ""; } @@ -223,6 +312,11 @@ public boolean isRestrictUsage() { return restrictUsage; } + @DataBoundSetter + public void setRestrictUsage(boolean restrictUsage) { + this.restrictUsage = restrictUsage; + } + @Override public synchronized boolean hasExcessCapacity() { // TODO: Check if the current count of instances are greater than allowed diff --git a/src/main/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/config.jelly b/src/main/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/config.jelly index 87e5af98..733ab87f 100644 --- a/src/main/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/config.jelly +++ b/src/main/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/config.jelly @@ -94,7 +94,7 @@ - + diff --git a/src/test/java/com/amazon/jenkins/ec2fleet/EC2FleetCloudConfigurationAsCodeTest.java b/src/test/java/com/amazon/jenkins/ec2fleet/EC2FleetCloudConfigurationAsCodeTest.java index 2d0dbc23..c2d2cec6 100644 --- a/src/test/java/com/amazon/jenkins/ec2fleet/EC2FleetCloudConfigurationAsCodeTest.java +++ b/src/test/java/com/amazon/jenkins/ec2fleet/EC2FleetCloudConfigurationAsCodeTest.java @@ -61,7 +61,7 @@ public void shouldCreateCloudFromMinConfiguration() { assertEquals(cloud.getLabelString(), null); assertEquals(cloud.getIdleMinutes(), 0); assertEquals(cloud.getMinSize(), 0); - assertEquals(cloud.getMaxSize(), 0); + assertEquals(cloud.getMaxSize(), 1); assertEquals(cloud.getNumExecutors(), 1); assertEquals(cloud.isAddNodeOnlyIfRunning(), false); assertEquals(cloud.isRestrictUsage(), false); @@ -116,4 +116,25 @@ public void configurationWithEmptyName_shouldUseDefault() { assertEquals(("FleetCloud".length() + CloudNames.SUFFIX_LENGTH + 1), cloud.name.length()); } } + + @Test + @ConfiguredWithCode("EC2FleetCloud/credentials-id-configuration-as-code.yml") + public void configurationWithCredentialsId() { + assertEquals(jenkinsRule.jenkins.clouds.size(), 1); + EC2FleetCloud cloud = (EC2FleetCloud) jenkinsRule.jenkins.clouds.getByName("ec2-fleet"); + + assertEquals("ec2-fleet", cloud.name); + assertEquals(cloud.getCredentialsId(), "cred"); + } + + @Test + @ConfiguredWithCode("EC2FleetCloud/max-less-than-min-configuration-as-code.yml") + public void configurationWithMaxSizeLessThanMinSize() { + assertEquals(jenkinsRule.jenkins.clouds.size(), 1); + EC2FleetCloud cloud = (EC2FleetCloud) jenkinsRule.jenkins.clouds.getByName("ec2-fleet"); + + assertEquals("ec2-fleet", cloud.name); + assertEquals(cloud.getMinSize(), 10); + assertEquals(cloud.getMaxSize(), 10); + } } \ No newline at end of file diff --git a/src/test/java/com/amazon/jenkins/ec2fleet/EC2FleetCloudTest.java b/src/test/java/com/amazon/jenkins/ec2fleet/EC2FleetCloudTest.java index 623582e7..539636e0 100644 --- a/src/test/java/com/amazon/jenkins/ec2fleet/EC2FleetCloudTest.java +++ b/src/test/java/com/amazon/jenkins/ec2fleet/EC2FleetCloudTest.java @@ -1958,7 +1958,7 @@ public void getDisplayName_returnDisplayName() { EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "CloudName", null, null, null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 1, true, false, "-1", false , 0, 0, false, 10, false); @@ -1970,7 +1970,7 @@ public void getAwsCredentialsId_returnNull_whenNoCredentialsIdOrAwsCredentialsId EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "TestCloud", null, null, null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 1, true, false, "-1", false, 0, 0, false, 10, false); @@ -1982,7 +1982,7 @@ public void getAwsCredentialsId_returnValue_whenCredentialsIdPresent() { EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "TestCloud", null, "Opa", null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 1, true, false, "-1", false , 0, 0, false, 10, false); @@ -1994,7 +1994,7 @@ public void getAwsCredentialsId_returnValue_whenAwsCredentialsIdPresent() { EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "TestCloud", "Opa", null, null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 1, true, false, "-1", false , 0, 0, false, 10, false); @@ -2006,7 +2006,7 @@ public void getAwsCredentialsId_returnAwsCredentialsId_whenAwsCredentialsIdAndCr EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "TestCloud", "A", "B", null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 1, true, false, "-1", false , 0, 0, false, 10, false); @@ -2020,7 +2020,7 @@ public void getCloudStatusInterval_returnCloudStatusInterval() { EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "CloudName", null, null, null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 1, true, false, "-1", false , 0, 0, false, 45, false); @@ -2032,7 +2032,7 @@ public void create_numExecutorsLessThenOneShouldUpgradedToOne() { EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "CloudName", null, null, null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 0, true, false, "-1", false , 0, 0, false, 45, false); @@ -2045,7 +2045,7 @@ public void hasUnlimitedUsesForNodes_shouldReturnTrueWhenUnlimited() { EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "CloudName", null, null, null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 0, true, false, String.valueOf(maxTotalUses), false , 0, 0, false, 45, false); @@ -2058,7 +2058,7 @@ public void hasUnlimitedUsesForNodes_shouldReturnDefaultTrueForNull() { EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "CloudName", null, null, null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 0, true, false, maxTotalUses, false , 0, 0, false, 45, false); @@ -2071,7 +2071,7 @@ public void hasUnlimitedUsesForNodes_shouldReturnDefaultTrueForEmptyString() { EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "CloudName", null, null, null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 0, true, false, maxTotalUses, false , 0, 0, false, 45, false); @@ -2084,7 +2084,7 @@ public void hasUnlimitedUsesForNodes_shouldReturnFalseWhenLimited() { EC2FleetCloud ec2FleetCloud = new EC2FleetCloud( "CloudName", null, null, null, null, null, null, null, null, false, - false, null, 0, 1, 0, + false, 0, 0, 1, 0, 0, true, false, String.valueOf(maxTotalUses), false , 0, 0, false, 45, false); diff --git a/src/test/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/credentials-id-configuration-as-code.yml b/src/test/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/credentials-id-configuration-as-code.yml new file mode 100644 index 00000000..01371d7e --- /dev/null +++ b/src/test/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/credentials-id-configuration-as-code.yml @@ -0,0 +1,5 @@ +jenkins: + clouds: + - ec2Fleet: + name: ec2-fleet + credentialsId: cred \ No newline at end of file diff --git a/src/test/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/max-less-than-min-configuration-as-code.yml b/src/test/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/max-less-than-min-configuration-as-code.yml new file mode 100644 index 00000000..e60fa107 --- /dev/null +++ b/src/test/resources/com/amazon/jenkins/ec2fleet/EC2FleetCloud/max-less-than-min-configuration-as-code.yml @@ -0,0 +1,6 @@ +jenkins: + clouds: + - ec2Fleet: + name: ec2-fleet + minSize: 10 + maxSize: 0 \ No newline at end of file