diff --git a/README.md b/README.md index a9492c135..b461c8638 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,7 @@ # Introduction Allow Jenkins to start agents on -[EC2](http://aws.amazon.com/ec2/) or -[Eucalyptus](https://www.eucalyptus.cloud/) on demand, and +[EC2](http://aws.amazon.com/ec2/) on demand, and kill them as they get unused. With this plugin, if Jenkins notices that your build cluster is @@ -299,7 +298,7 @@ import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl import com.cloudbees.plugins.credentials.* import com.cloudbees.plugins.credentials.domains.Domain import hudson.model.* -import hudson.plugins.ec2.AmazonEC2Cloud +import hudson.plugins.ec2.EC2Cloud import hudson.plugins.ec2.AMITypeData import hudson.plugins.ec2.EC2Tag import hudson.plugins.ec2.SlaveTemplate @@ -363,7 +362,7 @@ def slaveTemplateUsEast1Parameters = [ nodeProperties: null ] -def AmazonEC2CloudParameters = [ +def EC2CloudParameters = [ name: 'MyCompany', credentialsId: 'jenkins-aws-key', instanceCapStr: '2', @@ -462,14 +461,14 @@ SlaveTemplate slaveTemplateUsEast1 = new SlaveTemplate( slaveTemplateUsEast1Parameters.metadataHopsLimit, ) -// https://javadoc.jenkins.io/plugin/ec2/index.html?hudson/plugins/ec2/AmazonEC2Cloud.html -AmazonEC2Cloud amazonEC2Cloud = new AmazonEC2Cloud( - AmazonEC2CloudParameters.name, - AmazonEC2CloudParameters.useInstanceProfileForCredentials, - AmazonEC2CloudParameters.credentialsId, - AmazonEC2CloudParameters.region, - AmazonEC2CloudParameters.privateKey, - AmazonEC2CloudParameters.instanceCapStr, +// https://javadoc.jenkins.io/plugin/ec2/hudson/plugins/ec2/EC2Cloud.html +EC2Cloud ec2Cloud = new EC2Cloud( + EC2CloudParameters.name, + EC2CloudParameters.useInstanceProfileForCredentials, + EC2CloudParameters.credentialsId, + EC2CloudParameters.region, + EC2CloudParameters.privateKey, + EC2CloudParameters.instanceCapStr, [slaveTemplateUsEast1], '', '' @@ -488,7 +487,7 @@ def store = jenkins.getExtensionList('com.cloudbees.plugins.credentials.SystemCr store.addCredentials(domain, aWSCredentialsImpl) // add cloud configuration to Jenkins -jenkins.clouds.add(amazonEC2Cloud) +jenkins.clouds.add(ec2Cloud) // save current Jenkins state to disk jenkins.save() @@ -504,7 +503,7 @@ Example: ```java // Assuming on the Jenkins instance, there exists an EC2Cloud with the name "AwsCloud" - AmazonEC2Cloud cloud = (AmazonEC2Cloud) Jenkins.get().clouds.stream().filter(cloud1 -> Objects.equals(cloud.getDisplayName(), "AwsCloud")).findFirst().get(); + EC2Cloud cloud = (EC2Cloud) Jenkins.get().clouds.stream().filter(cloud1 -> Objects.equals(cloud.getDisplayName(), "AwsCloud")).findFirst().get(); SlaveTemplate template = new SlaveTemplate(/*constructor*/); // View available constructors at https://github.com/jenkinsci/ec2-plugin/blob/master/src/main/java/hudson/plugins/ec2/SlaveTemplate.java diff --git a/src/main/java/hudson/plugins/ec2/AmazonEC2Cloud.java b/src/main/java/hudson/plugins/ec2/AmazonEC2Cloud.java index 950fc104b..31083accd 100644 --- a/src/main/java/hudson/plugins/ec2/AmazonEC2Cloud.java +++ b/src/main/java/hudson/plugins/ec2/AmazonEC2Cloud.java @@ -23,53 +23,13 @@ */ package hudson.plugins.ec2; -import com.amazonaws.SdkClientException; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.DescribeRegionsResult; -import com.amazonaws.services.ec2.model.Region; -import edu.umd.cs.findbugs.annotations.Nullable; -import hudson.Extension; -import hudson.Util; -import hudson.model.Failure; -import hudson.model.ItemGroup; -import hudson.plugins.ec2.util.AmazonEC2Factory; -import hudson.util.FormValidation; -import hudson.util.ListBoxModel; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; import java.util.List; -import java.util.Locale; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.servlet.ServletException; -import jenkins.model.Jenkins; -import org.kohsuke.stapler.AncestorInPath; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.interceptor.RequirePOST; -import org.kohsuke.stapler.verb.POST; /** - * The original implementation of {@link EC2Cloud}. - * - * @author Kohsuke Kawaguchi + * @deprecated use {@link EC2Cloud} */ +@Deprecated public class AmazonEC2Cloud extends EC2Cloud { - private static final Logger LOGGER = Logger.getLogger(AmazonEC2Cloud.class.getName()); - - /** - * Represents the region. Can be null for backward compatibility reasons. - */ - private String region; - - private String altEC2Endpoint; - - private boolean noDelayProvisioning; - - @DataBoundConstructor public AmazonEC2Cloud( String name, boolean useInstanceProfileForCredentials, @@ -85,16 +45,15 @@ public AmazonEC2Cloud( name, useInstanceProfileForCredentials, credentialsId, + region, privateKey, sshKeysCredentialsId, instanceCapStr, templates, roleArn, roleSessionName); - this.region = region; } - @Deprecated public AmazonEC2Cloud( String name, boolean useInstanceProfileForCredentials, @@ -109,185 +68,11 @@ public AmazonEC2Cloud( name, useInstanceProfileForCredentials, credentialsId, + region, privateKey, instanceCapStr, templates, roleArn, roleSessionName); - this.region = region; - } - - /** - * @deprecated Use public field "name" instead. - */ - @Deprecated - public String getCloudName() { - return name; - } - - public String getRegion() { - if (region == null) { - region = DEFAULT_EC2_HOST; // Backward compatibility - } - // Handles pre 1.14 region names that used the old AwsRegion enum, note we don't change - // the region here to keep the meta-data compatible in the case of a downgrade (is that right?) - if (region.indexOf('_') > 0) { - return region.replace('_', '-').toLowerCase(Locale.ENGLISH); - } - return region; - } - - public static URL getEc2EndpointUrl(String region) { - try { - return new URL("https://" + getAwsPartitionHostForService(region, "ec2")); - } catch (MalformedURLException e) { - throw new Error(e); // Impossible - } - } - - @Override - public URL getEc2EndpointUrl() { - return getEc2EndpointUrl(getRegion()); - } - - @Override - public URL getS3EndpointUrl() { - try { - return new URL("https://" + getAwsPartitionHostForService(getRegion(), "s3") + "/"); - } catch (MalformedURLException e) { - throw new Error(e); // Impossible - } - } - - public boolean isNoDelayProvisioning() { - return noDelayProvisioning; - } - - @DataBoundSetter - public void setNoDelayProvisioning(boolean noDelayProvisioning) { - this.noDelayProvisioning = noDelayProvisioning; - } - - public String getAltEC2Endpoint() { - return altEC2Endpoint; - } - - @DataBoundSetter - public void setAltEC2Endpoint(String altEC2Endpoint) { - this.altEC2Endpoint = altEC2Endpoint; - } - - @Override - protected AWSCredentialsProvider createCredentialsProvider() { - return createCredentialsProvider( - isUseInstanceProfileForCredentials(), - getCredentialsId(), - getRoleArn(), - getRoleSessionName(), - getRegion()); - } - - @Extension - public static class DescriptorImpl extends EC2Cloud.DescriptorImpl { - - @Override - public String getDisplayName() { - return "Amazon EC2"; - } - - @POST - public FormValidation doCheckCloudName(@QueryParameter String value) { - if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - return FormValidation.ok(); - } - try { - Jenkins.checkGoodName(value); - } catch (Failure e) { - return FormValidation.error(e.getMessage()); - } - return FormValidation.ok(); - } - - @POST - public FormValidation doCheckAltEC2Endpoint(@QueryParameter String value) { - if (Util.fixEmpty(value) != null) { - try { - new URL(value); - } catch (MalformedURLException ignored) { - return FormValidation.error(Messages.AmazonEC2Cloud_MalformedUrl()); - } - } - return FormValidation.ok(); - } - - @RequirePOST - public ListBoxModel doFillRegionItems( - @QueryParameter String altEC2Endpoint, - @QueryParameter boolean useInstanceProfileForCredentials, - @QueryParameter String credentialsId) - throws IOException, ServletException { - ListBoxModel model = new ListBoxModel(); - if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - try { - AWSCredentialsProvider credentialsProvider = - createCredentialsProvider(useInstanceProfileForCredentials, credentialsId); - AmazonEC2 client = AmazonEC2Factory.getInstance() - .connect(credentialsProvider, determineEC2EndpointURL(altEC2Endpoint)); - DescribeRegionsResult regions = client.describeRegions(); - List regionList = regions.getRegions(); - for (Region r : regionList) { - String name = r.getRegionName(); - model.add(name, name); - } - } catch (SdkClientException ex) { - // Ignore, as this may happen before the credentials are specified - } - } - return model; - } - - // Will use the alternate EC2 endpoint if provided by the UI (via a @QueryParameter field), or use the default - // value if not specified. - // VisibleForTesting - URL determineEC2EndpointURL(@Nullable String altEC2Endpoint) throws MalformedURLException { - if (Util.fixEmpty(altEC2Endpoint) == null) { - return new URL(DEFAULT_EC2_ENDPOINT); - } - try { - return new URL(altEC2Endpoint); - } catch (MalformedURLException e) { - LOGGER.log( - Level.WARNING, - "The alternate EC2 endpoint is malformed ({0}). Using the default endpoint ({1})", - new Object[] {altEC2Endpoint, DEFAULT_EC2_ENDPOINT}); - return new URL(DEFAULT_EC2_ENDPOINT); - } - } - - @RequirePOST - public FormValidation doTestConnection( - @AncestorInPath ItemGroup context, - @QueryParameter String region, - @QueryParameter boolean useInstanceProfileForCredentials, - @QueryParameter String credentialsId, - @QueryParameter String sshKeysCredentialsId, - @QueryParameter String roleArn, - @QueryParameter String roleSessionName) - throws IOException, ServletException { - - if (Util.fixEmpty(region) == null) { - region = DEFAULT_EC2_HOST; - } - - return super.doTestConnection( - context, - getEc2EndpointUrl(region), - useInstanceProfileForCredentials, - credentialsId, - sshKeysCredentialsId, - roleArn, - roleSessionName, - region); - } } } diff --git a/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java b/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java index 618f6d65b..45dc4d9f9 100644 --- a/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java +++ b/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java @@ -1041,8 +1041,8 @@ public static ListBoxModel fillZoneItems(AWSCredentialsProvider credentialsProvi ListBoxModel model = new ListBoxModel(); if (!StringUtils.isEmpty(region)) { - AmazonEC2 client = AmazonEC2Factory.getInstance() - .connect(credentialsProvider, AmazonEC2Cloud.getEc2EndpointUrl(region)); + AmazonEC2 client = + AmazonEC2Factory.getInstance().connect(credentialsProvider, EC2Cloud.getEc2EndpointUrl(region)); DescribeAvailabilityZonesResult zones = client.describeAvailabilityZones(); List zoneList = zones.getAvailabilityZones(); model.add("", ""); diff --git a/src/main/java/hudson/plugins/ec2/EC2Cloud.java b/src/main/java/hudson/plugins/ec2/EC2Cloud.java index b929a6637..e0163c61c 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Cloud.java +++ b/src/main/java/hudson/plugins/ec2/EC2Cloud.java @@ -21,6 +21,7 @@ import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.ClientConfiguration; +import com.amazonaws.SdkClientException; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.AWSStaticCredentialsProvider; @@ -30,6 +31,7 @@ import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; import com.amazonaws.services.ec2.model.DescribeInstancesResult; +import com.amazonaws.services.ec2.model.DescribeRegionsResult; import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest; import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsResult; import com.amazonaws.services.ec2.model.Filter; @@ -38,6 +40,7 @@ import com.amazonaws.services.ec2.model.InstanceType; import com.amazonaws.services.ec2.model.KeyPair; import com.amazonaws.services.ec2.model.KeyPairInfo; +import com.amazonaws.services.ec2.model.Region; import com.amazonaws.services.ec2.model.Reservation; import com.amazonaws.services.ec2.model.SpotInstanceRequest; import com.amazonaws.services.ec2.model.Tag; @@ -61,11 +64,13 @@ import com.google.common.annotations.VisibleForTesting; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import hudson.Extension; import hudson.ProxyConfiguration; import hudson.Util; import hudson.model.Computer; import hudson.model.Descriptor; +import hudson.model.Failure; import hudson.model.ItemGroup; import hudson.model.Label; import hudson.model.Node; @@ -96,6 +101,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -112,9 +118,12 @@ import jenkins.model.Jenkins; import jenkins.model.JenkinsLocationConfiguration; import org.apache.commons.lang.StringUtils; +import org.jenkinsci.Symbol; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.AncestorInPath; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; @@ -127,7 +136,7 @@ * * @author Kohsuke Kawaguchi */ -public abstract class EC2Cloud extends Cloud { +public class EC2Cloud extends Cloud { private static final Logger LOGGER = Logger.getLogger(EC2Cloud.class.getName()); @@ -189,12 +198,23 @@ public abstract class EC2Cloud extends Cloud { private transient KeyPair usableKeyPair; + /** + * Represents the region. Can be null for backward compatibility reasons. + */ + private String region; + + private String altEC2Endpoint; + + private boolean noDelayProvisioning; + private transient volatile AmazonEC2 connection; - protected EC2Cloud( + @DataBoundConstructor + public EC2Cloud( String name, boolean useInstanceProfileForCredentials, String credentialsId, + String region, String privateKey, String sshKeysCredentialsId, String instanceCapStr, @@ -206,6 +226,7 @@ protected EC2Cloud( this.roleArn = roleArn; this.roleSessionName = roleSessionName; this.credentialsId = Util.fixEmpty(credentialsId); + this.region = Util.fixEmpty(region); this.sshKeysCredentialsId = Util.fixEmpty(sshKeysCredentialsId); if (templates == null) { @@ -223,6 +244,30 @@ protected EC2Cloud( readResolve(); // set parents } + @Deprecated + public EC2Cloud( + String name, + boolean useInstanceProfileForCredentials, + String credentialsId, + String region, + String privateKey, + String instanceCapStr, + List templates, + String roleArn, + String roleSessionName) { + this( + name, + useInstanceProfileForCredentials, + credentialsId, + region, + privateKey, + null, + instanceCapStr, + templates, + roleArn, + roleSessionName); + } + @Deprecated protected EC2Cloud( String id, @@ -239,6 +284,7 @@ protected EC2Cloud( credentialsId, privateKey, null, + null, instanceCapStr, templates, roleArn, @@ -260,9 +306,63 @@ public EC2PrivateKey resolvePrivateKey() { return null; } - public abstract URL getEc2EndpointUrl() throws IOException; + /** + * @deprecated Use public field "name" instead. + */ + @Deprecated + public String getCloudName() { + return name; + } + + public String getRegion() { + if (region == null) { + region = DEFAULT_EC2_HOST; // Backward compatibility + } + // Handles pre 1.14 region names that used the old AwsRegion enum, note we don't change + // the region here to keep the meta-data compatible in the case of a downgrade (is that right?) + if (region.indexOf('_') > 0) { + return region.replace('_', '-').toLowerCase(Locale.ENGLISH); + } + return region; + } + + public static URL getEc2EndpointUrl(String region) { + try { + return new URL("https://" + getAwsPartitionHostForService(region, "ec2")); + } catch (MalformedURLException e) { + throw new Error(e); // Impossible + } + } + + public URL getEc2EndpointUrl() { + return getEc2EndpointUrl(getRegion()); + } + + public URL getS3EndpointUrl() { + try { + return new URL("https://" + getAwsPartitionHostForService(getRegion(), "s3") + "/"); + } catch (MalformedURLException e) { + throw new Error(e); // Impossible + } + } + + public boolean isNoDelayProvisioning() { + return noDelayProvisioning; + } + + @DataBoundSetter + public void setNoDelayProvisioning(boolean noDelayProvisioning) { + this.noDelayProvisioning = noDelayProvisioning; + } + + public String getAltEC2Endpoint() { + return altEC2Endpoint; + } - public abstract URL getS3EndpointUrl() throws IOException; + @DataBoundSetter + public void setAltEC2Endpoint(String altEC2Endpoint) { + this.altEC2Endpoint = altEC2Endpoint; + } public void addTemplate(SlaveTemplate newTemplate) throws Exception { String newTemplateDescription = newTemplate.description; @@ -636,15 +736,9 @@ private int countCurrentEC2SpotSlaves(SlaveTemplate template, String jenkinsServ DescribeSpotInstanceRequestsResult sirResp = null; do { - try { - sirResp = connect().describeSpotInstanceRequests(dsir); - sirs = sirResp.getSpotInstanceRequests(); - dsir.setNextToken(sirResp.getNextToken()); - } catch (Exception ex) { - // Some ec2 implementations don't implement spot requests (Eucalyptus) - LOGGER.log(Level.FINEST, "Describe spot instance requests failed", ex); - break; - } + sirResp = connect().describeSpotInstanceRequests(dsir); + sirs = sirResp.getSpotInstanceRequests(); + dsir.setNextToken(sirResp.getNextToken()); if (sirs != null) { for (SpotInstanceRequest sir : sirs) { @@ -1075,7 +1169,12 @@ public boolean canProvision(Label label) { } protected AWSCredentialsProvider createCredentialsProvider() { - return createCredentialsProvider(useInstanceProfileForCredentials, credentialsId); + return createCredentialsProvider( + isUseInstanceProfileForCredentials(), + getCredentialsId(), + getRoleArn(), + getRoleSessionName(), + getRegion()); } public static String getSlaveTypeTagValue(String slaveType, String templateDescription) { @@ -1254,7 +1353,14 @@ private static SSHUserPrivateKey getSshCredential(String id, ItemGroup context) return credential; } - public abstract static class DescriptorImpl extends Descriptor { + @Extension + @Symbol("amazonEC2") + public static class DescriptorImpl extends Descriptor { + + @Override + public String getDisplayName() { + return "Amazon EC2"; + } public InstanceType[] getInstanceTypes() { return InstanceType.values(); @@ -1385,27 +1491,25 @@ public FormValidation doCheckSshKeysCredentialsId( * Tests the connection settings. * * Overriding needs to {@code @RequirePOST} - * @param ec2endpoint + * @param region * @param useInstanceProfileForCredentials * @param credentialsId * @param sshKeysCredentialsId * @param roleArn * @param roleSessionName - * @param region * @return the validation result * @throws IOException * @throws ServletException */ - @POST - protected FormValidation doTestConnection( + @RequirePOST + public FormValidation doTestConnection( @AncestorInPath ItemGroup context, - URL ec2endpoint, - boolean useInstanceProfileForCredentials, - String credentialsId, - String sshKeysCredentialsId, - String roleArn, - String roleSessionName, - String region) + @QueryParameter String region, + @QueryParameter boolean useInstanceProfileForCredentials, + @QueryParameter String credentialsId, + @QueryParameter String sshKeysCredentialsId, + @QueryParameter String roleArn, + @QueryParameter String roleSessionName) throws IOException, ServletException { if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { return FormValidation.ok(); @@ -1439,9 +1543,13 @@ protected FormValidation doTestConnection( } LOGGER.fine(() -> "private key found ok"); + if (Util.fixEmpty(region) == null) { + region = DEFAULT_EC2_HOST; + } + AWSCredentialsProvider credentialsProvider = createCredentialsProvider( useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); - AmazonEC2 ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, ec2endpoint); + AmazonEC2 ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, getEc2EndpointUrl(region)); ec2.describeInstances(); if (!privateKey.trim().isEmpty()) { @@ -1484,6 +1592,75 @@ public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context) Collections.emptyList(), CredentialsMatchers.always()); } + + @POST + public FormValidation doCheckCloudName(@QueryParameter String value) { + if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { + return FormValidation.ok(); + } + try { + Jenkins.checkGoodName(value); + } catch (Failure e) { + return FormValidation.error(e.getMessage()); + } + return FormValidation.ok(); + } + + @POST + public FormValidation doCheckAltEC2Endpoint(@QueryParameter String value) { + if (Util.fixEmpty(value) != null) { + try { + new URL(value); + } catch (MalformedURLException ignored) { + return FormValidation.error(Messages.EC2Cloud_MalformedUrl()); + } + } + return FormValidation.ok(); + } + + @RequirePOST + public ListBoxModel doFillRegionItems( + @QueryParameter String altEC2Endpoint, + @QueryParameter boolean useInstanceProfileForCredentials, + @QueryParameter String credentialsId) + throws IOException, ServletException { + ListBoxModel model = new ListBoxModel(); + if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { + try { + AWSCredentialsProvider credentialsProvider = + createCredentialsProvider(useInstanceProfileForCredentials, credentialsId); + AmazonEC2 client = AmazonEC2Factory.getInstance() + .connect(credentialsProvider, determineEC2EndpointURL(altEC2Endpoint)); + DescribeRegionsResult regions = client.describeRegions(); + List regionList = regions.getRegions(); + for (Region r : regionList) { + String name = r.getRegionName(); + model.add(name, name); + } + } catch (SdkClientException ex) { + // Ignore, as this may happen before the credentials are specified + } + } + return model; + } + + // Will use the alternate EC2 endpoint if provided by the UI (via a @QueryParameter field), or use the default + // value if not specified. + // VisibleForTesting + URL determineEC2EndpointURL(@Nullable String altEC2Endpoint) throws MalformedURLException { + if (Util.fixEmpty(altEC2Endpoint) == null) { + return new URL(DEFAULT_EC2_ENDPOINT); + } + try { + return new URL(altEC2Endpoint); + } catch (MalformedURLException e) { + LOGGER.log( + Level.WARNING, + "The alternate EC2 endpoint is malformed ({0}). Using the default endpoint ({1})", + new Object[] {altEC2Endpoint, DEFAULT_EC2_ENDPOINT}); + return new URL(DEFAULT_EC2_ENDPOINT); + } + } } public static void log(Logger logger, Level level, TaskListener listener, String message) { diff --git a/src/main/java/hudson/plugins/ec2/EC2Step.java b/src/main/java/hudson/plugins/ec2/EC2Step.java index 57135242c..86580abe9 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Step.java +++ b/src/main/java/hudson/plugins/ec2/EC2Step.java @@ -99,10 +99,7 @@ public ListBoxModel doFillCloudItems() { Jenkins.get().checkPermission(Jenkins.SYSTEM_READ); ListBoxModel r = new ListBoxModel(); r.add("", ""); - Jenkins.get() - .clouds - .getAll(AmazonEC2Cloud.class) - .forEach(c -> r.add(c.getDisplayName(), c.getDisplayName())); + Jenkins.get().clouds.getAll(EC2Cloud.class).forEach(c -> r.add(c.getDisplayName(), c.getDisplayName())); return r; } @@ -111,8 +108,8 @@ public ListBoxModel doFillTemplateItems(@QueryParameter String cloudName) { Jenkins.get().checkPermission(Jenkins.SYSTEM_READ); ListBoxModel r = new ListBoxModel(); Cloud cloud = Jenkins.get().getCloud(Util.fixEmpty(cloudName)); - if (cloud instanceof AmazonEC2Cloud) { - AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) cloud; + if (cloud instanceof EC2Cloud) { + EC2Cloud ec2Cloud = (EC2Cloud) cloud; for (SlaveTemplate template : ec2Cloud.getTemplates()) { for (String labelList : template.labels.split(" ")) { r.add( @@ -144,9 +141,9 @@ public static class Execution extends SynchronousNonBlockingStepExecution opt = EnumSet.noneOf(SlaveTemplate.ProvisionOptions.class); @@ -159,7 +156,7 @@ protected Instance run() throws Exception { } EC2AbstractSlave slave = instances.get(0); - return CloudHelper.getInstanceWithRetry(slave.getInstanceId(), (AmazonEC2Cloud) cl); + return CloudHelper.getInstanceWithRetry(slave.getInstanceId(), (EC2Cloud) cl); } else { throw new IllegalArgumentException( "Error in AWS Cloud. Please review AWS template defined in Jenkins configuration."); diff --git a/src/main/java/hudson/plugins/ec2/Eucalyptus.java b/src/main/java/hudson/plugins/ec2/Eucalyptus.java deleted file mode 100644 index 788f4e5f0..000000000 --- a/src/main/java/hudson/plugins/ec2/Eucalyptus.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package hudson.plugins.ec2; - -import hudson.Extension; -import hudson.model.ItemGroup; -import hudson.util.FormValidation; -import java.io.IOException; -import java.net.URL; -import java.util.List; -import javax.servlet.ServletException; -import org.kohsuke.stapler.AncestorInPath; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.interceptor.RequirePOST; - -/** - * Eucalyptus. - * - * @author Kohsuke Kawaguchi - */ -public class Eucalyptus extends EC2Cloud { - private final URL ec2endpoint; - private final URL s3endpoint; - - @DataBoundConstructor - public Eucalyptus( - String name, - URL ec2EndpointUrl, - URL s3EndpointUrl, - boolean useInstanceProfileForCredentials, - String credentialsId, - String privateKey, - String sshKeysCredentialsId, - String instanceCapStr, - List templates, - String roleArn, - String roleSessionName) { - super( - name, - useInstanceProfileForCredentials, - credentialsId, - privateKey, - sshKeysCredentialsId, - instanceCapStr, - templates, - roleArn, - roleSessionName); - this.ec2endpoint = ec2EndpointUrl; - this.s3endpoint = s3EndpointUrl; - } - - @Deprecated - public Eucalyptus( - URL ec2EndpointUrl, - URL s3EndpointUrl, - boolean useInstanceProfileForCredentials, - String credentialsId, - String privateKey, - String sshKeysCredentialsId, - String instanceCapStr, - List templates, - String roleArn, - String roleSessionName) - throws IOException { - this( - "eucalyptus", - ec2EndpointUrl, - s3EndpointUrl, - useInstanceProfileForCredentials, - credentialsId, - privateKey, - sshKeysCredentialsId, - instanceCapStr, - templates, - roleArn, - roleSessionName); - } - - @Deprecated - public Eucalyptus( - URL ec2EndpointUrl, - URL s3EndpointUrl, - boolean useInstanceProfileForCredentials, - String credentialsId, - String privateKey, - String instanceCapStr, - List templates, - String roleArn, - String roleSessionName) - throws IOException { - this( - "eucalyptus", - ec2EndpointUrl, - s3EndpointUrl, - useInstanceProfileForCredentials, - credentialsId, - privateKey, - null, - instanceCapStr, - templates, - roleArn, - roleSessionName); - } - - @Override - public URL getEc2EndpointUrl() throws IOException { - return this.ec2endpoint; - } - - @Override - public URL getS3EndpointUrl() throws IOException { - return this.s3endpoint; - } - - @Extension - public static class DescriptorImpl extends EC2Cloud.DescriptorImpl { - @Override - public String getDisplayName() { - return "Eucalyptus"; - } - - @Override - @RequirePOST - public FormValidation doTestConnection( - @AncestorInPath ItemGroup context, - @QueryParameter URL ec2endpoint, - @QueryParameter boolean useInstanceProfileForCredentials, - @QueryParameter String credentialsId, - @QueryParameter String sshKeysCredentialsId, - @QueryParameter String roleArn, - @QueryParameter String roleSessionName, - @QueryParameter String region) - throws IOException, ServletException { - return super.doTestConnection( - context, - ec2endpoint, - useInstanceProfileForCredentials, - credentialsId, - sshKeysCredentialsId, - roleArn, - roleSessionName, - region); - } - } -} diff --git a/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java b/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java index 056ba5ab3..7fab52c01 100644 --- a/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java +++ b/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java @@ -39,13 +39,13 @@ public NodeProvisioner.StrategyDecision apply(NodeProvisioner.StrategyState stra if (availableCapacity < currentDemand) { Jenkins jenkinsInstance = Jenkins.get(); for (Cloud cloud : jenkinsInstance.clouds) { - if (!(cloud instanceof AmazonEC2Cloud)) { + if (!(cloud instanceof EC2Cloud)) { continue; } if (!cloud.canProvision(label)) { continue; } - AmazonEC2Cloud ec2 = (AmazonEC2Cloud) cloud; + EC2Cloud ec2 = (EC2Cloud) cloud; if (!ec2.isNoDelayProvisioning()) { continue; } diff --git a/src/main/java/hudson/plugins/ec2/PluginImpl.java b/src/main/java/hudson/plugins/ec2/PluginImpl.java index 6f4dc0928..666046144 100644 --- a/src/main/java/hudson/plugins/ec2/PluginImpl.java +++ b/src/main/java/hudson/plugins/ec2/PluginImpl.java @@ -77,7 +77,7 @@ public String getDisplayName() { @Override public void postInitialize() throws IOException { // backward compatibility with the legacy class name - Jenkins.XSTREAM.alias("hudson.plugins.ec2.EC2Cloud", AmazonEC2Cloud.class); + Jenkins.XSTREAM.alias("hudson.plugins.ec2.EC2Cloud", AmazonEC2Cloud.class, EC2Cloud.class); Jenkins.XSTREAM.alias("hudson.plugins.ec2.EC2Slave", EC2OndemandSlave.class); // backward compatibility with the legacy instance type Jenkins.XSTREAM.registerConverter(new InstanceTypeConverter()); diff --git a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java index 859d2065d..adaa017f9 100644 --- a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java +++ b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java @@ -2841,8 +2841,7 @@ public FormValidation doValidateAmi( useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); AmazonEC2 ec2; if (region != null) { - ec2 = AmazonEC2Factory.getInstance() - .connect(credentialsProvider, AmazonEC2Cloud.getEc2EndpointUrl(region)); + ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, EC2Cloud.getEc2EndpointUrl(region)); } else { ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, new URL(ec2endpoint)); } diff --git a/src/main/java/hudson/plugins/ec2/SpotConfiguration.java b/src/main/java/hudson/plugins/ec2/SpotConfiguration.java index 577a55370..50328ce66 100644 --- a/src/main/java/hudson/plugins/ec2/SpotConfiguration.java +++ b/src/main/java/hudson/plugins/ec2/SpotConfiguration.java @@ -158,8 +158,8 @@ public FormValidation doCurrentSpotPrice( // region queried from the created cloud AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); - AmazonEC2 ec2 = AmazonEC2Factory.getInstance() - .connect(credentialsProvider, AmazonEC2Cloud.getEc2EndpointUrl(region)); + AmazonEC2 ec2 = + AmazonEC2Factory.getInstance().connect(credentialsProvider, EC2Cloud.getEc2EndpointUrl(region)); if (ec2 != null) { diff --git a/src/main/resources/hudson/plugins/ec2/AmazonEC2Cloud/config-entries.jelly b/src/main/resources/hudson/plugins/ec2/EC2Cloud/config-entries.jelly similarity index 100% rename from src/main/resources/hudson/plugins/ec2/AmazonEC2Cloud/config-entries.jelly rename to src/main/resources/hudson/plugins/ec2/EC2Cloud/config-entries.jelly diff --git a/src/main/resources/hudson/plugins/ec2/EC2Cloud/help-ec2EndpointUrl.html b/src/main/resources/hudson/plugins/ec2/EC2Cloud/help-ec2EndpointUrl.html deleted file mode 100644 index 656704393..000000000 --- a/src/main/resources/hudson/plugins/ec2/EC2Cloud/help-ec2EndpointUrl.html +++ /dev/null @@ -1,33 +0,0 @@ - -
- This field is optional. It controls what EC2 API endpoint to connect to. - By default https://ec2.us-east-1.amazonaws.com/ is used. To use other - Amazon AWS zones, change this appropriately. - - When using Ubuntu Enterprise Cloud (Eucalyptus) a url like - http://cluster-controler:8773/services/Eucalyptus is needed. - - For other EC2 API-compatible clouds please see your product documentation. -
diff --git a/src/main/resources/hudson/plugins/ec2/AmazonEC2Cloud/help-noDelayProvisioning.html b/src/main/resources/hudson/plugins/ec2/EC2Cloud/help-noDelayProvisioning.html similarity index 100% rename from src/main/resources/hudson/plugins/ec2/AmazonEC2Cloud/help-noDelayProvisioning.html rename to src/main/resources/hudson/plugins/ec2/EC2Cloud/help-noDelayProvisioning.html diff --git a/src/main/resources/hudson/plugins/ec2/AmazonEC2Cloud/help-region.html b/src/main/resources/hudson/plugins/ec2/EC2Cloud/help-region.html similarity index 100% rename from src/main/resources/hudson/plugins/ec2/AmazonEC2Cloud/help-region.html rename to src/main/resources/hudson/plugins/ec2/EC2Cloud/help-region.html diff --git a/src/main/resources/hudson/plugins/ec2/EC2Cloud/help-s3EndpointUrl.html b/src/main/resources/hudson/plugins/ec2/EC2Cloud/help-s3EndpointUrl.html deleted file mode 100644 index ebc8732f7..000000000 --- a/src/main/resources/hudson/plugins/ec2/EC2Cloud/help-s3EndpointUrl.html +++ /dev/null @@ -1,33 +0,0 @@ - -
- This field is optional. It controls what S3 API endpoint to connect to. - By default https://s3.amazonaws.com/ is used (note that this works with all - amazonaws zones - s3 transparently redirects between zones). - - When using Ubuntu Enterprise Cloud (Eucalyptus) the default URL should be - something like http://cluster-controller-ip:8773/services/Walrus. - - For other EC2/S3 compatible servers please see your product documentation. -
diff --git a/src/main/resources/hudson/plugins/ec2/Eucalyptus/config-entries.jelly b/src/main/resources/hudson/plugins/ec2/Eucalyptus/config-entries.jelly deleted file mode 100644 index 7036fd28b..000000000 --- a/src/main/resources/hudson/plugins/ec2/Eucalyptus/config-entries.jelly +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/hudson/plugins/ec2/Eucalyptus/help-url.html b/src/main/resources/hudson/plugins/ec2/Eucalyptus/help-url.html deleted file mode 100644 index f27813798..000000000 --- a/src/main/resources/hudson/plugins/ec2/Eucalyptus/help-url.html +++ /dev/null @@ -1,27 +0,0 @@ - -
- Specify the HTTPS URL of the Eucalyptus web UI. - Normally, this is something like https://yourmachine:8443/ -
diff --git a/src/main/resources/hudson/plugins/ec2/Messages.properties b/src/main/resources/hudson/plugins/ec2/Messages.properties index 0152a90c3..b0842f148 100644 --- a/src/main/resources/hudson/plugins/ec2/Messages.properties +++ b/src/main/resources/hudson/plugins/ec2/Messages.properties @@ -8,6 +8,6 @@ EC2SpotSlave.AmazonEC2SpotInstance=Amazon EC2 Spot Instance EC2SpotSlave.Spot1=Spot $ EC2SpotSlave.Spot2= max bid price -AmazonEC2Cloud.NonUniqName=Cloud name must be unique across EC2 clouds -AmazonEC2Cloud.MalformedUrl=The URL is malformed. The default endpoint will be used +EC2Cloud.NonUniqName=Cloud name must be unique across EC2 clouds +EC2Cloud.MalformedUrl=The URL is malformed. The default endpoint will be used General.MissingPermission=You do not have the Overall/Administer right to modify this field diff --git a/src/test/java/hudson/plugins/ec2/AmazonEC2CloudTest.java b/src/test/java/hudson/plugins/ec2/AmazonEC2CloudTest.java deleted file mode 100644 index 9defc3ee2..000000000 --- a/src/test/java/hudson/plugins/ec2/AmazonEC2CloudTest.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package hudson.plugins.ec2; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import com.amazonaws.services.ec2.AmazonEC2; -import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl; -import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; -import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey; -import com.cloudbees.plugins.credentials.CredentialsProvider; -import com.cloudbees.plugins.credentials.CredentialsScope; -import com.cloudbees.plugins.credentials.CredentialsStore; -import com.cloudbees.plugins.credentials.SystemCredentialsProvider; -import com.cloudbees.plugins.credentials.domains.Domain; -import hudson.plugins.ec2.util.TestSSHUserPrivateKey; -import hudson.util.FormValidation; -import hudson.util.ListBoxModel; -import java.io.IOException; -import java.util.Collections; -import jenkins.model.Jenkins; -import org.htmlunit.html.HtmlForm; -import org.htmlunit.html.HtmlTextInput; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.jvnet.hudson.test.Issue; -import org.jvnet.hudson.test.JenkinsRule; -import org.mockito.Mockito; -import org.xml.sax.SAXException; - -/** - * @author Kohsuke Kawaguchi - */ -public class AmazonEC2CloudTest { - - @Rule - public JenkinsRule r = new JenkinsRule(); - - private AmazonEC2Cloud cloud; - - @Before - public void setUp() throws Exception { - cloud = new AmazonEC2Cloud( - "us-east-1", - true, - "abc", - "us-east-1", - null, - "ghi", - "3", - Collections.emptyList(), - "roleArn", - "roleSessionName"); - r.jenkins.clouds.add(cloud); - } - - @Test - public void testConfigRoundtrip() throws Exception { - r.submit(getConfigForm()); - r.assertEqualBeans( - cloud, - r.jenkins.clouds.get(AmazonEC2Cloud.class), - "name,region,useInstanceProfileForCredentials,privateKey,instanceCap,roleArn,roleSessionName"); - } - - @Test - public void testAmazonEC2FactoryGetInstance() throws Exception { - AmazonEC2Cloud cloud = r.jenkins.clouds.get(AmazonEC2Cloud.class); - AmazonEC2 connection = cloud.connect(); - Assert.assertNotNull(connection); - Assert.assertTrue(Mockito.mockingDetails(connection).isMock()); - } - - @Test - public void testAmazonEC2FactoryWorksIfSessionNameMissing() throws Exception { - r.jenkins.clouds.replace(new AmazonEC2Cloud( - "us-east-1", true, "abc", "us-east-1", null, "ghi", "3", Collections.emptyList(), "roleArn", null)); - AmazonEC2Cloud cloud = r.jenkins.clouds.get(AmazonEC2Cloud.class); - AmazonEC2 connection = cloud.connect(); - Assert.assertNotNull(connection); - Assert.assertTrue(Mockito.mockingDetails(connection).isMock()); - } - - @Test - public void testSessionNameMissingWarning() { - AmazonEC2Cloud actual = r.jenkins.clouds.get(AmazonEC2Cloud.class); - AmazonEC2Cloud.DescriptorImpl descriptor = (AmazonEC2Cloud.DescriptorImpl) actual.getDescriptor(); - assertThat(descriptor.doCheckRoleSessionName("roleArn", "").kind, is(FormValidation.Kind.WARNING)); - assertThat(descriptor.doCheckRoleSessionName("roleArn", "roleSessionName").kind, is(FormValidation.Kind.OK)); - } - - @Test - public void testSshKeysCredentialsIdRemainsUnchangedAfterUpdatingOtherFields() throws Exception { - HtmlForm form = getConfigForm(); - HtmlTextInput input = form.getInputByName("_.roleSessionName"); - - input.setText("updatedSessionName"); - r.submit(form); - AmazonEC2Cloud actual = r.jenkins.clouds.get(AmazonEC2Cloud.class); - assertEquals("updatedSessionName", actual.getRoleSessionName()); - r.assertEqualBeans( - cloud, actual, "name,region,useInstanceProfileForCredentials,sshKeysCredentialsId,instanceCap,roleArn"); - } - - @Test - public void testAWSCredentials() throws IOException { - AmazonEC2Cloud actual = r.jenkins.clouds.get(AmazonEC2Cloud.class); - AmazonEC2Cloud.DescriptorImpl descriptor = (AmazonEC2Cloud.DescriptorImpl) actual.getDescriptor(); - assertNotNull(descriptor); - ListBoxModel m = descriptor.doFillCredentialsIdItems(Jenkins.get()); - assertThat(m.size(), is(1)); - SystemCredentialsProvider.getInstance() - .getCredentials() - .add(new AWSCredentialsImpl( - CredentialsScope.SYSTEM, "system_id", "system_ak", "system_sk", "system_desc")); - // Ensure added credential is displayed - m = descriptor.doFillCredentialsIdItems(Jenkins.get()); - assertThat(m.size(), is(2)); - SystemCredentialsProvider.getInstance() - .getCredentials() - .add(new AWSCredentialsImpl( - CredentialsScope.GLOBAL, "global_id", "global_ak", "global_sk", "global_desc")); - m = descriptor.doFillCredentialsIdItems(Jenkins.get()); - assertThat(m.size(), is(3)); - } - - @Test - public void testSshCredentials() throws IOException { - AmazonEC2Cloud actual = r.jenkins.clouds.get(AmazonEC2Cloud.class); - AmazonEC2Cloud.DescriptorImpl descriptor = (AmazonEC2Cloud.DescriptorImpl) actual.getDescriptor(); - assertNotNull(descriptor); - ListBoxModel m = descriptor.doFillSshKeysCredentialsIdItems(Jenkins.get(), ""); - assertThat(m.size(), is(1)); - BasicSSHUserPrivateKey sshKeyCredentials = new BasicSSHUserPrivateKey( - CredentialsScope.SYSTEM, - "ghi", - "key", - new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource("somekey"), - "", - ""); - for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(r.jenkins)) { - if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) { - credentialsStore.addCredentials(Domain.global(), sshKeyCredentials); - } - } - // Ensure added credential is displayed - m = descriptor.doFillSshKeysCredentialsIdItems(Jenkins.get(), ""); - assertThat(m.size(), is(2)); - // Ensure that the cloud can resolve the new key - assertThat(actual.resolvePrivateKey(), notNullValue()); - } - - /** - * Ensure that EC2 plugin can use any implementation of SSHUserPrivateKey (not just the default implementation, BasicSSHUserPrivateKey). - */ - @Test - @Issue("JENKINS-63986") - public void testCustomSshCredentialTypes() throws IOException { - AmazonEC2Cloud actual = r.jenkins.clouds.get(AmazonEC2Cloud.class); - AmazonEC2Cloud.DescriptorImpl descriptor = (AmazonEC2Cloud.DescriptorImpl) actual.getDescriptor(); - assertNotNull(descriptor); - ListBoxModel m = descriptor.doFillSshKeysCredentialsIdItems(Jenkins.get(), ""); - assertThat(m.size(), is(1)); - SSHUserPrivateKey sshKeyCredentials = new TestSSHUserPrivateKey( - CredentialsScope.SYSTEM, - "ghi", - "key", - new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource("somekey"), - "", - ""); - for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(r.jenkins)) { - if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) { - credentialsStore.addCredentials(Domain.global(), sshKeyCredentials); - } - } - // Ensure added credential is displayed - m = descriptor.doFillSshKeysCredentialsIdItems(Jenkins.get(), ""); - assertThat(m.size(), is(2)); - // Ensure that the cloud can resolve the new key - assertThat(actual.resolvePrivateKey(), notNullValue()); - } - - private HtmlForm getConfigForm() throws IOException, SAXException { - return r.createWebClient().goTo(cloud.getUrl() + "configure").getFormByName("config"); - } -} diff --git a/src/test/java/hudson/plugins/ec2/AmazonEC2CloudUnitTest.java b/src/test/java/hudson/plugins/ec2/AmazonEC2CloudUnitTest.java deleted file mode 100644 index fd285cc2e..000000000 --- a/src/test/java/hudson/plugins/ec2/AmazonEC2CloudUnitTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package hudson.plugins.ec2; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.Tag; -import hudson.plugins.ec2.util.AmazonEC2FactoryMockImpl; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import jenkins.model.Jenkins; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; - -/** - * Unit tests related to {@link AmazonEC2Cloud}, but do not require a Jenkins instance. - */ -@RunWith(MockitoJUnitRunner.Silent.class) -public class AmazonEC2CloudUnitTest { - - @Test - public void testEC2EndpointURLCreation() throws MalformedURLException { - AmazonEC2Cloud.DescriptorImpl descriptor = new AmazonEC2Cloud.DescriptorImpl(); - - assertEquals(new URL(EC2Cloud.DEFAULT_EC2_ENDPOINT), descriptor.determineEC2EndpointURL(null)); - assertEquals(new URL(EC2Cloud.DEFAULT_EC2_ENDPOINT), descriptor.determineEC2EndpointURL("")); - assertEquals(new URL("https://www.abc.com"), descriptor.determineEC2EndpointURL("https://www.abc.com")); - } - - @Test - public void testInstaceCap() throws Exception { - AmazonEC2Cloud cloud = new AmazonEC2Cloud( - "us-east-1", - true, - "abc", - "us-east-1", - null, - "key", - null, - Collections.emptyList(), - "roleArn", - "roleSessionName"); - assertEquals(cloud.getInstanceCap(), Integer.MAX_VALUE); - assertEquals(cloud.getInstanceCapStr(), ""); - - final int cap = 3; - final String capStr = String.valueOf(cap); - cloud = new AmazonEC2Cloud( - "us-east-1", - true, - "abc", - "us-east-1", - null, - "key", - capStr, - Collections.emptyList(), - "roleArn", - "roleSessionName"); - assertEquals(cloud.getInstanceCap(), cap); - assertEquals(cloud.getInstanceCapStr(), capStr); - } - - @Test - public void testSpotInstanceCount() throws Exception { - final int numberOfSpotInstanceRequests = 105; - AmazonEC2Cloud cloud = Mockito.spy(new AmazonEC2Cloud( - "us-east-1", - true, - "abc", - "us-east-1", - null, - "key", - null, - Collections.emptyList(), - "roleArn", - "roleSessionName")); - Jenkins jenkinsMock = mock(Jenkins.class); - EC2SpotSlave spotSlaveMock = mock(EC2SpotSlave.class); - try (MockedStatic mocked = Mockito.mockStatic(Jenkins.class)) { - mocked.when(Jenkins::get).thenReturn(jenkinsMock); - Mockito.when(jenkinsMock.getNodes()).thenReturn(Collections.singletonList(spotSlaveMock)); - when(spotSlaveMock.getSpotRequest()).thenReturn(null); - when(spotSlaveMock.getSpotInstanceRequestId()).thenReturn("sir-id"); - - List instances = new ArrayList<>(); - for (int i = 0; i <= numberOfSpotInstanceRequests; i++) { - instances.add(new Instance() - .withInstanceId("id" + i) - .withTags(new Tag().withKey("jenkins_slave_type").withValue("spot"))); - } - - AmazonEC2FactoryMockImpl.instances = instances; - - Mockito.doReturn(AmazonEC2FactoryMockImpl.createAmazonEC2Mock(null)) - .when(cloud) - .connect(); - - Method countCurrentEC2SpotSlaves = EC2Cloud.class.getDeclaredMethod( - "countCurrentEC2SpotSlaves", SlaveTemplate.class, String.class, Set.class); - countCurrentEC2SpotSlaves.setAccessible(true); - Object[] params = {null, "jenkinsurl", new HashSet()}; - int n = (int) countCurrentEC2SpotSlaves.invoke(cloud, params); - - // Should equal number of spot instance requests + 1 for spot nodes not having a spot instance request - assertEquals(numberOfSpotInstanceRequests + 1, n); - } - } - - @Test - public void testCNPartition() { - assertEquals( - EC2Cloud.getAwsPartitionHostForService("cn-northwest-1", "ec2"), "ec2.cn-northwest-1.amazonaws.com.cn"); - assertEquals( - EC2Cloud.getAwsPartitionHostForService("cn-northwest-1", "s3"), "s3.cn-northwest-1.amazonaws.com.cn"); - } - - @Test - public void testNormalPartition() { - assertEquals(EC2Cloud.getAwsPartitionHostForService("us-east-1", "ec2"), "ec2.us-east-1.amazonaws.com"); - assertEquals(EC2Cloud.getAwsPartitionHostForService("us-east-1", "s3"), "s3.us-east-1.amazonaws.com"); - } -} diff --git a/src/test/java/hudson/plugins/ec2/CloudHelperTest.java b/src/test/java/hudson/plugins/ec2/CloudHelperTest.java index 50008c4bb..b22219df1 100644 --- a/src/test/java/hudson/plugins/ec2/CloudHelperTest.java +++ b/src/test/java/hudson/plugins/ec2/CloudHelperTest.java @@ -23,11 +23,11 @@ public class CloudHelperTest { @Mock - private AmazonEC2Cloud cloud; + private EC2Cloud cloud; @Before public void init() throws Exception { - cloud = new AmazonEC2Cloud( + cloud = new EC2Cloud( "us-east-1", true, "abc", diff --git a/src/test/java/hudson/plugins/ec2/ConfigurationAsCodeTest.java b/src/test/java/hudson/plugins/ec2/ConfigurationAsCodeTest.java index 24aa6d8fa..b61381a8c 100644 --- a/src/test/java/hudson/plugins/ec2/ConfigurationAsCodeTest.java +++ b/src/test/java/hudson/plugins/ec2/ConfigurationAsCodeTest.java @@ -32,7 +32,7 @@ public class ConfigurationAsCodeTest { @Test @ConfiguredWithCode("EC2CloudEmpty.yml") public void testEmptyConfig() throws Exception { - final AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) Jenkins.get().getCloud("empty"); + final EC2Cloud ec2Cloud = (EC2Cloud) Jenkins.get().getCloud("empty"); assertNotNull(ec2Cloud); assertEquals(0, ec2Cloud.getTemplates().size()); } @@ -40,7 +40,7 @@ public void testEmptyConfig() throws Exception { @Test @ConfiguredWithCode("UnixData.yml") public void testUnixData() throws Exception { - final AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) Jenkins.get().getCloud("production"); + final EC2Cloud ec2Cloud = (EC2Cloud) Jenkins.get().getCloud("production"); assertNotNull(ec2Cloud); assertTrue(ec2Cloud.isUseInstanceProfileForCredentials()); @@ -77,7 +77,7 @@ public void testUnixData() throws Exception { @Test @ConfiguredWithCode("Unix.yml") public void testUnix() throws Exception { - final AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) Jenkins.get().getCloud("staging"); + final EC2Cloud ec2Cloud = (EC2Cloud) Jenkins.get().getCloud("staging"); assertNotNull(ec2Cloud); assertTrue(ec2Cloud.isUseInstanceProfileForCredentials()); @@ -99,7 +99,7 @@ public void testUnix() throws Exception { @Test @ConfiguredWithCode("WindowsData.yml") public void testWindowsData() throws Exception { - final AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) Jenkins.get().getCloud("development"); + final EC2Cloud ec2Cloud = (EC2Cloud) Jenkins.get().getCloud("development"); assertNotNull(ec2Cloud); assertTrue(ec2Cloud.isUseInstanceProfileForCredentials()); @@ -128,7 +128,7 @@ public void testWindowsData() throws Exception { @Test @ConfiguredWithCode("BackwardsCompatibleConnectionStrategy.yml") public void testBackwardsCompatibleConnectionStrategy() throws Exception { - final AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) Jenkins.get().getCloud("us-east-1"); + final EC2Cloud ec2Cloud = (EC2Cloud) Jenkins.get().getCloud("us-east-1"); assertNotNull(ec2Cloud); final List templates = ec2Cloud.getTemplates(); @@ -162,7 +162,7 @@ public void testConfigAsCodeWithAltEndpointAndJavaPathExport() throws Exception @Test @ConfiguredWithCode("Unix-withMinimumInstancesTimeRange.yml") public void testConfigAsCodeWithMinimumInstancesTimeRange() throws Exception { - final AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) Jenkins.get().getCloud("timed"); + final EC2Cloud ec2Cloud = (EC2Cloud) Jenkins.get().getCloud("timed"); assertNotNull(ec2Cloud); assertTrue(ec2Cloud.isUseInstanceProfileForCredentials()); @@ -191,7 +191,7 @@ public void testConfigAsCodeWithMinimumInstancesTimeRange() throws Exception { @Test @ConfiguredWithCode("Ami.yml") public void testAmi() throws Exception { - final AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) Jenkins.get().getCloud("test"); + final EC2Cloud ec2Cloud = (EC2Cloud) Jenkins.get().getCloud("test"); assertNotNull(ec2Cloud); final List templates = ec2Cloud.getTemplates(); @@ -224,7 +224,7 @@ public void testAmi() throws Exception { @Test @ConfiguredWithCode("MacData.yml") public void testMacData() throws Exception { - final AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) Jenkins.get().getCloud("production"); + final EC2Cloud ec2Cloud = (EC2Cloud) Jenkins.get().getCloud("production"); assertNotNull(ec2Cloud); assertTrue(ec2Cloud.isUseInstanceProfileForCredentials()); @@ -254,7 +254,7 @@ public void testMacData() throws Exception { @Test @ConfiguredWithCode("Mac.yml") public void testMac() throws Exception { - final AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) Jenkins.get().getCloud("staging"); + final EC2Cloud ec2Cloud = (EC2Cloud) Jenkins.get().getCloud("staging"); assertNotNull(ec2Cloud); assertTrue(ec2Cloud.isUseInstanceProfileForCredentials()); diff --git a/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java b/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java index 9d37d5c4d..a4856d658 100644 --- a/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java @@ -117,8 +117,7 @@ public void testMaxUsesBackwardCompat() throws Exception { List templates = new ArrayList<>(); templates.add(orig); String cloudName = "us-east-1"; - AmazonEC2Cloud ac = - new AmazonEC2Cloud(cloudName, false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud(cloudName, false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); EC2AbstractSlave slave = new EC2AbstractSlave( diff --git a/src/test/java/hudson/plugins/ec2/EC2CloudTest.java b/src/test/java/hudson/plugins/ec2/EC2CloudTest.java index d7257f1f8..3a01e27fd 100644 --- a/src/test/java/hudson/plugins/ec2/EC2CloudTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2CloudTest.java @@ -1,35 +1,73 @@ +/* + * The MIT License + * + * Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package hudson.plugins.ec2; -import static org.junit.Assert.assertArrayEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.DescribeInstancesResult; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.InstanceType; -import hudson.model.Node; -import java.util.ArrayList; -import java.util.Arrays; +import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl; +import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; +import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey; +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; +import com.cloudbees.plugins.credentials.CredentialsStore; +import com.cloudbees.plugins.credentials.SystemCredentialsProvider; +import com.cloudbees.plugins.credentials.domains.Domain; +import hudson.plugins.ec2.util.TestSSHUserPrivateKey; +import hudson.util.FormValidation; +import hudson.util.ListBoxModel; +import java.io.IOException; import java.util.Collections; -import java.util.List; import jenkins.model.Jenkins; +import org.htmlunit.html.HtmlForm; +import org.htmlunit.html.HtmlTextInput; import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.MockedStatic; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; +import org.xml.sax.SAXException; -@RunWith(MockitoJUnitRunner.class) +/** + * @author Kohsuke Kawaguchi + */ public class EC2CloudTest { - @Test - public void testSlaveTemplateAddition() throws Exception { - AmazonEC2Cloud cloud = new AmazonEC2Cloud( + + @Rule + public JenkinsRule r = new JenkinsRule(); + + private EC2Cloud cloud; + + @Before + public void setUp() throws Exception { + cloud = new EC2Cloud( "us-east-1", true, "abc", @@ -40,275 +78,136 @@ public void testSlaveTemplateAddition() throws Exception { Collections.emptyList(), "roleArn", "roleSessionName"); - SlaveTemplate orig = new SlaveTemplate( - "ami-123", - EC2AbstractSlave.TEST_ZONE, - null, - "default", - "foo", - InstanceType.M1Large, - false, - "ttt", - Node.Mode.NORMAL, - "description", - "bar", - "bbb", - "aaa", - "10", - "fff", - null, - EC2AbstractSlave.DEFAULT_JAVA_PATH, - "-Xmx1g", - false, - "subnet 456", - null, - null, - 0, - 0, - null, - "iamInstanceProfile", - true, - false, - "", - false, - "", - false, - false, - false, - ConnectionStrategy.PUBLIC_IP, - -1, - Collections.emptyList(), - null, - Tenancy.Default, - EbsEncryptRootVolume.DEFAULT, - EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED, - EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, - EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, - EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); - cloud.addTemplate(orig); - assertNotNull(cloud.getTemplate(orig.description)); + r.jenkins.clouds.add(cloud); } @Test - public void testSlaveTemplateUpdate() throws Exception { - AmazonEC2Cloud cloud = new AmazonEC2Cloud( - "us-east-1", - true, - "abc", - "us-east-1", - null, - "ghi", - "3", - Collections.emptyList(), - "roleArn", - "roleSessionName"); - SlaveTemplate oldSlaveTemplate = new SlaveTemplate( - "ami-123", - EC2AbstractSlave.TEST_ZONE, - null, - "default", - "foo", - InstanceType.M1Large, - false, - "ttt", - Node.Mode.NORMAL, - "OldSlaveDescription", - "bar", - "bbb", - "aaa", - "10", - "fff", - null, - EC2AbstractSlave.DEFAULT_JAVA_PATH, - "-Xmx1g", - false, - "subnet 456", - null, - null, - 0, - 0, - null, - "iamInstanceProfile", - true, - false, - "", - false, - "", - false, - false, - false, - ConnectionStrategy.PUBLIC_IP, - -1, - Collections.emptyList(), - null, - Tenancy.Default, - EbsEncryptRootVolume.DEFAULT, - EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED, - EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, - EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, - EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); - SlaveTemplate secondSlaveTemplate = new SlaveTemplate( - "ami-123", - EC2AbstractSlave.TEST_ZONE, - null, - "default", - "foo", - InstanceType.M1Large, - false, - "ttt", - Node.Mode.NORMAL, - "SecondSlaveDescription", - "bar", - "bbb", - "aaa", - "10", - "fff", - null, - EC2AbstractSlave.DEFAULT_JAVA_PATH, - "-Xmx1g", - false, - "subnet 456", - null, - null, - 0, - 0, - null, - "iamInstanceProfile", - true, - false, - "", - false, - "", - false, - false, - false, - ConnectionStrategy.PUBLIC_IP, - -1, - Collections.emptyList(), - null, - Tenancy.Default, - EbsEncryptRootVolume.DEFAULT, - EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED, - EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, - EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, - EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); - cloud.addTemplate(oldSlaveTemplate); - cloud.addTemplate(secondSlaveTemplate); - SlaveTemplate newSlaveTemplate = new SlaveTemplate( - "ami-456", - EC2AbstractSlave.TEST_ZONE, - null, - "default", - "foo", - InstanceType.M1Large, - false, - "ttt", - Node.Mode.NORMAL, - "NewSlaveDescription", - "bar", - "bbb", - "aaa", - "10", - "fff", - null, - EC2AbstractSlave.DEFAULT_JAVA_PATH, - "-Xmx1g", - false, - "subnet 456", - null, - null, - 0, - 0, - null, - "iamInstanceProfile", - true, - false, - "", - false, - "", - false, - false, - false, - ConnectionStrategy.PUBLIC_IP, - -1, - Collections.emptyList(), - null, - Tenancy.Default, - EbsEncryptRootVolume.DEFAULT, - EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED, - EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, - EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, - EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); - int index = cloud.getTemplates().indexOf(oldSlaveTemplate); + public void testConfigRoundtrip() throws Exception { + r.submit(getConfigForm()); + r.assertEqualBeans( + cloud, + r.jenkins.clouds.get(EC2Cloud.class), + "name,region,useInstanceProfileForCredentials,privateKey,instanceCap,roleArn,roleSessionName"); + } - cloud.updateTemplate(newSlaveTemplate, "OldSlaveDescription"); - assertNull(cloud.getTemplate("OldSlaveDescription")); - assertNotNull(cloud.getTemplate("NewSlaveDescription")); - Assert.assertEquals(index, cloud.getTemplates().indexOf(newSlaveTemplate)); // assert order of templates is kept + @Test + public void testAmazonEC2FactoryGetInstance() throws Exception { + EC2Cloud cloud = r.jenkins.clouds.get(EC2Cloud.class); + AmazonEC2 connection = cloud.connect(); + Assert.assertNotNull(connection); + Assert.assertTrue(Mockito.mockingDetails(connection).isMock()); } @Test - public void testReattachOrphanStoppedNodes() throws Exception { - /* Mocked items */ - AmazonEC2Cloud cloud = new AmazonEC2Cloud( - "us-east-1", - true, - "abc", - "us-east-1", - null, - "ghi", - "3", - Collections.emptyList(), - "roleArn", - "roleSessionName"); - EC2Cloud spyCloud = Mockito.spy(cloud); - AmazonEC2 mockEc2 = Mockito.mock(AmazonEC2.class); - Jenkins mockJenkins = Mockito.mock(Jenkins.class); - EC2AbstractSlave mockOrphanNode = Mockito.mock(EC2AbstractSlave.class); - SlaveTemplate mockSlaveTemplate = Mockito.mock(SlaveTemplate.class); - DescribeInstancesResult mockedDIResult = Mockito.mock(DescribeInstancesResult.class); - Instance mockedInstance = Mockito.mock(Instance.class); - List listOfMockedInstances = new ArrayList<>(); - listOfMockedInstances.add(mockedInstance); + public void testAmazonEC2FactoryWorksIfSessionNameMissing() throws Exception { + r.jenkins.clouds.replace(new EC2Cloud( + "us-east-1", true, "abc", "us-east-1", null, "ghi", "3", Collections.emptyList(), "roleArn", null)); + EC2Cloud cloud = r.jenkins.clouds.get(EC2Cloud.class); + AmazonEC2 connection = cloud.connect(); + Assert.assertNotNull(connection); + Assert.assertTrue(Mockito.mockingDetails(connection).isMock()); + } - try (MockedStatic mocked = Mockito.mockStatic(Jenkins.class)) { - mocked.when(Jenkins::getInstanceOrNull).thenReturn(mockJenkins); - EC2AbstractSlave[] orphanNodes = {mockOrphanNode}; - Mockito.doReturn(Arrays.asList(orphanNodes)).when(mockSlaveTemplate).toSlaves(eq(listOfMockedInstances)); - List listOfJenkinsNodes = new ArrayList<>(); + @Test + public void testSessionNameMissingWarning() { + EC2Cloud actual = r.jenkins.clouds.get(EC2Cloud.class); + EC2Cloud.DescriptorImpl descriptor = (EC2Cloud.DescriptorImpl) actual.getDescriptor(); + assertThat(descriptor.doCheckRoleSessionName("roleArn", "").kind, is(FormValidation.Kind.WARNING)); + assertThat(descriptor.doCheckRoleSessionName("roleArn", "roleSessionName").kind, is(FormValidation.Kind.OK)); + } - Mockito.doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocation) { - Node n = (Node) invocation.getArguments()[0]; - listOfJenkinsNodes.add(n); - return null; - } - }) - .when(mockJenkins) - .addNode(Mockito.any(Node.class)); + @Test + public void testSshKeysCredentialsIdRemainsUnchangedAfterUpdatingOtherFields() throws Exception { + HtmlForm form = getConfigForm(); + HtmlTextInput input = form.getInputByName("_.roleSessionName"); - Mockito.doReturn(null).when(mockOrphanNode).toComputer(); - Mockito.doReturn(false).when(mockOrphanNode).getStopOnTerminate(); - Mockito.doReturn(mockEc2).when(spyCloud).connect(); - Mockito.doReturn(mockedDIResult) - .when(mockSlaveTemplate) - .getDescribeInstanceResult(Mockito.any(AmazonEC2.class), eq(true)); - Mockito.doReturn(listOfMockedInstances) - .when(mockSlaveTemplate) - .findOrphansOrStopped(eq(mockedDIResult), Mockito.anyInt()); - Mockito.doNothing() - .when(mockSlaveTemplate) - .wakeOrphansOrStoppedUp(Mockito.any(AmazonEC2.class), eq(listOfMockedInstances)); + input.setText("updatedSessionName"); + r.submit(form); + EC2Cloud actual = r.jenkins.clouds.get(EC2Cloud.class); + assertEquals("updatedSessionName", actual.getRoleSessionName()); + r.assertEqualBeans( + cloud, actual, "name,region,useInstanceProfileForCredentials,sshKeysCredentialsId,instanceCap,roleArn"); + } - /* Actual call to test*/ - spyCloud.attemptReattachOrphanOrStoppedNodes(mockJenkins, mockSlaveTemplate, 1); + @Test + public void testAWSCredentials() throws IOException { + EC2Cloud actual = r.jenkins.clouds.get(EC2Cloud.class); + EC2Cloud.DescriptorImpl descriptor = (EC2Cloud.DescriptorImpl) actual.getDescriptor(); + assertNotNull(descriptor); + ListBoxModel m = descriptor.doFillCredentialsIdItems(Jenkins.get()); + assertThat(m.size(), is(1)); + SystemCredentialsProvider.getInstance() + .getCredentials() + .add(new AWSCredentialsImpl( + CredentialsScope.SYSTEM, "system_id", "system_ak", "system_sk", "system_desc")); + // Ensure added credential is displayed + m = descriptor.doFillCredentialsIdItems(Jenkins.get()); + assertThat(m.size(), is(2)); + SystemCredentialsProvider.getInstance() + .getCredentials() + .add(new AWSCredentialsImpl( + CredentialsScope.GLOBAL, "global_id", "global_ak", "global_sk", "global_desc")); + m = descriptor.doFillCredentialsIdItems(Jenkins.get()); + assertThat(m.size(), is(3)); + } - /* Checks */ - Mockito.verify(mockSlaveTemplate, times(1)) - .wakeOrphansOrStoppedUp(Mockito.any(AmazonEC2.class), eq(listOfMockedInstances)); - Node[] expectedNodes = {mockOrphanNode}; - assertArrayEquals(expectedNodes, listOfJenkinsNodes.toArray()); + @Test + public void testSshCredentials() throws IOException { + EC2Cloud actual = r.jenkins.clouds.get(EC2Cloud.class); + EC2Cloud.DescriptorImpl descriptor = (EC2Cloud.DescriptorImpl) actual.getDescriptor(); + assertNotNull(descriptor); + ListBoxModel m = descriptor.doFillSshKeysCredentialsIdItems(Jenkins.get(), ""); + assertThat(m.size(), is(1)); + BasicSSHUserPrivateKey sshKeyCredentials = new BasicSSHUserPrivateKey( + CredentialsScope.SYSTEM, + "ghi", + "key", + new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource("somekey"), + "", + ""); + for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(r.jenkins)) { + if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) { + credentialsStore.addCredentials(Domain.global(), sshKeyCredentials); + } + } + // Ensure added credential is displayed + m = descriptor.doFillSshKeysCredentialsIdItems(Jenkins.get(), ""); + assertThat(m.size(), is(2)); + // Ensure that the cloud can resolve the new key + assertThat(actual.resolvePrivateKey(), notNullValue()); + } + + /** + * Ensure that EC2 plugin can use any implementation of SSHUserPrivateKey (not just the default implementation, BasicSSHUserPrivateKey). + */ + @Test + @Issue("JENKINS-63986") + public void testCustomSshCredentialTypes() throws IOException { + EC2Cloud actual = r.jenkins.clouds.get(EC2Cloud.class); + EC2Cloud.DescriptorImpl descriptor = (EC2Cloud.DescriptorImpl) actual.getDescriptor(); + assertNotNull(descriptor); + ListBoxModel m = descriptor.doFillSshKeysCredentialsIdItems(Jenkins.get(), ""); + assertThat(m.size(), is(1)); + SSHUserPrivateKey sshKeyCredentials = new TestSSHUserPrivateKey( + CredentialsScope.SYSTEM, + "ghi", + "key", + new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource("somekey"), + "", + ""); + for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(r.jenkins)) { + if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) { + credentialsStore.addCredentials(Domain.global(), sshKeyCredentials); + } } + // Ensure added credential is displayed + m = descriptor.doFillSshKeysCredentialsIdItems(Jenkins.get(), ""); + assertThat(m.size(), is(2)); + // Ensure that the cloud can resolve the new key + assertThat(actual.resolvePrivateKey(), notNullValue()); + } + + private HtmlForm getConfigForm() throws IOException, SAXException { + return r.createWebClient().goTo(cloud.getUrl() + "configure").getFormByName("config"); } } diff --git a/src/test/java/hudson/plugins/ec2/EC2CloudUnitTest.java b/src/test/java/hudson/plugins/ec2/EC2CloudUnitTest.java new file mode 100644 index 000000000..15f613822 --- /dev/null +++ b/src/test/java/hudson/plugins/ec2/EC2CloudUnitTest.java @@ -0,0 +1,453 @@ +/* + * The MIT License + * + * Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package hudson.plugins.ec2; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.model.DescribeInstancesResult; +import com.amazonaws.services.ec2.model.Instance; +import com.amazonaws.services.ec2.model.InstanceType; +import com.amazonaws.services.ec2.model.Tag; +import hudson.model.Node; +import hudson.plugins.ec2.util.AmazonEC2FactoryMockImpl; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import jenkins.model.Jenkins; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +/** + * Unit tests related to {@link EC2Cloud}, but do not require a Jenkins instance. + */ +@RunWith(MockitoJUnitRunner.Silent.class) +public class EC2CloudUnitTest { + + @Test + public void testEC2EndpointURLCreation() throws MalformedURLException { + EC2Cloud.DescriptorImpl descriptor = new EC2Cloud.DescriptorImpl(); + + assertEquals(new URL(EC2Cloud.DEFAULT_EC2_ENDPOINT), descriptor.determineEC2EndpointURL(null)); + assertEquals(new URL(EC2Cloud.DEFAULT_EC2_ENDPOINT), descriptor.determineEC2EndpointURL("")); + assertEquals(new URL("https://www.abc.com"), descriptor.determineEC2EndpointURL("https://www.abc.com")); + } + + @Test + public void testInstaceCap() throws Exception { + EC2Cloud cloud = new EC2Cloud( + "us-east-1", + true, + "abc", + "us-east-1", + null, + "key", + null, + Collections.emptyList(), + "roleArn", + "roleSessionName"); + assertEquals(cloud.getInstanceCap(), Integer.MAX_VALUE); + assertEquals(cloud.getInstanceCapStr(), ""); + + final int cap = 3; + final String capStr = String.valueOf(cap); + cloud = new EC2Cloud( + "us-east-1", + true, + "abc", + "us-east-1", + null, + "key", + capStr, + Collections.emptyList(), + "roleArn", + "roleSessionName"); + assertEquals(cloud.getInstanceCap(), cap); + assertEquals(cloud.getInstanceCapStr(), capStr); + } + + @Test + public void testSpotInstanceCount() throws Exception { + final int numberOfSpotInstanceRequests = 105; + EC2Cloud cloud = Mockito.spy(new EC2Cloud( + "us-east-1", + true, + "abc", + "us-east-1", + null, + "key", + null, + Collections.emptyList(), + "roleArn", + "roleSessionName")); + Jenkins jenkinsMock = mock(Jenkins.class); + EC2SpotSlave spotSlaveMock = mock(EC2SpotSlave.class); + try (MockedStatic mocked = Mockito.mockStatic(Jenkins.class)) { + mocked.when(Jenkins::get).thenReturn(jenkinsMock); + Mockito.when(jenkinsMock.getNodes()).thenReturn(Collections.singletonList(spotSlaveMock)); + when(spotSlaveMock.getSpotRequest()).thenReturn(null); + when(spotSlaveMock.getSpotInstanceRequestId()).thenReturn("sir-id"); + + List instances = new ArrayList<>(); + for (int i = 0; i <= numberOfSpotInstanceRequests; i++) { + instances.add(new Instance() + .withInstanceId("id" + i) + .withTags(new Tag().withKey("jenkins_slave_type").withValue("spot"))); + } + + AmazonEC2FactoryMockImpl.instances = instances; + + Mockito.doReturn(AmazonEC2FactoryMockImpl.createAmazonEC2Mock(null)) + .when(cloud) + .connect(); + + Method countCurrentEC2SpotSlaves = EC2Cloud.class.getDeclaredMethod( + "countCurrentEC2SpotSlaves", SlaveTemplate.class, String.class, Set.class); + countCurrentEC2SpotSlaves.setAccessible(true); + Object[] params = {null, "jenkinsurl", new HashSet()}; + int n = (int) countCurrentEC2SpotSlaves.invoke(cloud, params); + + // Should equal number of spot instance requests + 1 for spot nodes not having a spot instance request + assertEquals(numberOfSpotInstanceRequests + 1, n); + } + } + + @Test + public void testCNPartition() { + assertEquals( + EC2Cloud.getAwsPartitionHostForService("cn-northwest-1", "ec2"), "ec2.cn-northwest-1.amazonaws.com.cn"); + assertEquals( + EC2Cloud.getAwsPartitionHostForService("cn-northwest-1", "s3"), "s3.cn-northwest-1.amazonaws.com.cn"); + } + + @Test + public void testNormalPartition() { + assertEquals(EC2Cloud.getAwsPartitionHostForService("us-east-1", "ec2"), "ec2.us-east-1.amazonaws.com"); + assertEquals(EC2Cloud.getAwsPartitionHostForService("us-east-1", "s3"), "s3.us-east-1.amazonaws.com"); + } + + @Test + public void testSlaveTemplateAddition() throws Exception { + EC2Cloud cloud = new EC2Cloud( + "us-east-1", + true, + "abc", + "us-east-1", + null, + "ghi", + "3", + Collections.emptyList(), + "roleArn", + "roleSessionName"); + SlaveTemplate orig = new SlaveTemplate( + "ami-123", + EC2AbstractSlave.TEST_ZONE, + null, + "default", + "foo", + InstanceType.M1Large, + false, + "ttt", + Node.Mode.NORMAL, + "description", + "bar", + "bbb", + "aaa", + "10", + "fff", + null, + EC2AbstractSlave.DEFAULT_JAVA_PATH, + "-Xmx1g", + false, + "subnet 456", + null, + null, + 0, + 0, + null, + "iamInstanceProfile", + true, + false, + "", + false, + "", + false, + false, + false, + ConnectionStrategy.PUBLIC_IP, + -1, + Collections.emptyList(), + null, + Tenancy.Default, + EbsEncryptRootVolume.DEFAULT, + EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED, + EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, + EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, + EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); + cloud.addTemplate(orig); + assertNotNull(cloud.getTemplate(orig.description)); + } + + @Test + public void testSlaveTemplateUpdate() throws Exception { + EC2Cloud cloud = new EC2Cloud( + "us-east-1", + true, + "abc", + "us-east-1", + null, + "ghi", + "3", + Collections.emptyList(), + "roleArn", + "roleSessionName"); + SlaveTemplate oldSlaveTemplate = new SlaveTemplate( + "ami-123", + EC2AbstractSlave.TEST_ZONE, + null, + "default", + "foo", + InstanceType.M1Large, + false, + "ttt", + Node.Mode.NORMAL, + "OldSlaveDescription", + "bar", + "bbb", + "aaa", + "10", + "fff", + null, + EC2AbstractSlave.DEFAULT_JAVA_PATH, + "-Xmx1g", + false, + "subnet 456", + null, + null, + 0, + 0, + null, + "iamInstanceProfile", + true, + false, + "", + false, + "", + false, + false, + false, + ConnectionStrategy.PUBLIC_IP, + -1, + Collections.emptyList(), + null, + Tenancy.Default, + EbsEncryptRootVolume.DEFAULT, + EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED, + EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, + EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, + EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); + SlaveTemplate secondSlaveTemplate = new SlaveTemplate( + "ami-123", + EC2AbstractSlave.TEST_ZONE, + null, + "default", + "foo", + InstanceType.M1Large, + false, + "ttt", + Node.Mode.NORMAL, + "SecondSlaveDescription", + "bar", + "bbb", + "aaa", + "10", + "fff", + null, + EC2AbstractSlave.DEFAULT_JAVA_PATH, + "-Xmx1g", + false, + "subnet 456", + null, + null, + 0, + 0, + null, + "iamInstanceProfile", + true, + false, + "", + false, + "", + false, + false, + false, + ConnectionStrategy.PUBLIC_IP, + -1, + Collections.emptyList(), + null, + Tenancy.Default, + EbsEncryptRootVolume.DEFAULT, + EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED, + EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, + EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, + EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); + cloud.addTemplate(oldSlaveTemplate); + cloud.addTemplate(secondSlaveTemplate); + SlaveTemplate newSlaveTemplate = new SlaveTemplate( + "ami-456", + EC2AbstractSlave.TEST_ZONE, + null, + "default", + "foo", + InstanceType.M1Large, + false, + "ttt", + Node.Mode.NORMAL, + "NewSlaveDescription", + "bar", + "bbb", + "aaa", + "10", + "fff", + null, + EC2AbstractSlave.DEFAULT_JAVA_PATH, + "-Xmx1g", + false, + "subnet 456", + null, + null, + 0, + 0, + null, + "iamInstanceProfile", + true, + false, + "", + false, + "", + false, + false, + false, + ConnectionStrategy.PUBLIC_IP, + -1, + Collections.emptyList(), + null, + Tenancy.Default, + EbsEncryptRootVolume.DEFAULT, + EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED, + EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, + EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, + EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); + int index = cloud.getTemplates().indexOf(oldSlaveTemplate); + + cloud.updateTemplate(newSlaveTemplate, "OldSlaveDescription"); + assertNull(cloud.getTemplate("OldSlaveDescription")); + assertNotNull(cloud.getTemplate("NewSlaveDescription")); + Assert.assertEquals(index, cloud.getTemplates().indexOf(newSlaveTemplate)); // assert order of templates is kept + } + + @Test + public void testReattachOrphanStoppedNodes() throws Exception { + /* Mocked items */ + EC2Cloud cloud = new EC2Cloud( + "us-east-1", + true, + "abc", + "us-east-1", + null, + "ghi", + "3", + Collections.emptyList(), + "roleArn", + "roleSessionName"); + EC2Cloud spyCloud = Mockito.spy(cloud); + AmazonEC2 mockEc2 = Mockito.mock(AmazonEC2.class); + Jenkins mockJenkins = Mockito.mock(Jenkins.class); + EC2AbstractSlave mockOrphanNode = Mockito.mock(EC2AbstractSlave.class); + SlaveTemplate mockSlaveTemplate = Mockito.mock(SlaveTemplate.class); + DescribeInstancesResult mockedDIResult = Mockito.mock(DescribeInstancesResult.class); + Instance mockedInstance = Mockito.mock(Instance.class); + List listOfMockedInstances = new ArrayList<>(); + listOfMockedInstances.add(mockedInstance); + + try (MockedStatic mocked = Mockito.mockStatic(Jenkins.class)) { + mocked.when(Jenkins::getInstanceOrNull).thenReturn(mockJenkins); + EC2AbstractSlave[] orphanNodes = {mockOrphanNode}; + Mockito.doReturn(Arrays.asList(orphanNodes)).when(mockSlaveTemplate).toSlaves(eq(listOfMockedInstances)); + List listOfJenkinsNodes = new ArrayList<>(); + + Mockito.doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) { + Node n = (Node) invocation.getArguments()[0]; + listOfJenkinsNodes.add(n); + return null; + } + }) + .when(mockJenkins) + .addNode(Mockito.any(Node.class)); + + Mockito.doReturn(null).when(mockOrphanNode).toComputer(); + Mockito.doReturn(false).when(mockOrphanNode).getStopOnTerminate(); + Mockito.doReturn(mockEc2).when(spyCloud).connect(); + Mockito.doReturn(mockedDIResult) + .when(mockSlaveTemplate) + .getDescribeInstanceResult(Mockito.any(AmazonEC2.class), eq(true)); + Mockito.doReturn(listOfMockedInstances) + .when(mockSlaveTemplate) + .findOrphansOrStopped(eq(mockedDIResult), Mockito.anyInt()); + Mockito.doNothing() + .when(mockSlaveTemplate) + .wakeOrphansOrStoppedUp(Mockito.any(AmazonEC2.class), eq(listOfMockedInstances)); + + /* Actual call to test*/ + spyCloud.attemptReattachOrphanOrStoppedNodes(mockJenkins, mockSlaveTemplate, 1); + + /* Checks */ + Mockito.verify(mockSlaveTemplate, times(1)) + .wakeOrphansOrStoppedUp(Mockito.any(AmazonEC2.class), eq(listOfMockedInstances)); + Node[] expectedNodes = {mockOrphanNode}; + assertArrayEquals(expectedNodes, listOfJenkinsNodes.toArray()); + } + } +} diff --git a/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java b/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java index e885d148e..8857c3fb2 100644 --- a/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java @@ -777,7 +777,7 @@ public void testRetentionDespiteIdleWithMinimumInstances() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); SSHCredentialHelper.assureSshCredentialAvailableThroughCredentialProviders("ghi"); - AmazonEC2Cloud cloud = new AmazonEC2Cloud( + EC2Cloud cloud = new EC2Cloud( "us-east-1", true, "abc", @@ -900,7 +900,7 @@ public void testRetentionDespiteIdleWithMinimumInstanceActiveTimeRange() throws MinimumInstanceChecker.clock = Clock.fixed(localDateTime.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); SSHCredentialHelper.assureSshCredentialAvailableThroughCredentialProviders("ghi"); - AmazonEC2Cloud cloud = new AmazonEC2Cloud( + EC2Cloud cloud = new EC2Cloud( "us-east-1", true, "abc", @@ -999,7 +999,7 @@ public void testRetentionIdleWithMinimumInstanceInactiveTimeRange() throws Excep MinimumInstanceChecker.clock = Clock.fixed(localDateTime.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); - AmazonEC2Cloud cloud = new AmazonEC2Cloud( + EC2Cloud cloud = new EC2Cloud( "us-east-1", true, "abc", @@ -1084,7 +1084,7 @@ public void testRetentionDespiteIdleWithMinimumInstanceActiveTimeRangeAfterMidni MinimumInstanceChecker.clock = Clock.fixed(localDateTime.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); SSHCredentialHelper.assureSshCredentialAvailableThroughCredentialProviders("ghi"); - AmazonEC2Cloud cloud = new AmazonEC2Cloud( + EC2Cloud cloud = new EC2Cloud( "us-east-1", true, "abc", @@ -1183,7 +1183,7 @@ public void testRetentionStopsAfterActiveRangeEnds() throws Exception { Clock.fixed(localDateTime.atZone(ZoneId.systemDefault()).toInstant(), ZoneId.systemDefault()); SSHCredentialHelper.assureSshCredentialAvailableThroughCredentialProviders("ghi"); - AmazonEC2Cloud cloud = new AmazonEC2Cloud( + EC2Cloud cloud = new EC2Cloud( "us-east-1", true, "abc", diff --git a/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java b/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java index 4ed16c698..17a45742e 100644 --- a/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java @@ -72,7 +72,7 @@ public void testMinimumNumberOfInstances() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); SSHCredentialHelper.assureSshCredentialAvailableThroughCredentialProviders("ghi"); - AmazonEC2Cloud cloud = new AmazonEC2Cloud( + EC2Cloud cloud = new EC2Cloud( "us-east-1", true, "abc", @@ -142,7 +142,7 @@ public void testMinimumNumberOfSpareInstances() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); SSHCredentialHelper.assureSshCredentialAvailableThroughCredentialProviders("ghi"); - AmazonEC2Cloud cloud = new AmazonEC2Cloud( + EC2Cloud cloud = new EC2Cloud( "us-east-1", true, "abc", diff --git a/src/test/java/hudson/plugins/ec2/EC2StepTest.java b/src/test/java/hudson/plugins/ec2/EC2StepTest.java index aad919976..0a2ac9251 100644 --- a/src/test/java/hudson/plugins/ec2/EC2StepTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2StepTest.java @@ -37,7 +37,7 @@ public class EC2StepTest { public JenkinsRule r = new JenkinsRule(); @Mock - private AmazonEC2Cloud cl; + private EC2Cloud cl; @Mock private SlaveTemplate st; diff --git a/src/test/java/hudson/plugins/ec2/EucalyptusTest.java b/src/test/java/hudson/plugins/ec2/EucalyptusTest.java deleted file mode 100644 index b24c7e40e..000000000 --- a/src/test/java/hudson/plugins/ec2/EucalyptusTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package hudson.plugins.ec2; - -import java.net.URL; -import org.htmlunit.html.HtmlForm; -import org.htmlunit.html.HtmlPage; -import org.junit.Rule; -import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; - -public class EucalyptusTest { - @Rule - public JenkinsRule r = new JenkinsRule(); - - @Test - public void configRoundTrip() throws Exception { - Eucalyptus cloud = new Eucalyptus( - "test", - new URL("https://ec2"), - new URL("https://s3"), - false, - null, - "test", - null, - "0", - null, - null, - null); - r.jenkins.clouds.add(cloud); - r.jenkins.save(); - JenkinsRule.WebClient wc = r.createWebClient(); - HtmlPage p = wc.goTo(cloud.getUrl() + "configure"); - HtmlForm f = p.getFormByName("config"); - r.submit(f); - r.assertEqualBeans( - cloud, - r.jenkins.getCloud("test"), - "name,ec2EndpointUrl,s3EndpointUrl,useInstanceProfileForCredentials,roleArn,roleSessionName,credentialsId,sshKeysCredentialsId,instanceCap,templates"); - } -} diff --git a/src/test/java/hudson/plugins/ec2/FileBasedSSHKeyTest.java b/src/test/java/hudson/plugins/ec2/FileBasedSSHKeyTest.java index 34f68c152..1179356b0 100644 --- a/src/test/java/hudson/plugins/ec2/FileBasedSSHKeyTest.java +++ b/src/test/java/hudson/plugins/ec2/FileBasedSSHKeyTest.java @@ -31,7 +31,7 @@ private static void verifyKeyFile(JenkinsRule r) throws Throwable { } private static void verifyCorrectKeyIsResolved(JenkinsRule r) throws Throwable { - AmazonEC2Cloud cloud = new AmazonEC2Cloud( + EC2Cloud cloud = new EC2Cloud( "us-east-1", true, "abc", @@ -43,7 +43,7 @@ private static void verifyCorrectKeyIsResolved(JenkinsRule r) throws Throwable { "roleArn", "roleSessionName"); r.jenkins.clouds.add(cloud); - AmazonEC2Cloud c = r.jenkins.clouds.get(AmazonEC2Cloud.class); + EC2Cloud c = r.jenkins.clouds.get(EC2Cloud.class); assertEquals("An unexpected key was returned!", c.resolvePrivateKey().getPrivateKey(), "hello, world!"); } } diff --git a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java index 82f0396a1..9ec683015 100644 --- a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java +++ b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java @@ -152,8 +152,7 @@ public void testConfigRoundtrip() throws Exception { List templates = new ArrayList<>(); templates.add(orig); - AmazonEC2Cloud ac = - new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); r.submit(getConfigForm(ac)); @@ -222,8 +221,7 @@ public void testConfigRoundtripWithCustomSSHHostKeyVerificationStrategy() throws List templates = new ArrayList<>(); templates.add(orig); - AmazonEC2Cloud ac = - new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); r.submit(getConfigForm(ac)); @@ -299,8 +297,7 @@ public void testConfigWithSpotBidPrice() throws Exception { List templates = new ArrayList<>(); templates.add(orig); - AmazonEC2Cloud ac = - new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); r.submit(getConfigForm(ac)); @@ -371,8 +368,7 @@ public void testSpotConfigWithoutBidPrice() throws Exception { List templates = new ArrayList<>(); templates.add(orig); - AmazonEC2Cloud ac = - new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); r.submit(getConfigForm(ac)); @@ -436,8 +432,7 @@ public void testWindowsConfigRoundTrip() throws Exception { List templates = new ArrayList<>(); templates.add(orig); - AmazonEC2Cloud ac = - new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); r.submit(getConfigForm(ac)); @@ -499,8 +494,7 @@ public void testUnixConfigRoundTrip() throws Exception { List templates = new ArrayList<>(); templates.add(orig); - AmazonEC2Cloud ac = - new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); r.submit(getConfigForm(ac)); @@ -570,18 +564,13 @@ public void testMinimumNumberOfInstancesActiveRangeConfig() throws Exception { List templates = new ArrayList<>(); templates.add(slaveTemplate); - AmazonEC2Cloud ac = - new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); r.configRoundtrip(); - MinimumNumberOfInstancesTimeRangeConfig stored = r.jenkins - .clouds - .get(AmazonEC2Cloud.class) - .getTemplates() - .get(0) - .getMinimumNumberOfInstancesTimeRangeConfig(); + MinimumNumberOfInstancesTimeRangeConfig stored = + r.jenkins.clouds.get(EC2Cloud.class).getTemplates().get(0).getMinimumNumberOfInstancesTimeRangeConfig(); Assert.assertNotNull(stored); Assert.assertEquals("11:00", stored.getMinimumNoInstancesActiveTimeRangeFrom()); Assert.assertEquals("15:00", stored.getMinimumNoInstancesActiveTimeRangeTo()); @@ -921,7 +910,7 @@ public void provisionSpotFallsBackToOndemandWhenSpotQuotaExceeded() throws Excep } private AmazonEC2 setupTestForProvisioning(SlaveTemplate template) throws Exception { - AmazonEC2Cloud mockedCloud = mock(AmazonEC2Cloud.class); + EC2Cloud mockedCloud = mock(EC2Cloud.class); AmazonEC2 mockedEC2 = mock(AmazonEC2.class); EC2PrivateKey mockedPrivateKey = mock(EC2PrivateKey.class); KeyPair mockedKeyPair = new KeyPair(); @@ -1029,8 +1018,7 @@ public void testMacConfig() throws Exception { List templates = new ArrayList<>(); templates.add(orig); - AmazonEC2Cloud ac = - new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); r.submit(getConfigForm(ac)); @@ -1134,12 +1122,12 @@ public void testAgentName() { List templates = new ArrayList<>(); templates.add(broken); templates.add(working); - AmazonEC2Cloud brokenCloud = - new AmazonEC2Cloud("broken/cloud", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud brokenCloud = + new EC2Cloud("broken/cloud", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); assertThat(broken.getSlaveName("test"), is("test")); assertThat(working.getSlaveName("test"), is("test")); - AmazonEC2Cloud workingCloud = - new AmazonEC2Cloud("cloud", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud workingCloud = + new EC2Cloud("cloud", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); assertThat(broken.getSlaveName("test"), is("test")); assertThat(working.getSlaveName("test"), is("EC2 (cloud) - working (test)")); } @@ -1195,8 +1183,7 @@ public void testMetadataV2Config() throws Exception { List templates = Collections.singletonList(orig); - AmazonEC2Cloud ac = - new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); r.jenkins.clouds.add(ac); r.submit(r.createWebClient().goTo("configure").getFormByName("config")); @@ -1453,7 +1440,7 @@ public void provisionOnDemandSetsMetadataDefaultOptions() throws Exception { assertEquals(metadataOptionsRequest.getHttpPutResponseHopLimit(), Integer.valueOf(1)); } - private HtmlForm getConfigForm(AmazonEC2Cloud ac) throws IOException, SAXException { + private HtmlForm getConfigForm(EC2Cloud ac) throws IOException, SAXException { return r.createWebClient().goTo(ac.getUrl() + "configure").getFormByName("config"); } } diff --git a/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java b/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java index 32f3f99e4..3844c5991 100644 --- a/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java +++ b/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java @@ -42,7 +42,7 @@ public class TemplateLabelsTest { @Rule public JenkinsRule r = new JenkinsRule(); - private AmazonEC2Cloud ac; + private EC2Cloud ac; private final String LABEL1 = "label1"; private final String LABEL2 = "label2"; @@ -105,7 +105,7 @@ private void setUpCloud(String label, Node.Mode mode) throws Exception { List templates = new ArrayList<>(); templates.add(template); - ac = new AmazonEC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); } @Test diff --git a/src/test/java/hudson/plugins/ec2/util/AmazonEC2FactoryMockImpl.java b/src/test/java/hudson/plugins/ec2/util/AmazonEC2FactoryMockImpl.java index f7dd1c87b..7d04514e2 100644 --- a/src/test/java/hudson/plugins/ec2/util/AmazonEC2FactoryMockImpl.java +++ b/src/test/java/hudson/plugins/ec2/util/AmazonEC2FactoryMockImpl.java @@ -40,7 +40,6 @@ import com.amazonaws.services.ec2.model.TerminateInstancesResult; import edu.umd.cs.findbugs.annotations.Nullable; import hudson.Extension; -import hudson.plugins.ec2.AmazonEC2Cloud; import hudson.plugins.ec2.EC2Cloud; import java.net.URL; import java.util.ArrayList; @@ -189,7 +188,7 @@ private static void mockDescribeKeyPairs(AmazonEC2Client mock) { KeyPairInfo keyPairInfo = new KeyPairInfo(); keyPairInfo.setKeyFingerprint(Jenkins.get() .clouds - .get(AmazonEC2Cloud.class) + .get(EC2Cloud.class) .resolvePrivateKey() .getFingerprint()); return new DescribeKeyPairsResult().withKeyPairs(keyPairInfo);