diff --git a/plugin/src/main/java/jenkins/plugins/openstack/compute/JCloudsSlaveTemplate.java b/plugin/src/main/java/jenkins/plugins/openstack/compute/JCloudsSlaveTemplate.java index fe64d238e..6d4dbba06 100644 --- a/plugin/src/main/java/jenkins/plugins/openstack/compute/JCloudsSlaveTemplate.java +++ b/plugin/src/main/java/jenkins/plugins/openstack/compute/JCloudsSlaveTemplate.java @@ -124,7 +124,7 @@ private Object readResolve() { lf = LauncherFactory.JNLP.JNLP; } - BootSource.Image bs = imageId == null ? null : new BootSource.Image(imageId); + BootSource.Image bs = imageId == null ? null : new BootSource.Image(imageId, null, false); slaveOptions = SlaveOptions.builder().bootSource(bs).hardwareId(hardwareId).numExecutors(Integer.getInteger(numExecutors)).jvmOptions(jvmOptions).userDataId(userDataId) .fsRoot(fsRoot).retentionTime(overrideRetentionTime).keyPairName(keyPairName).networkId(networkId).securityGroups(securityGroups) .launcherFactory(lf).availabilityZone(availabilityZone).build() diff --git a/plugin/src/main/java/jenkins/plugins/openstack/compute/SlaveOptions.java b/plugin/src/main/java/jenkins/plugins/openstack/compute/SlaveOptions.java index 7270b4c13..46ccc11ea 100644 --- a/plugin/src/main/java/jenkins/plugins/openstack/compute/SlaveOptions.java +++ b/plugin/src/main/java/jenkins/plugins/openstack/compute/SlaveOptions.java @@ -228,7 +228,7 @@ public SlaveOptions( private Object readResolve() { if (bootSource == null && imageId != null) { - bootSource = new BootSource.Image(imageId); + bootSource = new BootSource.Image(imageId, null, false); } imageId = null; return this; diff --git a/plugin/src/main/java/jenkins/plugins/openstack/compute/slaveopts/BootSource.java b/plugin/src/main/java/jenkins/plugins/openstack/compute/slaveopts/BootSource.java index 7b7cd4710..911aef922 100644 --- a/plugin/src/main/java/jenkins/plugins/openstack/compute/slaveopts/BootSource.java +++ b/plugin/src/main/java/jenkins/plugins/openstack/compute/slaveopts/BootSource.java @@ -54,6 +54,7 @@ import org.openstack4j.model.compute.builder.ServerCreateBuilder; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; @@ -132,9 +133,10 @@ public List getAuthFieldsOffsets() { return Arrays.asList("../..", "../../.."); } - protected ListBoxModel makeListBoxModelOfAllNames(String existingValue, String endPointUrl, boolean ignoreSsl, String credentialsId, String zone) { + protected ListBoxModel makeListBoxModelOfAllNames(String existingValue, String endPointUrl, boolean ignoreSsl, String credentialsId, String zone, String nameFilterRegexp) { ListBoxModel m = new ListBoxModel(); final String valueOrEmpty = Util.fixNull(existingValue); + final String filter = Util.fixEmptyAndTrim(nameFilterRegexp); OpenstackCredential openstackCredential = OpenstackCredentials.getCredential(credentialsId); m.add(new ListBoxModel.Option("None specified", "", valueOrEmpty.isEmpty())); try { @@ -142,6 +144,11 @@ protected ListBoxModel makeListBoxModelOfAllNames(String existingValue, String e final Openstack openstack = Openstack.Factory.get(endPointUrl, ignoreSsl, openstackCredential, zone); final List values = listAllNames(openstack); for (String value : values) { + if (filter != null) { + if (!value.matches(filter)){ + continue; + } + } final String displayText = value; m.add(displayText, value); } @@ -216,11 +223,17 @@ public static class Image extends BootSource { private static final String OPENSTACK_BOOTSOURCE_IMAGE_ID_KEY = "jenkins-boot-image-id"; protected final @Nonnull String name; + protected final @Nullable String nameFilterRegexp; + protected final boolean overrideNotFoundImage; + + protected String imageId; @DataBoundConstructor - public Image(@Nonnull String name) { + public Image(@Nonnull String name, @Nullable String nameFilterRegexp, boolean overrideNotFoundImage) { Objects.requireNonNull(name, "Image name missing"); this.name = name; + this.nameFilterRegexp = Util.fixEmptyAndTrim(nameFilterRegexp); + this.overrideNotFoundImage = overrideNotFoundImage; } @Nonnull @@ -228,16 +241,42 @@ public String getName() { return name; } + public String getNameFilterRegexp() { + return nameFilterRegexp; + } + + public boolean getOverrideNotFoundImage() { + return overrideNotFoundImage; + } + @Override public void setServerBootSource( @Nonnull ServerCreateBuilder builder, @Nonnull Openstack os ) throws JCloudsCloud.ProvisioningFailedException { super.setServerBootSource(builder, os); - final List matchingIds = getDescriptor().findMatchingIds(os, name); - final String id = selectIdFromListAndLogProblems(matchingIds, name, "Images"); + List matchingIds = getDescriptor().findMatchingIds(os, name); + if (matchingIds.size() == 0 && overrideNotFoundImage) { + String overrideName = name; + final List values = getDescriptor().listAllNames(os); + for (String value : values) { + if (nameFilterRegexp != null) { + if (value.matches(nameFilterRegexp)){ + overrideName = value; + LOGGER.warning("Image " + name + "not found, overriding with " + overrideName); + break; + } + } + } + matchingIds = getDescriptor().findMatchingIds(os, overrideName); + imageId = selectIdFromListAndLogProblems(matchingIds, overrideName, "Images"); + + } else { + imageId = selectIdFromListAndLogProblems(matchingIds, name, "Images"); + + } + builder.image(imageId); + builder.addMetadataItem(OPENSTACK_BOOTSOURCE_IMAGE_ID_KEY, imageId); - builder.image(id); - builder.addMetadataItem(OPENSTACK_BOOTSOURCE_IMAGE_ID_KEY, id); } @Override @@ -287,8 +326,9 @@ public ListBoxModel doFillNameItems(@QueryParameter String name, @QueryParameter String endPointUrl, @QueryParameter boolean ignoreSsl, @QueryParameter String credentialsId, - @QueryParameter String zone) { - return makeListBoxModelOfAllNames(name, endPointUrl, ignoreSsl, credentialsId, zone); + @QueryParameter String zone, + @QueryParameter String nameFilterRegexp) { + return makeListBoxModelOfAllNames(name, endPointUrl, ignoreSsl, credentialsId, zone, nameFilterRegexp); } @Restricted(DoNotUse.class) @@ -319,8 +359,8 @@ public int getVolumeSize() { } @DataBoundConstructor - public VolumeFromImage(@Nonnull String name, int volumeSize) { - super(name); + public VolumeFromImage(@Nonnull String name, int volumeSize, @Nullable String nameFilterRegexp, boolean overrideNotFoundImage) { + super(name, nameFilterRegexp, overrideNotFoundImage); if (volumeSize <= 0) throw new IllegalArgumentException("Volume size must be positive, got " + volumeSize); this.volumeSize = volumeSize; } @@ -328,19 +368,17 @@ public VolumeFromImage(@Nonnull String name, int volumeSize) { @Override public void setServerBootSource(@Nonnull ServerCreateBuilder builder, @Nonnull Openstack os) throws JCloudsCloud.ProvisioningFailedException { super.setServerBootSource(builder, os); - final List matchingIds = getDescriptor().findMatchingIds(os, name); - final String id = selectIdFromListAndLogProblems(matchingIds, name, "Images"); final BlockDeviceMappingBuilder volumeBuilder = Builders.blockDeviceMapping() .sourceType(BDMSourceType.IMAGE) .destinationType(BDMDestType.VOLUME) - .uuid(id) + .uuid(imageId) .volumeSize(volumeSize) .deleteOnTermination(true) .bootIndex(0) ; builder.blockDevice(volumeBuilder.build()); - builder.addMetadataItem(OPENSTACK_BOOTSOURCE_VOLUME_FROM_IMAGE_ID_KEY, id); + builder.addMetadataItem(OPENSTACK_BOOTSOURCE_VOLUME_FROM_IMAGE_ID_KEY, imageId); } @Override @@ -380,11 +418,15 @@ public static final class VolumeSnapshot extends BootSource { private static final String OPENSTACK_BOOTSOURCE_VOLUMESNAPSHOT_DESC_KEY = "jenkins-boot-volumesnapshot-description"; private final @Nonnull String name; + protected final @Nullable String nameFilterRegexp; + protected final boolean overrideNotFoundImage; @DataBoundConstructor - public VolumeSnapshot(@Nonnull String name) { + public VolumeSnapshot(@Nonnull String name, @Nullable String nameFilterRegexp, boolean overrideNotFoundImage) { Objects.requireNonNull(name, "Volume snapshot name missing"); this.name = name; + this.nameFilterRegexp = Util.fixEmptyAndTrim(nameFilterRegexp); + this.overrideNotFoundImage = overrideNotFoundImage; } @Nonnull @@ -395,8 +437,25 @@ public String getName() { @Override public void setServerBootSource(@Nonnull ServerCreateBuilder builder, @Nonnull Openstack os) { super.setServerBootSource(builder, os); - final List matchingIds = getDescriptor().findMatchingIds(os, name); - final String id = selectIdFromListAndLogProblems(matchingIds, name, "VolumeSnapshots"); + List matchingIds = getDescriptor().findMatchingIds(os, name); + final String id; + if (matchingIds.size() == 0 && overrideNotFoundImage) { + String overrideName = name; + final List values = getDescriptor().listAllNames(os); + for (String value : values) { + if (nameFilterRegexp != null) { + if (value.matches(nameFilterRegexp)){ + overrideName = value; + LOGGER.warning("Volume snapshot " + name + "not found, overriding with " + overrideName); + break; + } + } + } + matchingIds = getDescriptor().findMatchingIds(os, overrideName); + id = selectIdFromListAndLogProblems(matchingIds, overrideName, "VolumeSnapshots"); + } else { + id = selectIdFromListAndLogProblems(matchingIds, name, "VolumeSnapshots"); + } String volumeSnapshotDescriptionOrNull = null; try { volumeSnapshotDescriptionOrNull = os.getVolumeSnapshotDescription(id); @@ -499,8 +558,8 @@ public List listAllNames(Openstack openstack) { @Restricted(DoNotUse.class) @OsAuthDescriptor.InjectOsAuth @RequirePOST - public ListBoxModel doFillNameItems(@QueryParameter String name, @QueryParameter String endPointUrl, @QueryParameter boolean ignoreSsl, @QueryParameter String credentialsId, @QueryParameter String zone) { - return makeListBoxModelOfAllNames(name, endPointUrl, ignoreSsl, credentialsId, zone); + public ListBoxModel doFillNameItems(@QueryParameter String name, @QueryParameter String endPointUrl, @QueryParameter boolean ignoreSsl, @QueryParameter String credentialsId, @QueryParameter String zone, @QueryParameter String nameFilterRegexp) { + return makeListBoxModelOfAllNames(name, endPointUrl, ignoreSsl, credentialsId, zone, nameFilterRegexp); } @Restricted(DoNotUse.class) diff --git a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/config.jelly b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/config.jelly index d1652fcfe..f63980f79 100644 --- a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/config.jelly +++ b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/config.jelly @@ -3,4 +3,10 @@ + + + + + + diff --git a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/help-nameFilterRegexp.html b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/help-nameFilterRegexp.html new file mode 100644 index 000000000..0372419e4 --- /dev/null +++ b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/help-nameFilterRegexp.html @@ -0,0 +1,5 @@ +
+ Filter to list available image names based on RegExp. +
+ The name list will be populated with the names of images that matches provided RegExp. +
diff --git a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/help-overrideNotFoundImage.html b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/help-overrideNotFoundImage.html new file mode 100644 index 000000000..f781afd20 --- /dev/null +++ b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/Image/help-overrideNotFoundImage.html @@ -0,0 +1,5 @@ +
+ Override selected name, if not found or no longer available on server. +
+ Note: If the filter RegExp is ambiguous, i.e. there are multiple images matches filter regexp, then first matched will be used. +
diff --git a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/config.jelly b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/config.jelly index b75aea3fb..2d6b20680 100644 --- a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/config.jelly +++ b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/config.jelly @@ -6,4 +6,10 @@ + + + + + + diff --git a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/help-nameFilterRegexp.html b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/help-nameFilterRegexp.html new file mode 100644 index 000000000..0372419e4 --- /dev/null +++ b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/help-nameFilterRegexp.html @@ -0,0 +1,5 @@ +
+ Filter to list available image names based on RegExp. +
+ The name list will be populated with the names of images that matches provided RegExp. +
diff --git a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/help-overrideNotFoundImage.html b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/help-overrideNotFoundImage.html new file mode 100644 index 000000000..f781afd20 --- /dev/null +++ b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeFromImage/help-overrideNotFoundImage.html @@ -0,0 +1,5 @@ +
+ Override selected name, if not found or no longer available on server. +
+ Note: If the filter RegExp is ambiguous, i.e. there are multiple images matches filter regexp, then first matched will be used. +
diff --git a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/config.jelly b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/config.jelly index d1652fcfe..0ec36911e 100644 --- a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/config.jelly +++ b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/config.jelly @@ -3,4 +3,10 @@ + + + + + + diff --git a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/help-nameFilterRegexp.html b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/help-nameFilterRegexp.html new file mode 100644 index 000000000..0372419e4 --- /dev/null +++ b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/help-nameFilterRegexp.html @@ -0,0 +1,5 @@ +
+ Filter to list available image names based on RegExp. +
+ The name list will be populated with the names of images that matches provided RegExp. +
diff --git a/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/help-overrideNotFoundImage.html b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/help-overrideNotFoundImage.html new file mode 100644 index 000000000..f781afd20 --- /dev/null +++ b/plugin/src/main/resources/jenkins/plugins/openstack/compute/slaveopts/BootSource/VolumeSnapshot/help-overrideNotFoundImage.html @@ -0,0 +1,5 @@ +
+ Override selected name, if not found or no longer available on server. +
+ Note: If the filter RegExp is ambiguous, i.e. there are multiple images matches filter regexp, then first matched will be used. +
diff --git a/plugin/src/test/java/jenkins/plugins/openstack/PluginTestRule.java b/plugin/src/test/java/jenkins/plugins/openstack/PluginTestRule.java index 9b5dafb59..04ce20ca9 100644 --- a/plugin/src/test/java/jenkins/plugins/openstack/PluginTestRule.java +++ b/plugin/src/test/java/jenkins/plugins/openstack/PluginTestRule.java @@ -123,7 +123,7 @@ public static SlaveOptions dummySlaveOptions() { dummyUserData("dummyUserDataId"); } return new SlaveOptions( - new BootSource.VolumeSnapshot("id"), "hw", "nw1,mw2", "dummyUserDataId", 1, 2, "pool", "sg", "az", 1, null, 10, + new BootSource.VolumeSnapshot("id", null, false), "hw", "nw1,mw2", "dummyUserDataId", 1, 2, "pool", "sg", "az", 1, null, 10, "jvmo", "fsRoot", LauncherFactory.JNLP.JNLP, mkListOfNodeProperties(1, 2), 1, null ); } @@ -173,7 +173,7 @@ public SlaveOptions defaultSlaveOptions() { // Use some real-looking values preserving defaults to make sure plugin works with them return JCloudsCloud.DescriptorImpl.getDefaultOptions().getBuilder() - .bootSource(new BootSource.Image("dummyImageId")) + .bootSource(new BootSource.Image("dummyImageId", null, false)) .hardwareId("dummyHardwareId") .networkId("dummyNetworkId") .userDataId("dummyUserDataId") diff --git a/plugin/src/test/java/jenkins/plugins/openstack/compute/JCloudsCloudTest.java b/plugin/src/test/java/jenkins/plugins/openstack/compute/JCloudsCloudTest.java index 757393b22..72929752f 100644 --- a/plugin/src/test/java/jenkins/plugins/openstack/compute/JCloudsCloudTest.java +++ b/plugin/src/test/java/jenkins/plugins/openstack/compute/JCloudsCloudTest.java @@ -152,10 +152,10 @@ public void presentUIDefaults() throws Exception { String openstackAuth = j.dummyCredentials(); JCloudsSlaveTemplate template = new JCloudsSlaveTemplate("template", "label", new SlaveOptions( - new BootSource.Image("iid"), "hw", "nw", "ud", 1, 0, "public", "sg", "az", 2, "kp", 3, "jvmo", "fsRoot", LauncherFactory.JNLP.JNLP, null, 4, false + new BootSource.Image("iid", null, false), "hw", "nw", "ud", 1, 0, "public", "sg", "az", 2, "kp", 3, "jvmo", "fsRoot", LauncherFactory.JNLP.JNLP, null, 4, false )); JCloudsCloud cloud = new JCloudsCloud("openstack", "endPointUrl", false,"zone", new SlaveOptions( - new BootSource.VolumeSnapshot("vsid"), "HW", "NW", "UD", 6, 4, null, "SG", "AZ", 7, "KP", 8, "JVMO", "FSrOOT", new LauncherFactory.SSH("cid"), null, 9, false + new BootSource.VolumeSnapshot("vsid", null, false), "HW", "NW", "UD", 6, 4, null, "SG", "AZ", 7, "KP", 8, "JVMO", "FSrOOT", new LauncherFactory.SSH("cid"), null, 9, false ), Collections.singletonList(template),openstackAuth); j.jenkins.clouds.add(cloud); diff --git a/plugin/src/test/java/jenkins/plugins/openstack/compute/JCloudsSlaveTemplateTest.java b/plugin/src/test/java/jenkins/plugins/openstack/compute/JCloudsSlaveTemplateTest.java index 9d568d27e..a6ef73d17 100644 --- a/plugin/src/test/java/jenkins/plugins/openstack/compute/JCloudsSlaveTemplateTest.java +++ b/plugin/src/test/java/jenkins/plugins/openstack/compute/JCloudsSlaveTemplateTest.java @@ -109,7 +109,7 @@ public void configRoundtrip() throws Exception { @Test public void eraseDefaults() { SlaveOptions cloudOpts = dummySlaveOptions(); // Make sure nothing collides with defaults - SlaveOptions templateOpts = cloudOpts.getBuilder().bootSource(new BootSource.Image("id")).availabilityZone("other").build(); + SlaveOptions templateOpts = cloudOpts.getBuilder().bootSource(new BootSource.Image("id", null, false)).availabilityZone("other").build(); assertEquals(cloudOpts.getHardwareId(), templateOpts.getHardwareId()); JCloudsSlaveTemplate template = new JCloudsSlaveTemplate( @@ -124,7 +124,7 @@ public void eraseDefaults() { ); assertEquals(cloudOpts, cloud.getRawSlaveOptions()); - assertEquals(SlaveOptions.builder().bootSource(new BootSource.Image("id")).availabilityZone("other").build(), template.getRawSlaveOptions()); + assertEquals(SlaveOptions.builder().bootSource(new BootSource.Image("id", null, false)).availabilityZone("other").build(), template.getRawSlaveOptions()); } @Test @@ -189,7 +189,7 @@ public void bootWithMultipleNetworks() { public void bootFromVolumeSnapshot() { final String volumeSnapshotName = "MyVolumeSnapshot"; final String volumeSnapshotId = "vs-123-id"; - final SlaveOptions opts = dummySlaveOptions().getBuilder().bootSource(new VolumeSnapshot(volumeSnapshotName)).build(); + final SlaveOptions opts = dummySlaveOptions().getBuilder().bootSource(new VolumeSnapshot(volumeSnapshotName, null, false)).build(); final JCloudsSlaveTemplate instance = j.dummySlaveTemplate(opts, "a"); final JCloudsCloud cloud = j.configureSlaveProvisioningWithFloatingIP(j.dummyCloud(instance)); final Openstack mockOs = cloud.getOpenstack(); @@ -206,7 +206,7 @@ public void bootFromVolumeSnapshotDoesNotNPEIfCantFindVolumeSnapshot() { */ final String volumeSnapshotName = "MyNonexistentVolumeSnapshot"; final String volumeSnapshotId = volumeSnapshotName; - final SlaveOptions opts = dummySlaveOptions().getBuilder().bootSource(new VolumeSnapshot(volumeSnapshotName)).build(); + final SlaveOptions opts = dummySlaveOptions().getBuilder().bootSource(new VolumeSnapshot(volumeSnapshotName, null, false)).build(); final JCloudsSlaveTemplate instance = j.dummySlaveTemplate(opts, "b"); final JCloudsCloud cloud = j.configureSlaveProvisioningWithFloatingIP(j.dummyCloud(instance)); final Openstack mockOs = cloud.getOpenstack(); @@ -230,7 +230,7 @@ public void bootFromVolumeSnapshotStillPossibleEvenIfCantSetVolumeNameAndDescrip */ final String volumeSnapshotName = "MyOtherVolumeSnapshot"; final String volumeSnapshotId = "vs-345-id"; - final SlaveOptions opts = dummySlaveOptions().getBuilder().bootSource(new VolumeSnapshot(volumeSnapshotName)).build(); + final SlaveOptions opts = dummySlaveOptions().getBuilder().bootSource(new VolumeSnapshot(volumeSnapshotName, null, false)).build(); final JCloudsSlaveTemplate instance = j.dummySlaveTemplate(opts, "b"); final JCloudsCloud cloud = j.configureSlaveProvisioningWithFloatingIP(j.dummyCloud(instance)); final Openstack mockOs = cloud.getOpenstack(); @@ -278,7 +278,7 @@ private NovaBlockDeviceMappingCreate getBlockDeviceMapping(ServerCreateBuilder s @Test public void bootFromImageVolume() { - final SlaveOptions opts = dummySlaveOptions().getBuilder().bootSource(new BootSource.VolumeFromImage("src_img_id", 42)).build(); + final SlaveOptions opts = dummySlaveOptions().getBuilder().bootSource(new BootSource.VolumeFromImage("src_img_id", 42, null, false)).build(); final JCloudsSlaveTemplate template = j.dummySlaveTemplate(opts, "label"); final JCloudsCloud cloud = j.configureSlaveProvisioningWithFloatingIP(j.dummyCloud(template)); final Openstack os = cloud.getOpenstack(); @@ -297,9 +297,34 @@ public void bootFromImageVolume() { assertThat(blockDeviceMapping.volume_size, equalTo(42)); } + @Test + public void bootFromImageVolumeImageNotFoundAndOverrideByFilter() { + final SlaveOptions opts = dummySlaveOptions().getBuilder().bootSource(new BootSource.VolumeFromImage("src_img_id", 42, "default.*", true)).build(); + final JCloudsSlaveTemplate template = j.dummySlaveTemplate(opts, "label"); + final JCloudsCloud cloud = j.configureSlaveProvisioningWithFloatingIP(j.dummyCloud(template)); + final Openstack os = cloud.getOpenstack(); + + when(os.getImageIdsFor("src_img_id")).thenReturn(Collections.EMPTY_LIST); + when(os.getImageIdsFor("default_image")).thenReturn(Collections.singletonList("default_image")); + when(os.getImages()).thenReturn(Collections.singletonMap("default_image", null)); + + template.provisionServer(null, null); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ServerCreateBuilder.class); + verify(os, times(1)).bootAndWaitActive(captor.capture(), any(Integer.class)); + NovaBlockDeviceMappingCreate blockDeviceMapping = getBlockDeviceMapping(captor.getValue()); + + assertThat(blockDeviceMapping.boot_index, equalTo(0)); + assertThat(blockDeviceMapping.delete_on_termination, equalTo(true)); + assertThat(blockDeviceMapping.uuid, equalTo("default_image")); + assertThat(blockDeviceMapping.source_type, equalTo(BDMSourceType.IMAGE)); + assertThat(blockDeviceMapping.destination_type, equalTo(BDMDestType.VOLUME)); + assertThat(blockDeviceMapping.volume_size, equalTo(42)); + } + @Test public void allowToUseImageNameAsWellAsId() throws Exception { - SlaveOptions opts = j.defaultSlaveOptions().getBuilder().bootSource(new BootSource.Image("image-id")).build(); + SlaveOptions opts = j.defaultSlaveOptions().getBuilder().bootSource(new BootSource.Image("image-id", null, false)).build(); JCloudsCloud cloud = j.configureSlaveLaunchingWithFloatingIP(j.dummyCloud(j.dummySlaveTemplate(opts, "label"))); Openstack os = cloud.getOpenstack(); @@ -319,7 +344,7 @@ public void allowToUseImageNameAsWellAsId() throws Exception { @Test public void allowToUseVolumeSnapshotNameAsWellAsId() throws Exception { - SlaveOptions opts = j.defaultSlaveOptions().getBuilder().bootSource(new VolumeSnapshot("vs-id")).build(); + SlaveOptions opts = j.defaultSlaveOptions().getBuilder().bootSource(new VolumeSnapshot("vs-id", "vs-.*", true)).build(); JCloudsCloud cloud = j.configureSlaveLaunchingWithFloatingIP(j.dummyCloud(j.dummySlaveTemplate(opts, "label"))); Openstack os = cloud.getOpenstack(); diff --git a/plugin/src/test/java/jenkins/plugins/openstack/compute/SlaveOptionsTest.java b/plugin/src/test/java/jenkins/plugins/openstack/compute/SlaveOptionsTest.java index 038c25cce..bb31743db 100644 --- a/plugin/src/test/java/jenkins/plugins/openstack/compute/SlaveOptionsTest.java +++ b/plugin/src/test/java/jenkins/plugins/openstack/compute/SlaveOptionsTest.java @@ -23,7 +23,7 @@ public void defaultOverrides() { SlaveOptions dummySlaveOptions = PluginTestRule.dummySlaveOptions(); SlaveOptions unmodified = dummySlaveOptions.override(SlaveOptions.empty()); - assertEquals(new BootSource.VolumeSnapshot("id"), unmodified.getBootSource()); + assertEquals(new BootSource.VolumeSnapshot("id", null, false), unmodified.getBootSource()); assertEquals("hw", unmodified.getHardwareId()); assertEquals("nw1,mw2", unmodified.getNetworkId()); assertEquals("dummyUserDataId", unmodified.getUserDataId()); @@ -42,7 +42,7 @@ public void defaultOverrides() { assertEquals(1, (int) unmodified.getRetentionTime()); SlaveOptions override = SlaveOptions.builder() - .bootSource(new BootSource.Image("iid")) + .bootSource(new BootSource.Image("iid", null, false)) .hardwareId("HW") .networkId("NW") .userDataId("UD") @@ -63,7 +63,7 @@ public void defaultOverrides() { ; SlaveOptions overridden = PluginTestRule.dummySlaveOptions().override(override); - assertEquals(new BootSource.Image("iid"), overridden.getBootSource()); + assertEquals(new BootSource.Image("iid", null, false), overridden.getBootSource()); assertEquals("HW", overridden.getHardwareId()); assertEquals("NW", overridden.getNetworkId()); assertEquals("UD", overridden.getUserDataId()); @@ -84,8 +84,8 @@ public void defaultOverrides() { @Test public void eraseDefaults() { - SlaveOptions defaults = SlaveOptions.builder().bootSource(new BootSource.Image("ID")).hardwareId("hw").networkId(null).floatingIpPool("a").build(); - SlaveOptions configured = SlaveOptions.builder().bootSource(new BootSource.Image("ID")).hardwareId("hw").networkId("MW").floatingIpPool("A").build(); + SlaveOptions defaults = SlaveOptions.builder().bootSource(new BootSource.Image("ID", null, false)).hardwareId("hw").networkId(null).floatingIpPool("a").build(); + SlaveOptions configured = SlaveOptions.builder().bootSource(new BootSource.Image("ID", null, false)).hardwareId("hw").networkId("MW").floatingIpPool("A").build(); SlaveOptions actual = configured.eraseDefaults(defaults); diff --git a/plugin/src/test/java/jenkins/plugins/openstack/compute/slaveopts/BootSourceTest.java b/plugin/src/test/java/jenkins/plugins/openstack/compute/slaveopts/BootSourceTest.java index c4675ac06..cdd6f6fae 100644 --- a/plugin/src/test/java/jenkins/plugins/openstack/compute/slaveopts/BootSourceTest.java +++ b/plugin/src/test/java/jenkins/plugins/openstack/compute/slaveopts/BootSourceTest.java @@ -41,7 +41,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -71,28 +73,28 @@ public void before() { @Test public void constructorInvariants() { try { - new BootSource.Image(null); + new BootSource.Image(null, null, false); fail(); } catch (NullPointerException e) { assertThat(e.getMessage(), containsString("Image name missing")); } try { - new BootSource.VolumeSnapshot(null); + new BootSource.VolumeSnapshot(null, null, false); fail(); } catch (NullPointerException e) { assertThat(e.getMessage(), containsString("Volume snapshot name missing")); } try { - new BootSource.VolumeFromImage(null, 1); + new BootSource.VolumeFromImage(null, 1, null, false); fail(); } catch (NullPointerException e) { assertThat(e.getMessage(), containsString("Image name missing")); } try { - new BootSource.VolumeFromImage("foo", 0); + new BootSource.VolumeFromImage("foo", 0, null, false); fail(); } catch (IllegalArgumentException e) { assertThat(e.getMessage(), containsString("Volume size must be positive, got 0")); @@ -111,7 +113,34 @@ public void doFillImageNameItemsPopulatesImageNamesNotIds() { doReturn(Collections.singletonMap(imageName, Collections.singletonList(image))).when(os).getImages(); - ListBoxModel list = id.doFillNameItems("", "OSurl", false, credentialsId, "OSzone"); + ListBoxModel list = id.doFillNameItems("", "OSurl", false, credentialsId, "OSzone", null); + assertEquals(2, list.size()); + assertEquals("First menu entry is 'nothing selected'", "", list.get(0).value); + ListBoxModel.Option item = list.get(1); + assertEquals("menu item name", imageName, item.name); + assertEquals("menu item value", imageName, item.value); + } + + @Test + public void doFillImageNameItemsPopulatesImageNamesWithFilter() { + Image image = mock(Image.class); + when(image.getId()).thenReturn("image-id"); + final String imageName = "image-name"; + when(image.getName()).thenReturn(imageName); + + Image imageFiltered = mock(Image.class); + when(imageFiltered.getId()).thenReturn("filtered-id"); + final String imageFilteredName = "filtered-name"; + when(imageFiltered.getName()).thenReturn(imageFilteredName); + + Openstack os = j.fakeOpenstackFactory(); + final String credentialsId = j.dummyCredentials(); + Map images = new HashMap(); + images.put(imageName, Collections.singletonList(image)); + images.put(imageFilteredName, Collections.singletonList(imageFiltered)); + doReturn(images).when(os).getImages(); + + ListBoxModel list = id.doFillNameItems("", "OSurl", false, credentialsId, "OSzone", "image-.*"); assertEquals(2, list.size()); assertEquals("First menu entry is 'nothing selected'", "", list.get(0).value); ListBoxModel.Option item = list.get(1); @@ -130,7 +159,7 @@ public void doFillSnapshotNameItemsPopulatesVolumeSnapshotNames() { Openstack os = j.fakeOpenstackFactory(); when(os.getVolumeSnapshots()).thenReturn(Collections.singletonMap("vs-name", Collections.singletonList(volumeSnapshot))); - ListBoxModel list = vsd.doFillNameItems("existing-vs-name", "OSurl", false, credentialsId, "OSzone"); + ListBoxModel list = vsd.doFillNameItems("existing-vs-name", "OSurl", false, credentialsId, "OSzone", null); assertEquals(3, list.size()); assertEquals("First menu entry is 'nothing selected'", "", list.get(0).value); assertEquals("Second menu entry is the VS OpenStack can see", "vs-name", list.get(1).name); @@ -139,6 +168,34 @@ public void doFillSnapshotNameItemsPopulatesVolumeSnapshotNames() { assertEquals("Third menu entry is the existing value", "existing-vs-name", list.get(2).value); } + @Test + public void doFillSnapshotNameItemsPopulatesVolumeSnapshotNamesWithFilter() { + VolumeSnapshot volumeSnapshot = mock(VolumeSnapshot.class); + when(volumeSnapshot.getId()).thenReturn("vs-id"); + when(volumeSnapshot.getName()).thenReturn("vs-name"); + when(volumeSnapshot.getStatus()).thenReturn(Volume.Status.AVAILABLE); + + VolumeSnapshot volumeSnapshotFiltered = mock(VolumeSnapshot.class); + when(volumeSnapshotFiltered.getId()).thenReturn("vs-filtered-id"); + when(volumeSnapshotFiltered.getName()).thenReturn("vs-filtered-name"); + when(volumeSnapshotFiltered.getStatus()).thenReturn(Volume.Status.AVAILABLE); + final String credentialsId = j.dummyCredentials(); + + Openstack os = j.fakeOpenstackFactory(); + Map> volumes = new HashMap(); + volumes.put("vs-name", Collections.singletonList(volumeSnapshot)); + volumes.put("vs-filtered-name", Collections.singletonList(volumeSnapshot)); + when(os.getVolumeSnapshots()).thenReturn(volumes); + + ListBoxModel list = vsd.doFillNameItems("existing-vs-name", "OSurl", false, credentialsId, "OSzone", "vs-filtered.*"); + assertEquals(3, list.size()); + assertEquals("First menu entry is 'nothing selected'", "", list.get(0).value); + assertEquals("Second menu entry is the VS OpenStack can see", "vs-filtered-name", list.get(1).name); + assertEquals("Second menu entry is the VS OpenStack can see", "vs-filtered-name", list.get(1).value); + assertEquals("Third menu entry is the existing value", "existing-vs-name", list.get(2).name); + assertEquals("Third menu entry is the existing value", "existing-vs-name", list.get(2).value); + } + @Test @Issue("JENKINS-29993") public void doFillImageIdItemsAcceptsNullAsImageName() { Image image = mock(Image.class); @@ -153,7 +210,7 @@ public void doFillImageIdItemsAcceptsNullAsImageName() { j.fakeOpenstackFactory(new Openstack(osClient)); final String credentialsId = j.dummyCredentials(); - ListBoxModel list = id.doFillNameItems("", "OSurl", false, credentialsId, "OSzone"); + ListBoxModel list = id.doFillNameItems("", "OSurl", false, credentialsId, "OSzone", null); assertThat(list.get(0).name, list, Matchers.iterableWithSize(2)); assertEquals(2, list.size()); ListBoxModel.Option item = list.get(1);