From fae79b20a0fbb3cd0d8057c6a2cd1db3a08a38a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20H=C3=A5kansson?= Date: Sun, 29 Sep 2019 17:24:23 +0200 Subject: [PATCH] Support having minimum number of instances only during an active time-range --- .../plugins/ec2/EC2RetentionStrategy.java | 5 +- .../hudson/plugins/ec2/SlaveTemplate.java | 43 ++++ .../ec2/util/MinimumInstanceChecker.java | 51 ++++- ...nimumNumberOfInstancesTimeRangeConfig.java | 90 ++++++++ .../plugins/ec2/SlaveTemplate/config.jelly | 28 +++ ...nimumNumberOfInstancesTimeRangeConfig.html | 5 + .../plugins/ec2/EC2RetentionStrategyTest.java | 215 ++++++++++++++++++ .../hudson/plugins/ec2/SlaveTemplateTest.java | 36 +++ 8 files changed, 468 insertions(+), 5 deletions(-) create mode 100644 src/main/java/hudson/plugins/ec2/util/MinimumNumberOfInstancesTimeRangeConfig.java create mode 100644 src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-minimumNumberOfInstancesTimeRangeConfig.html diff --git a/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java b/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java index aeed7318a..30642932d 100644 --- a/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java +++ b/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java @@ -135,7 +135,10 @@ private long internalCheck(EC2Computer computer) { if (slaveTemplate != null) { long numberOfCurrentInstancesForTemplate = MinimumInstanceChecker.countCurrentNumberOfAgents(slaveTemplate); if (numberOfCurrentInstancesForTemplate > 0 && numberOfCurrentInstancesForTemplate <= slaveTemplate.getMinimumNumberOfInstances()) { - return 1; + //Check if we're in an active time-range for keeping minimum number of instances + if (MinimumInstanceChecker.minimumInstancesActive(slaveTemplate.getMinimumNumberOfInstancesTimeRangeConfig())) { + return 1; + } } } diff --git a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java index 5e7a59e7f..c71d807cf 100644 --- a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java +++ b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java @@ -130,6 +130,8 @@ public class SlaveTemplate implements Describable { private int minimumNumberOfInstances; + private MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig; + public final boolean stopOnTerminate; private final List tags; @@ -508,6 +510,15 @@ public int getMinimumNumberOfInstances() { return minimumNumberOfInstances; } + public MinimumNumberOfInstancesTimeRangeConfig getMinimumNumberOfInstancesTimeRangeConfig() { + return minimumNumberOfInstancesTimeRangeConfig; + } + + @DataBoundSetter + public void setMinimumNumberOfInstancesTimeRangeConfig(MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig) { + this.minimumNumberOfInstancesTimeRangeConfig = minimumNumberOfInstancesTimeRangeConfig; + } + public int getInstanceCap() { return instanceCap; } @@ -1503,6 +1514,38 @@ public FormValidation doCheckMinimumNumberOfInstances(@QueryParameter String val return FormValidation.error("Minimum number of instances must be a non-negative integer (or null)"); } + public FormValidation doCheckMinimumNoInstancesActiveTimeRangeFrom(@QueryParameter String value) { + try { + MinimumNumberOfInstancesTimeRangeConfig.validateLocalTimeString(value); + return FormValidation.ok(); + } catch (IllegalArgumentException e) { + return FormValidation.error("Please enter value in format 'h:mm a' or 'HH:mm'"); + } + } + + public FormValidation doCheckMinimumNoInstancesActiveTimeRangeTo(@QueryParameter String value) { + try { + MinimumNumberOfInstancesTimeRangeConfig.validateLocalTimeString(value); + return FormValidation.ok(); + } catch (IllegalArgumentException e) { + return FormValidation.error("Please enter value in format 'h:mm a' or 'HH:mm'"); + } + } + + // For some reason, all days will validate against this method so no need to repeat for each day. + public FormValidation doCheckMonday(@QueryParameter boolean monday, + @QueryParameter boolean tuesday, + @QueryParameter boolean wednesday, + @QueryParameter boolean thursday, + @QueryParameter boolean friday, + @QueryParameter boolean saturday, + @QueryParameter boolean sunday) { + if (!(monday || tuesday || wednesday || thursday || friday || saturday || sunday)) { + return FormValidation.warning("At least one day should be checked or minimum number of instances won't be active"); + } + return FormValidation.ok(); + } + public FormValidation doCheckInstanceCapStr(@QueryParameter String value) { if (value == null || value.trim().isEmpty()) return FormValidation.ok(); diff --git a/src/main/java/hudson/plugins/ec2/util/MinimumInstanceChecker.java b/src/main/java/hudson/plugins/ec2/util/MinimumInstanceChecker.java index cfe628a49..06bdf8af5 100644 --- a/src/main/java/hudson/plugins/ec2/util/MinimumInstanceChecker.java +++ b/src/main/java/hudson/plugins/ec2/util/MinimumInstanceChecker.java @@ -1,5 +1,6 @@ package hudson.plugins.ec2.util; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.plugins.ec2.EC2Cloud; import hudson.plugins.ec2.EC2Computer; import hudson.plugins.ec2.SlaveTemplate; @@ -8,12 +9,18 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; import javax.annotation.Nonnull; +import java.time.Clock; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Arrays; import java.util.Objects; @Restricted(NoExternalUse.class) public class MinimumInstanceChecker { + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Needs to be overridden from tests") + public static Clock clock = Clock.systemDefaultZone(); + public static int countCurrentNumberOfAgents(@Nonnull SlaveTemplate slaveTemplate) { return (int) Arrays.stream(Jenkins.get().getComputers()).filter(computer -> { if (computer instanceof EC2Computer) { @@ -33,14 +40,50 @@ public static void checkForMinimumInstances() { .forEach(cloud -> { cloud.getTemplates().forEach(slaveTemplate -> { if (slaveTemplate.getMinimumNumberOfInstances() > 0) { - int currentNumberOfSlavesForTemplate = countCurrentNumberOfAgents(slaveTemplate); - int numberToProvision = slaveTemplate.getMinimumNumberOfInstances() + if (minimumInstancesActive(slaveTemplate.getMinimumNumberOfInstancesTimeRangeConfig())) { + int currentNumberOfSlavesForTemplate = countCurrentNumberOfAgents(slaveTemplate); + int numberToProvision = slaveTemplate.getMinimumNumberOfInstances() - currentNumberOfSlavesForTemplate; - if (numberToProvision > 0) { - cloud.provision(slaveTemplate, numberToProvision); + if (numberToProvision > 0) { + cloud.provision(slaveTemplate, numberToProvision); + } } } }); }); } + + public static boolean minimumInstancesActive( + MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig) { + if (minimumNumberOfInstancesTimeRangeConfig == null) { + return true; + } + LocalTime fromTime = minimumNumberOfInstancesTimeRangeConfig.getMinimumNoInstancesActiveTimeRangeFromAsTime(); + LocalTime toTime = minimumNumberOfInstancesTimeRangeConfig.getMinimumNoInstancesActiveTimeRangeToAsTime(); + + LocalDateTime now = LocalDateTime.now(clock); + LocalTime nowTime = LocalTime.from(now); //No date. Easier for comparison on time only. + + boolean passingMidnight = false; + if (toTime.isBefore(fromTime)) { + passingMidnight = true; + } + + if (passingMidnight) { + if (nowTime.isAfter(fromTime)) { + String today = now.getDayOfWeek().name().toLowerCase(); + return minimumNumberOfInstancesTimeRangeConfig.getMinimumNoInstancesActiveTimeRangeDays().get(today); + } else if (nowTime.isBefore(toTime)) { + //We've gone past midnight and want to check yesterday's setting. + String yesterday = now.minusDays(1).getDayOfWeek().name().toLowerCase(); + return minimumNumberOfInstancesTimeRangeConfig.getMinimumNoInstancesActiveTimeRangeDays().get(yesterday); + } + } else { + if (nowTime.isAfter(fromTime) && nowTime.isBefore(toTime)) { + String today = now.getDayOfWeek().name().toLowerCase(); + return minimumNumberOfInstancesTimeRangeConfig.getMinimumNoInstancesActiveTimeRangeDays().get(today); + } + } + return false; + } } diff --git a/src/main/java/hudson/plugins/ec2/util/MinimumNumberOfInstancesTimeRangeConfig.java b/src/main/java/hudson/plugins/ec2/util/MinimumNumberOfInstancesTimeRangeConfig.java new file mode 100644 index 000000000..6e2dc8d79 --- /dev/null +++ b/src/main/java/hudson/plugins/ec2/util/MinimumNumberOfInstancesTimeRangeConfig.java @@ -0,0 +1,90 @@ +package hudson.plugins.ec2.util; + +import net.sf.json.JSONObject; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class MinimumNumberOfInstancesTimeRangeConfig { + + private String minimumNoInstancesActiveTimeRangeFrom; + private String minimumNoInstancesActiveTimeRangeTo; + private Map minimumNoInstancesActiveTimeRangeDays; + + @DataBoundConstructor + public MinimumNumberOfInstancesTimeRangeConfig() { + } + + private static Map parseDays(JSONObject days) { + Map map = new HashMap<>(); + map.put("monday", days.getBoolean("monday")); + map.put("tuesday", days.getBoolean("tuesday")); + map.put("wednesday", days.getBoolean("wednesday")); + map.put("thursday", days.getBoolean("thursday")); + map.put("friday", days.getBoolean("friday")); + map.put("saturday", days.getBoolean("saturday")); + map.put("sunday", days.getBoolean("sunday")); + return map; + } + + private static LocalTime getLocalTime(String value) { + try { + return LocalTime.parse(value, DateTimeFormatter.ofPattern("h:mm a", Locale.ENGLISH)); + } catch (DateTimeParseException e) { + try { + return LocalTime.parse(value, DateTimeFormatter.ofPattern("HH:mm", Locale.ENGLISH)); + } catch (DateTimeParseException ignore) { + } + } + return null; + } + + public static void validateLocalTimeString(String value) { + if (getLocalTime(value) == null) { + throw new IllegalArgumentException("Value " + value + " is not valid time format, ([h:mm a] or [HH:mm])"); + } + } + + public String getMinimumNoInstancesActiveTimeRangeFrom() { + return minimumNoInstancesActiveTimeRangeFrom; + } + + @DataBoundSetter + public void setMinimumNoInstancesActiveTimeRangeFrom(String minimumNoInstancesActiveTimeRangeFrom) { + validateLocalTimeString(minimumNoInstancesActiveTimeRangeFrom); + this.minimumNoInstancesActiveTimeRangeFrom = minimumNoInstancesActiveTimeRangeFrom; + } + + public LocalTime getMinimumNoInstancesActiveTimeRangeFromAsTime() { + return getLocalTime(minimumNoInstancesActiveTimeRangeFrom); + } + + public String getMinimumNoInstancesActiveTimeRangeTo() { + return minimumNoInstancesActiveTimeRangeTo; + } + + @DataBoundSetter + public void setMinimumNoInstancesActiveTimeRangeTo(String minimumNoInstancesActiveTimeRangeTo) { + validateLocalTimeString(minimumNoInstancesActiveTimeRangeTo); + this.minimumNoInstancesActiveTimeRangeTo = minimumNoInstancesActiveTimeRangeTo; + } + + public LocalTime getMinimumNoInstancesActiveTimeRangeToAsTime() { + return getLocalTime(minimumNoInstancesActiveTimeRangeTo); + } + + public Map getMinimumNoInstancesActiveTimeRangeDays() { + return minimumNoInstancesActiveTimeRangeDays; + } + + @DataBoundSetter + public void setMinimumNoInstancesActiveTimeRangeDays(JSONObject minimumNoInstancesActiveTimeRangeDays) { + this.minimumNoInstancesActiveTimeRangeDays = parseDays(minimumNoInstancesActiveTimeRangeDays); + } +} diff --git a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly index 8c6fe48fa..0c48a085c 100644 --- a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly +++ b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly @@ -142,6 +142,34 @@ THE SOFTWARE. + + + From: + To: + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-minimumNumberOfInstancesTimeRangeConfig.html b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-minimumNumberOfInstancesTimeRangeConfig.html new file mode 100644 index 000000000..226a01f95 --- /dev/null +++ b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-minimumNumberOfInstancesTimeRangeConfig.html @@ -0,0 +1,5 @@ +
+ Choose the timespan and on which days a minimum number of instances should be kept alive. If the To-time is before the From-time, the time range will wrap around to the next day. + + E.g. From 23:00 to 06:00 with Monday selected would mean that the instance(s) would be kept up from 23:00 on Monday evening until 06:00 Tuesday morning. +
diff --git a/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java b/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java index 15d8559fb..2f90e7ecd 100644 --- a/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java @@ -4,17 +4,22 @@ import com.amazonaws.services.ec2.model.InstanceType; import hudson.plugins.ec2.util.AmazonEC2FactoryMockImpl; +import hudson.plugins.ec2.util.MinimumInstanceChecker; +import hudson.plugins.ec2.util.MinimumNumberOfInstancesTimeRangeConfig; import hudson.plugins.ec2.util.PrivateKeyHelper; import hudson.slaves.NodeProperty; import hudson.model.Executor; import hudson.model.Node; +import net.sf.json.JSONObject; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; import java.time.Clock; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.Month; import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; @@ -256,4 +261,214 @@ public void testRetentionDespiteIdleWithMinimumInstances() throws Exception { assertEquals(2, computers.size()); assertEquals(2, AmazonEC2FactoryMockImpl.instances.size()); } + + @Test + public void testRetentionDespiteIdleWithMinimumInstanceActiveTimeRange() throws Exception { + SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", + InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, + "-Xmx1g", false, "subnet 456", null, null, 2, "10", null, true, true, false, "", false, "", false, false, + true, ConnectionStrategy.PRIVATE_IP, 0); + + MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig = new MinimumNumberOfInstancesTimeRangeConfig(); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeFrom("11:00"); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeTo("15:00"); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("monday", false); + jsonObject.put("tuesday", true); + jsonObject.put("wednesday", false); + jsonObject.put("thursday", false); + jsonObject.put("friday", false); + jsonObject.put("saturday", false); + jsonObject.put("sunday", false); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeDays(jsonObject); + template.setMinimumNumberOfInstancesTimeRangeConfig(minimumNumberOfInstancesTimeRangeConfig); + + LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 24, 12, 0); //Tuesday + + //Set fixed clock to be able to test properly + MinimumInstanceChecker.clock = Clock.fixed(localDateTime.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); + + AmazonEC2Cloud cloud = new AmazonEC2Cloud("us-east-1", true, "abc", "us-east-1", PrivateKeyHelper.generate(), "3", + Collections + .singletonList(template), "roleArn", "roleSessionName"); + r.jenkins.clouds.add(cloud); + r.configRoundtrip(); + + List computers = Arrays.stream(r.jenkins.getComputers()) + .filter(computer -> computer instanceof EC2Computer) + .map(computer -> (EC2Computer) computer) + .collect(Collectors.toList()); + + // Should have two slaves before any checking + assertEquals(2, computers.size()); + + Instant now = Instant.now(); + Clock clock = Clock.fixed(now, zoneId); + EC2RetentionStrategy rs = new EC2RetentionStrategy("-2", clock, now.toEpochMilli() - 1); + rs.check(computers.get(0)); + + computers = Arrays.stream(r.jenkins.getComputers()) + .filter(computer -> computer instanceof EC2Computer) + .map(computer -> (EC2Computer) computer) + .collect(Collectors.toList()); + + // Should have two slaves after check too + assertEquals(2, computers.size()); + assertEquals(2, AmazonEC2FactoryMockImpl.instances.size()); + + } + + @Test + public void testRetentionIdleWithMinimumInstanceInactiveTimeRange() throws Exception { + SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", + InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, + "-Xmx1g", false, "subnet 456", null, null, 2, "10", null, true, true, false, "", false, "", false, false, + true, ConnectionStrategy.PRIVATE_IP, 0); + + MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig = new MinimumNumberOfInstancesTimeRangeConfig(); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeFrom("11:00"); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeTo("15:00"); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("monday", false); + jsonObject.put("tuesday", true); + jsonObject.put("wednesday", false); + jsonObject.put("thursday", false); + jsonObject.put("friday", false); + jsonObject.put("saturday", false); + jsonObject.put("sunday", false); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeDays(jsonObject); + template.setMinimumNumberOfInstancesTimeRangeConfig(minimumNumberOfInstancesTimeRangeConfig); + + LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 24, 10, 0); //Tuesday before range + + //Set fixed clock to be able to test properly + MinimumInstanceChecker.clock = Clock.fixed(localDateTime.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); + + AmazonEC2Cloud cloud = new AmazonEC2Cloud("us-east-1", true, "abc", "us-east-1", PrivateKeyHelper.generate(), "3", + Collections + .singletonList(template), "roleArn", "roleSessionName"); + r.jenkins.clouds.add(cloud); + r.configRoundtrip(); + + List computers = Arrays.stream(r.jenkins.getComputers()) + .filter(computer -> computer instanceof EC2Computer) + .map(computer -> (EC2Computer) computer) + .collect(Collectors.toList()); + + // Should have zero slaves + assertEquals(0, computers.size()); + } + + @Test + public void testRetentionDespiteIdleWithMinimumInstanceActiveTimeRangeAfterMidnight() throws Exception { + SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", + InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, + "-Xmx1g", false, "subnet 456", null, null, 2, "10", null, true, true, false, "", false, "", false, false, + true, ConnectionStrategy.PRIVATE_IP, 0); + + MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig = new MinimumNumberOfInstancesTimeRangeConfig(); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeFrom("15:00"); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeTo("03:00"); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("monday", false); + jsonObject.put("tuesday", true); + jsonObject.put("wednesday", false); + jsonObject.put("thursday", false); + jsonObject.put("friday", false); + jsonObject.put("saturday", false); + jsonObject.put("sunday", false); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeDays(jsonObject); + template.setMinimumNumberOfInstancesTimeRangeConfig(minimumNumberOfInstancesTimeRangeConfig); + + LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 25, 1, 0); //Wednesday + + //Set fixed clock to be able to test properly + MinimumInstanceChecker.clock = Clock.fixed(localDateTime.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); + + AmazonEC2Cloud cloud = new AmazonEC2Cloud("us-east-1", true, "abc", "us-east-1", PrivateKeyHelper.generate(), "3", + Collections + .singletonList(template), "roleArn", "roleSessionName"); + r.jenkins.clouds.add(cloud); + r.configRoundtrip(); + + List computers = Arrays.stream(r.jenkins.getComputers()) + .filter(computer -> computer instanceof EC2Computer) + .map(computer -> (EC2Computer) computer) + .collect(Collectors.toList()); + + // Should have two slaves before any checking + assertEquals(2, computers.size()); + + Instant now = Instant.now(); + Clock clock = Clock.fixed(now, zoneId); + EC2RetentionStrategy rs = new EC2RetentionStrategy("-2", clock, now.toEpochMilli() - 1); + rs.check(computers.get(0)); + + computers = Arrays.stream(r.jenkins.getComputers()) + .filter(computer -> computer instanceof EC2Computer) + .map(computer -> (EC2Computer) computer) + .collect(Collectors.toList()); + + // Should have two slaves after check too + assertEquals(2, computers.size()); + assertEquals(2, AmazonEC2FactoryMockImpl.instances.size()); + } + + @Test + public void testRetentionStopsAfterActiveRangeEnds() throws Exception { + SlaveTemplate template = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, null, "default", "foo", + InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, + "-Xmx1g", false, "subnet 456", null, null, 2, "10", null, true, true, false, "", false, "", false, false, + true, ConnectionStrategy.PRIVATE_IP, 0); + + MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig = new MinimumNumberOfInstancesTimeRangeConfig(); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeFrom("11:00"); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeTo("15:00"); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("monday", false); + jsonObject.put("tuesday", true); + jsonObject.put("wednesday", false); + jsonObject.put("thursday", false); + jsonObject.put("friday", false); + jsonObject.put("saturday", false); + jsonObject.put("sunday", false); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeDays(jsonObject); + template.setMinimumNumberOfInstancesTimeRangeConfig(minimumNumberOfInstancesTimeRangeConfig); + + //Set fixed clock to be able to test properly + LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 24, 14, 0); //Tuesday + MinimumInstanceChecker.clock = Clock.fixed(localDateTime.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); + + AmazonEC2Cloud cloud = new AmazonEC2Cloud("us-east-1", true, "abc", "us-east-1", PrivateKeyHelper.generate(), "3", + Collections + .singletonList(template), "roleArn", "roleSessionName"); + r.jenkins.clouds.add(cloud); + r.configRoundtrip(); + + List computers = Arrays.stream(r.jenkins.getComputers()) + .filter(computer -> computer instanceof EC2Computer) + .map(computer -> (EC2Computer) computer) + .collect(Collectors.toList()); + + // Should have two slaves before any checking + assertEquals(2, computers.size()); + + //Set fixed clock to after active period + localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 24, 16, 0); //Tuesday + MinimumInstanceChecker.clock = Clock.fixed(localDateTime.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); + + Instant now = Instant.now(); + Clock clock = Clock.fixed(now, zoneId); + EC2RetentionStrategy rs = new EC2RetentionStrategy("-2", clock, now.toEpochMilli() - 1); + rs.check(computers.get(0)); + + computers = Arrays.stream(r.jenkins.getComputers()) + .filter(computer -> computer instanceof EC2Computer) + .map(computer -> (EC2Computer) computer) + .collect(Collectors.toList()); + + // Should have 1 slaves after check + assertEquals(1, computers.size()); + assertEquals(1, AmazonEC2FactoryMockImpl.instances.size()); + } } diff --git a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java index c9c04bec5..e487b79be 100644 --- a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java +++ b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java @@ -25,8 +25,11 @@ import com.amazonaws.services.ec2.model.InstanceType; import hudson.model.Node; +import hudson.plugins.ec2.util.MinimumNumberOfInstancesTimeRangeConfig; import jenkins.model.Jenkins; +import net.sf.json.JSONObject; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -351,4 +354,37 @@ public void testConnectionStrategyDeprecatedFieldsAreExported() { assertThat(exported, containsString("usePrivateDnsName")); assertThat(exported, containsString("connectUsingPublicIp")); } + + @Test + public void testMinimumNumberOfInstancesActiveRangeConfig() throws Exception { + MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig = new MinimumNumberOfInstancesTimeRangeConfig(); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeFrom("11:00"); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeTo("15:00"); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("monday", false); + jsonObject.put("tuesday", true); + jsonObject.put("wednesday", false); + jsonObject.put("thursday", false); + jsonObject.put("friday", false); + jsonObject.put("saturday", false); + jsonObject.put("sunday", false); + minimumNumberOfInstancesTimeRangeConfig.setMinimumNoInstancesActiveTimeRangeDays(jsonObject); + SlaveTemplate slaveTemplate = new SlaveTemplate("ami1", EC2AbstractSlave.TEST_ZONE, new SpotConfiguration(true, "22", true, "1"), "default", "foo", InstanceType.M1Large, false, "ttt", Node.Mode.NORMAL, "foo ami", "bar", "bbb", "aaa", "10", "fff", null, "-Xmx1g", false, "subnet 456", null, null, 2, null, null, true, true, false, "", false, "", false, false, true, ConnectionStrategy.PRIVATE_IP, 0); + slaveTemplate.setMinimumNumberOfInstancesTimeRangeConfig(minimumNumberOfInstancesTimeRangeConfig); + + List templates = new ArrayList(); + templates.add(slaveTemplate); + + AmazonEC2Cloud ac = new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", "3", templates, null, null); + r.jenkins.clouds.add(ac); + + r.configRoundtrip(); + + MinimumNumberOfInstancesTimeRangeConfig stored = r.jenkins.clouds.get(AmazonEC2Cloud.class).getTemplates().get(0).getMinimumNumberOfInstancesTimeRangeConfig(); + Assert.assertNotNull(stored); + Assert.assertEquals("11:00", stored.getMinimumNoInstancesActiveTimeRangeFrom()); + Assert.assertEquals("15:00", stored.getMinimumNoInstancesActiveTimeRangeTo()); + Assert.assertEquals(false, stored.getMinimumNoInstancesActiveTimeRangeDays().get("monday")); + Assert.assertEquals(true, stored.getMinimumNoInstancesActiveTimeRangeDays().get("tuesday")); + } }