From 23e43b2c8dfb89bc4d8257303ec3f59cbc789e09 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Fri, 15 Nov 2013 12:59:19 -0800 Subject: [PATCH] JCLOUDS-381. Allow explicit naming of nodes via TemplateOptions. --- .../functions/ServerToNodeMetadata.java | 3 +- .../CloudServersComputeServiceAdapter.java | 2 + .../options/CloudSigmaTemplateOptions.java | 16 ++ .../VirtualMachineToNodeMetadata.java | 4 +- .../options/CloudStackTemplateOptions.java | 16 ++ .../CloudStackComputeServiceAdapter.java | 2 + ...dStackComputeServiceAdapterExpectTest.java | 183 +++++++++--------- .../ec2/compute/EC2ComputeService.java | 23 ++- .../compute/options/EC2TemplateOptions.java | 16 ++ .../compute/EC2ComputeServiceExpectTest.java | 57 +++++- .../describe_instances_running-named.xml | 74 +++++++ .../ElasticStackComputeServiceAdapter.java | 2 + .../functions/ServerInfoToNodeMetadata.java | 3 +- .../functions/ServerInZoneToNodeMetadata.java | 3 +- .../compute/options/NovaTemplateOptions.java | 17 ++ ...sWithGroupEncodedIntoNameThenAddToSet.java | 2 + .../compute/NovaComputeServiceExpectTest.java | 9 +- .../compute/functions/VAppToNodeMetadata.java | 15 +- .../options/VCloudTemplateOptions.java | 16 ++ ...IntoNameThenCustomizeDeployAndPowerOn.java | 1 + .../compute/options/TemplateOptions.java | 55 +++++- .../reference/ComputeServiceConstants.java | 2 +- ...sWithGroupEncodedIntoNameThenAddToSet.java | 18 +- .../compute/util/ComputeServiceUtils.java | 11 +- .../StubComputeServiceIntegrationTest.java | 6 + .../internal/BaseComputeServiceLiveTest.java | 28 ++- .../compute/options/TemplateOptionsTest.java | 12 ++ .../ec2/compute/AWSEC2TemplateOptions.java | 16 ++ .../compute/GleSYSComputeServiceAdapter.java | 1 + .../ServerDetailsToNodeMetadata.java | 14 +- .../options/GleSYSTemplateOptions.java | 16 ++ .../options/GoGridTemplateOptions.java | 16 ++ .../options/SoftLayerTemplateOptions.java | 16 ++ 33 files changed, 551 insertions(+), 124 deletions(-) create mode 100644 apis/ec2/src/test/resources/describe_instances_running-named.xml diff --git a/apis/cloudservers/src/main/java/org/jclouds/cloudservers/compute/functions/ServerToNodeMetadata.java b/apis/cloudservers/src/main/java/org/jclouds/cloudservers/compute/functions/ServerToNodeMetadata.java index 014019c25a..fcf1b8c05f 100644 --- a/apis/cloudservers/src/main/java/org/jclouds/cloudservers/compute/functions/ServerToNodeMetadata.java +++ b/apis/cloudservers/src/main/java/org/jclouds/cloudservers/compute/functions/ServerToNodeMetadata.java @@ -18,6 +18,7 @@ package org.jclouds.cloudservers.compute.functions; import static com.google.common.base.Preconditions.checkNotNull; import static org.jclouds.compute.util.ComputeServiceUtils.addMetadataAndParseTagsFromCommaDelimitedValue; +import static org.jclouds.compute.util.ComputeServiceUtils.groupFromMapOrName; import java.util.Map; import java.util.NoSuchElementException; @@ -111,7 +112,7 @@ public class ServerToNodeMetadata implements Function { builder.location(new LocationBuilder().scope(LocationScope.HOST).id(from.getHostId()).description( from.getHostId()).parent(location.get()).build()); addMetadataAndParseTagsFromCommaDelimitedValue(builder, from.getMetadata()); - builder.group(nodeNamingConvention.groupInUniqueNameOrNull(from.getName())); + builder.group(groupFromMapOrName(from.getMetadata(), from.getName(), nodeNamingConvention)); builder.imageId(from.getImageId() + ""); builder.operatingSystem(parseOperatingSystem(from)); builder.hardware(parseHardware(from)); diff --git a/apis/cloudservers/src/main/java/org/jclouds/cloudservers/compute/strategy/CloudServersComputeServiceAdapter.java b/apis/cloudservers/src/main/java/org/jclouds/cloudservers/compute/strategy/CloudServersComputeServiceAdapter.java index 52ef460be5..2b70a71e8a 100644 --- a/apis/cloudservers/src/main/java/org/jclouds/cloudservers/compute/strategy/CloudServersComputeServiceAdapter.java +++ b/apis/cloudservers/src/main/java/org/jclouds/cloudservers/compute/strategy/CloudServersComputeServiceAdapter.java @@ -34,6 +34,7 @@ import org.jclouds.cloudservers.domain.Server; import org.jclouds.cloudservers.options.ListOptions; import org.jclouds.compute.ComputeServiceAdapter; import org.jclouds.compute.domain.Template; +import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.domain.Location; import org.jclouds.domain.LoginCredentials; @@ -59,6 +60,7 @@ public class CloudServersComputeServiceAdapter implements ComputeServiceAdapter< @Override public NodeAndInitialCredentials createNodeWithGroupEncodedIntoName(String group, String name, Template template) { + template.getOptions().userMetadata(ComputeServiceConstants.NODE_GROUP_KEY, group); Server server = client .createServer(name, Integer.parseInt(template.getImage().getProviderId()), Integer.parseInt(template .getHardware().getProviderId()), withMetadata(metadataAndTagsAsCommaDelimitedValue(template.getOptions()))); diff --git a/apis/cloudsigma/src/main/java/org/jclouds/cloudsigma/compute/options/CloudSigmaTemplateOptions.java b/apis/cloudsigma/src/main/java/org/jclouds/cloudsigma/compute/options/CloudSigmaTemplateOptions.java index b50f1fff94..137adea250 100644 --- a/apis/cloudsigma/src/main/java/org/jclouds/cloudsigma/compute/options/CloudSigmaTemplateOptions.java +++ b/apis/cloudsigma/src/main/java/org/jclouds/cloudsigma/compute/options/CloudSigmaTemplateOptions.java @@ -106,6 +106,14 @@ public class CloudSigmaTemplateOptions extends TemplateOptions implements Clonea return CloudSigmaTemplateOptions.class.cast(options.userMetadata(userMetadata)); } + /** + * @see TemplateOptions#nodeNames(Iterable) + */ + public static CloudSigmaTemplateOptions nodeNames(Iterable nodeNames) { + CloudSigmaTemplateOptions options = new CloudSigmaTemplateOptions(); + return CloudSigmaTemplateOptions.class.cast(options.nodeNames(nodeNames)); + } + public static CloudSigmaTemplateOptions overrideLoginUser(String user) { CloudSigmaTemplateOptions options = new CloudSigmaTemplateOptions(); return options.overrideLoginUser(user); @@ -262,6 +270,14 @@ public class CloudSigmaTemplateOptions extends TemplateOptions implements Clonea return CloudSigmaTemplateOptions.class.cast(super.userMetadata(key, value)); } + /** + * {@inheritDoc} + */ + @Override + public CloudSigmaTemplateOptions nodeNames(Iterable nodeNames) { + return CloudSigmaTemplateOptions.class.cast(super.nodeNames(nodeNames)); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/functions/VirtualMachineToNodeMetadata.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/functions/VirtualMachineToNodeMetadata.java index c0c373e57b..d61aaa2fa6 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/functions/VirtualMachineToNodeMetadata.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/functions/VirtualMachineToNodeMetadata.java @@ -104,7 +104,9 @@ public class VirtualMachineToNodeMetadata implements Function() { diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptions.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptions.java index 14e72d40fa..93fcdf2dee 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptions.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/options/CloudStackTemplateOptions.java @@ -401,6 +401,14 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea CloudStackTemplateOptions options = new CloudStackTemplateOptions(); return CloudStackTemplateOptions.class.cast(options.userMetadata(key, value)); } + + /** + * @see TemplateOptions#nodeNames(Iterable) + */ + public static CloudStackTemplateOptions nodeNames(Iterable nodeNames) { + CloudStackTemplateOptions options = new CloudStackTemplateOptions(); + return CloudStackTemplateOptions.class.cast(options.nodeNames(nodeNames)); + } } // methods that only facilitate returning the correct object type @@ -452,4 +460,12 @@ public class CloudStackTemplateOptions extends TemplateOptions implements Clonea public CloudStackTemplateOptions userMetadata(String key, String value) { return CloudStackTemplateOptions.class.cast(super.userMetadata(key, value)); } + + /** + * {@inheritDoc} + */ + @Override + public CloudStackTemplateOptions nodeNames(Iterable nodeNames) { + return CloudStackTemplateOptions.class.cast(super.nodeNames(nodeNames)); + } } diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java index ecfe215616..a04b9fe5f0 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/strategy/CloudStackComputeServiceAdapter.java @@ -165,6 +165,8 @@ public class CloudStackComputeServiceAdapter implements OptionsConverter optionsConverter = optionsConverters.get(zone.getNetworkType()); options = optionsConverter.apply(templateOptions, networks, zoneId, options); + options.group(group); + if (templateOptions.getIpOnDefaultNetwork() != null) { options.ipOnDefaultNetwork(templateOptions.getIpOnDefaultNetwork()); } diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java index a79e29b6b4..be888cc787 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackComputeServiceAdapterExpectTest.java @@ -94,19 +94,20 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom public void testCreateNodeWithGroupEncodedIntoName() { HttpRequest deployVM = HttpRequest.builder().method("GET") - .endpoint("http://localhost:8080/client/api") - .addQueryParam("response", "json") - .addQueryParam("command", "deployVirtualMachine") - .addQueryParam("zoneid", "1") - .addQueryParam("serviceofferingid", "1") - .addQueryParam("templateid", "4") - .addQueryParam("displayname", "test-e92") - .addQueryParam("name", "test-e92") - .addQueryParam("networkids", "204") - .addQueryParam("apiKey", "APIKEY") - .addQueryParam("signature", "wJ%2BiflOS3am5qcjQOd8Y/Pw8/Dc%3D") - .addHeader("Accept", "application/json") - .build(); + .endpoint("http://localhost:8080/client/api") + .addQueryParam("response", "json") + .addQueryParam("command", "deployVirtualMachine") + .addQueryParam("zoneid", "1") + .addQueryParam("serviceofferingid", "1") + .addQueryParam("templateid", "4") + .addQueryParam("displayname", "test-e92") + .addQueryParam("name", "test-e92") + .addQueryParam("networkids", "204") + .addQueryParam("group", "test") + .addQueryParam("apiKey", "APIKEY") + .addQueryParam("signature", "M2Wx0CgOeH9vYHhbcbblwziqpwI%3D") + .addHeader("Accept", "application/json") + .build(); Map requestResponseMap = ImmutableMap. builder() .put(listTemplates, listTemplatesResponse) @@ -136,20 +137,21 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom public void testCreateNodeWithGroupEncodedIntoNameWithKeyPair() throws IOException { HttpRequest deployVM = HttpRequest.builder().method("GET") - .endpoint("http://localhost:8080/client/api") - .addQueryParam("response", "json") - .addQueryParam("command", "deployVirtualMachine") - .addQueryParam("zoneid", "1") - .addQueryParam("serviceofferingid", "1") - .addQueryParam("templateid", "4") - .addQueryParam("displayname", "test-e92") - .addQueryParam("name", "test-e92") - .addQueryParam("networkids", "204") - .addQueryParam("keypair", "mykeypair") - .addQueryParam("apiKey", "APIKEY") - .addQueryParam("signature", "hI/U4cWXdU6KTZKbJvzPCmOpGmU%3D") - .addHeader("Accept", "application/json") - .build(); + .endpoint("http://localhost:8080/client/api") + .addQueryParam("response", "json") + .addQueryParam("command", "deployVirtualMachine") + .addQueryParam("zoneid", "1") + .addQueryParam("serviceofferingid", "1") + .addQueryParam("templateid", "4") + .addQueryParam("displayname", "test-e92") + .addQueryParam("name", "test-e92") + .addQueryParam("networkids", "204") + .addQueryParam("group", "test") + .addQueryParam("keypair", "mykeypair") + .addQueryParam("apiKey", "APIKEY") + .addQueryParam("signature", "D3qQlTNjxrBXeG82C7YPrwU1jMc%3D") + .addHeader("Accept", "application/json") + .build(); Map requestResponseMap = ImmutableMap. builder() .put(listTemplates, listTemplatesResponse) @@ -182,20 +184,21 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom public void testCreateNodeWithGroupEncodedIntoNameWithGenerateKeyPair() throws IOException { HttpRequest deployVM = HttpRequest.builder().method("GET") - .endpoint("http://localhost:8080/client/api") - .addQueryParam("response", "json") - .addQueryParam("command", "deployVirtualMachine") - .addQueryParam("zoneid", "1") - .addQueryParam("serviceofferingid", "1") - .addQueryParam("templateid", "4") - .addQueryParam("displayname", "test-e92") - .addQueryParam("name", "test-e92") - .addQueryParam("networkids", "204") - .addQueryParam("keypair", "jclouds-test") - .addQueryParam("apiKey", "APIKEY") - .addQueryParam("signature", "4M9C8IjohDDKFMAXQSX3mjXpYvM%3D") - .addHeader("Accept", "application/json") - .build(); + .endpoint("http://localhost:8080/client/api") + .addQueryParam("response", "json") + .addQueryParam("command", "deployVirtualMachine") + .addQueryParam("zoneid", "1") + .addQueryParam("serviceofferingid", "1") + .addQueryParam("templateid", "4") + .addQueryParam("displayname", "test-e92") + .addQueryParam("name", "test-e92") + .addQueryParam("networkids", "204") + .addQueryParam("group", "test") + .addQueryParam("keypair", "jclouds-test") + .addQueryParam("apiKey", "APIKEY") + .addQueryParam("signature", "kfU/J/REa4DdYj0b/pSjuB3h3Qc%3D") + .addHeader("Accept", "application/json") + .build(); Map requestResponseMap = ImmutableMap. builder() .put(listTemplates, listTemplatesResponse) @@ -227,20 +230,21 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairDefaultSecurityGroup() throws IOException { HttpRequest deployVM = HttpRequest.builder().method("GET") - .endpoint("http://localhost:8080/client/api") - .addQueryParam("response", "json") - .addQueryParam("command", "deployVirtualMachine") - .addQueryParam("zoneid", "2") - .addQueryParam("serviceofferingid", "1") - .addQueryParam("templateid", "241") - .addQueryParam("displayname", "test-e92") - .addQueryParam("name", "test-e92") - .addQueryParam("networkids", "204") - .addQueryParam("keypair", "mykeypair") - .addQueryParam("apiKey", "APIKEY") - .addQueryParam("signature", "Ar2B/ZVxMO2078cP0XliWWR4cQ0%3D") - .addHeader("Accept", "application/json") - .build(); + .endpoint("http://localhost:8080/client/api") + .addQueryParam("response", "json") + .addQueryParam("command", "deployVirtualMachine") + .addQueryParam("zoneid", "2") + .addQueryParam("serviceofferingid", "1") + .addQueryParam("templateid", "241") + .addQueryParam("displayname", "test-e92") + .addQueryParam("name", "test-e92") + .addQueryParam("networkids", "204") + .addQueryParam("group", "test") + .addQueryParam("keypair", "mykeypair") + .addQueryParam("apiKey", "APIKEY") + .addQueryParam("signature", "5qkUSGh0y%2BP/t04/j3%2BEN9PAeFI%3D") + .addHeader("Accept", "application/json") + .build(); Map requestResponseMap = ImmutableMap. builder() .put(listTemplates, listTemplatesResponse) @@ -285,11 +289,12 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom .addQueryParam("displayname", "test-e92") .addQueryParam("name", "test-e92") .addQueryParam("networkids", "204") + .addQueryParam("group", "test") .addQueryParam("keypair", "mykeypair") .addQueryParam("diskofferingid", "5678") .addQueryParam("size", "10") .addQueryParam("apiKey", "APIKEY") - .addQueryParam("signature", "FWWCEpsrbbjxiqoQve302rrfOjI%3D") + .addQueryParam("signature", "lDzBXtVKCktueskyI/haID9ohJU%3D") .addHeader("Accept", "application/json") .build(); @@ -330,21 +335,22 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairGenerateSecurityGroup() throws IOException { HttpRequest deployVM = HttpRequest.builder().method("GET") - .endpoint("http://localhost:8080/client/api") - .addQueryParam("response", "json") - .addQueryParam("command", "deployVirtualMachine") - .addQueryParam("zoneid", "2") - .addQueryParam("serviceofferingid", "1") - .addQueryParam("templateid", "241") - .addQueryParam("displayname", "test-e92") - .addQueryParam("name", "test-e92") - .addQueryParam("networkids", "204") - .addQueryParam("keypair", "mykeypair") - .addQueryParam("securitygroupids", "30") - .addQueryParam("apiKey", "APIKEY") - .addQueryParam("signature", "yNAiMYw3RstNj979udttALOHxfU%3D") - .addHeader("Accept", "application/json") - .build(); + .endpoint("http://localhost:8080/client/api") + .addQueryParam("response", "json") + .addQueryParam("command", "deployVirtualMachine") + .addQueryParam("zoneid", "2") + .addQueryParam("serviceofferingid", "1") + .addQueryParam("templateid", "241") + .addQueryParam("displayname", "test-e92") + .addQueryParam("name", "test-e92") + .addQueryParam("networkids", "204") + .addQueryParam("group", "test") + .addQueryParam("keypair", "mykeypair") + .addQueryParam("securitygroupids", "30") + .addQueryParam("apiKey", "APIKEY") + .addQueryParam("signature", "rz8V/tMk/UbxUhNqp7Bq3CrSg/k%3D") + .addHeader("Accept", "application/json") + .build(); Map requestResponseMap = ImmutableMap. builder() .put(listTemplates, listTemplatesResponse) @@ -386,23 +392,24 @@ public class CloudStackComputeServiceAdapterExpectTest extends BaseCloudStackCom public void testCreateNodeWithGroupEncodedIntoNameWithKeyPairAssignedToAccountAndDomain() throws IOException { HttpRequest deployVM = HttpRequest.builder().method("GET") - .endpoint("http://localhost:8080/client/api") - .addQueryParam("response", "json") - .addQueryParam("command", "deployVirtualMachine") - .addQueryParam("zoneid", "1") - .addQueryParam("serviceofferingid", "1") - .addQueryParam("templateid", "4") - .addQueryParam("displayname", "test-e92") - .addQueryParam("name", "test-e92") - .addQueryParam("account", "account") // - .addQueryParam("domainid", "domainId") // - .addQueryParam("networkids", "204") - .addQueryParam("keypair", "mykeypair") - .addQueryParam("apiKey", "APIKEY") - .addQueryParam("signature", "ly5Pip8ICOoVTmNLdDBTc3gbKlA%3D") - .addHeader("Accept", "application/json") - .build(); - + .endpoint("http://localhost:8080/client/api") + .addQueryParam("response", "json") + .addQueryParam("command", "deployVirtualMachine") + .addQueryParam("zoneid", "1") + .addQueryParam("serviceofferingid", "1") + .addQueryParam("templateid", "4") + .addQueryParam("displayname", "test-e92") + .addQueryParam("name", "test-e92") + .addQueryParam("account", "account") // + .addQueryParam("domainid", "domainId") // + .addQueryParam("networkids", "204") + .addQueryParam("group", "test") + .addQueryParam("keypair", "mykeypair") + .addQueryParam("apiKey", "APIKEY") + .addQueryParam("signature", "hGV6gZZakwvNKhTJurkm48%2Bzgso%3D") + .addHeader("Accept", "application/json") + .build(); + Map requestResponseMap = ImmutableMap. builder() .put(listTemplates, listTemplatesResponse) .put(listOsTypes, listOsTypesResponse) diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java index 248102e5f9..078270a4e8 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/EC2ComputeService.java @@ -32,6 +32,7 @@ import static org.jclouds.ec2.reference.EC2Constants.PROPERTY_EC2_GENERATE_INSTA import static org.jclouds.ec2.util.Tags.resourceToTagsAsMap; import static org.jclouds.util.Predicates2.retry; +import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -96,6 +97,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap.Builder; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.util.concurrent.ListeningExecutorService; @@ -153,8 +155,9 @@ public class EC2ComputeService extends BaseComputeService { if (client.getTagApiForRegion(region).isPresent()) { Map common = metadataAndTagsAsValuesOfEmptyString(template.getOptions()); - if (common.size() > 0 || generateInstanceNames) { - return addTagsToInstancesInRegion(common, nodes, region, group); + if (generateInstanceNames || !common.isEmpty() || !template.getOptions().getNodeNames().isEmpty()) { + return addTagsAndNamesToInstancesInRegion(common, template.getOptions().getNodeNames(), + nodes, region, group); } } return nodes; @@ -167,17 +170,23 @@ public class EC2ComputeService extends BaseComputeService { } }; - private Set addTagsToInstancesInRegion(Map common, Set input, - String region, String group) { + private Set addTagsAndNamesToInstancesInRegion(Map common, Set nodeNames, + Set input, String region, + String group) { Map instancesById = Maps.uniqueIndex(input, instanceId); ImmutableSet.Builder builder = ImmutableSet. builder(); + if (generateInstanceNames && !common.containsKey("Name")) { for (Map.Entry entry : instancesById.entrySet()) { String id = entry.getKey(); - NodeMetadata instance = entry.getValue(); - + String name; + if (!nodeNames.isEmpty()) { + name = Iterables.get(nodeNames, 0); + } else { + name = id.replaceAll(".*-", group + "-"); + } Map tags = ImmutableMap. builder().putAll(common) - .put("Name", id.replaceAll(".*-", group + "-")).build(); + .put("Name", name).build(); logger.debug(">> applying tags %s to instance %s in region %s", tags, id, region); client.getTagApiForRegion(region).get().applyToResources(tags, ImmutableSet.of(id)); builder.add(addTagsForInstance(tags, instancesById.get(id))); diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/options/EC2TemplateOptions.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/options/EC2TemplateOptions.java index 0ecf4aaf09..8eea26589a 100644 --- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/options/EC2TemplateOptions.java +++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/options/EC2TemplateOptions.java @@ -324,6 +324,14 @@ public class EC2TemplateOptions extends TemplateOptions implements Cloneable { EC2TemplateOptions options = new EC2TemplateOptions(); return EC2TemplateOptions.class.cast(options.userMetadata(userMetadata)); } + + /** + * @see TemplateOptions#nodeNames(Iterable) + */ + public static EC2TemplateOptions nodeNames(Iterable nodeNames) { + EC2TemplateOptions options = new EC2TemplateOptions(); + return EC2TemplateOptions.class.cast(options.nodeNames(nodeNames)); + } public static EC2TemplateOptions overrideLoginUser(String user) { EC2TemplateOptions options = new EC2TemplateOptions(); @@ -521,6 +529,14 @@ public class EC2TemplateOptions extends TemplateOptions implements Cloneable { return EC2TemplateOptions.class.cast(super.userMetadata(key, value)); } + /** + * {@inheritDoc} + */ + @Override + public EC2TemplateOptions nodeNames(Iterable nodeNames) { + return EC2TemplateOptions.class.cast(super.nodeNames(nodeNames)); + } + /** * {@inheritDoc} */ diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/compute/EC2ComputeServiceExpectTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/compute/EC2ComputeServiceExpectTest.java index 61f3c50f13..24e03cf6d3 100644 --- a/apis/ec2/src/test/java/org/jclouds/ec2/compute/EC2ComputeServiceExpectTest.java +++ b/apis/ec2/src/test/java/org/jclouds/ec2/compute/EC2ComputeServiceExpectTest.java @@ -15,10 +15,13 @@ * limitations under the License. */ package org.jclouds.ec2.compute; + import static org.jclouds.ec2.compute.options.EC2TemplateOptions.Builder.blockUntilRunning; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; +import javax.ws.rs.core.MediaType; + import org.jclouds.compute.ComputeService; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.ec2.compute.internal.BaseEC2ComputeServiceExpectTest; @@ -28,6 +31,7 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; /** @@ -57,12 +61,63 @@ public class EC2ComputeServiceExpectTest extends BaseEC2ComputeServiceExpectTest ComputeService apiThatCreatesNode = requestsSendResponses(requestResponseMap.build()); NodeMetadata node = Iterables.getOnlyElement(apiThatCreatesNode.createNodesInGroup("test", 1, - blockUntilRunning(false).overrideLoginUser("ec2-user"))); + blockUntilRunning(false).overrideLoginUser("ec2-user"))); assertEquals(node.getCredentials().getUser(), "ec2-user"); System.out.println(node.getImageId()); assertNotNull(node.getCredentials().getPrivateKey()); } + public void testCreateNodeWithSpecifiedName() throws Exception { + HttpRequest createNamedTagsRequest = + formSigner.filter(HttpRequest.builder() + .method("POST") + .endpoint("https://ec2.us-east-1.amazonaws.com/") + .addHeader("Host", "ec2.us-east-1.amazonaws.com") + .payload( + payloadFromStringWithContentType( + "Action=CreateTags" + + "&ResourceId.1=i-2baa5550" + + "&Signature=Trp5e5%2BMqeBeBZbLYa9s9gxahQ9nkx6ETfsGl82IV8Y%3D" + + "&SignatureMethod=HmacSHA256" + + "&SignatureVersion=2" + + "&Tag.1.Key=Name" + + "&Tag.1.Value=test-node" + + "&Timestamp=2012-04-16T15%3A54%3A08.897Z" + + "&Version=2010-08-31" + + "&AWSAccessKeyId=identity", + "application/x-www-form-urlencoded")) + .build()); + + HttpResponse describeNamedInstanceResponse = + HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType( + "/describe_instances_running-named.xml", MediaType.APPLICATION_XML)).build(); + + + Builder requestResponseMap = ImmutableMap. builder(); + requestResponseMap.put(describeRegionsRequest, describeRegionsResponse); + requestResponseMap.put(describeAvailabilityZonesRequest, describeAvailabilityZonesResponse); + requestResponseMap.put(describeImagesRequest, describeImagesResponse); + requestResponseMap.put(createKeyPairRequest, createKeyPairResponse); + requestResponseMap.put(createSecurityGroupRequest, createSecurityGroupResponse); + requestResponseMap.put(describeSecurityGroupRequest, describeSecurityGroupResponse); + requestResponseMap.put(authorizeSecurityGroupIngressRequest22, authorizeSecurityGroupIngressResponse); + requestResponseMap.put(authorizeSecurityGroupIngressRequestGroup, authorizeSecurityGroupIngressResponse); + requestResponseMap.put(runInstancesRequest, runInstancesResponse); + requestResponseMap.put(describeInstanceRequest, describeNamedInstanceResponse); + requestResponseMap.put(describeInstanceMultiIdsRequest, describeInstanceMultiIdsResponse); + requestResponseMap.put(describeImageRequest, describeImagesResponse); + requestResponseMap.put(createNamedTagsRequest, createTagsResponse); + + ComputeService apiThatCreatesNode = requestsSendResponses(requestResponseMap.build()); + + NodeMetadata node = Iterables.getOnlyElement(apiThatCreatesNode.createNodesInGroup("test", 1, + blockUntilRunning(false).overrideLoginUser("ec2-user").nodeNames(ImmutableSet.of("test-node")))); + assertEquals(node.getCredentials().getUser(), "ec2-user"); + assertNotNull(node.getCredentials().getPrivateKey()); + assertEquals(node.getName(), "test-node"); + } + //FIXME - issue-1051 @Test(enabled = false) public void testCreateNodeWithGeneratedKeyPairAndOverriddenLoginUserWithTemplateBuilder() throws Exception { diff --git a/apis/ec2/src/test/resources/describe_instances_running-named.xml b/apis/ec2/src/test/resources/describe_instances_running-named.xml new file mode 100644 index 0000000000..4f46adae0c --- /dev/null +++ b/apis/ec2/src/test/resources/describe_instances_running-named.xml @@ -0,0 +1,74 @@ + + + f6d3252e-35e5-4ef5-b2c5-62da95dd829b + + + r-205ad944 + 993194456877 + + + sg-3c6ef654 + jclouds#mygroup2 + + + + + i-2baa5550 + ami-aecd60c7 + + 16 + running + + ip-10-28-89-195.ec2.internal + ec2-50-16-1-166.compute-1.amazonaws.com + + jclouds#mygroup2#81 + 0 + + t1.micro + 2012-08-02T04:28:30.000Z + + us-east-1e + + default + + aki-88aa75e1 + + disabled + + 10.28.89.195 + 50.16.1.166 + + + sg-3c6ef654 + jclouds#mygroup2 + + + x86_64 + ebs + /dev/sda1 + + + /dev/sda1 + + vol-f2d7c993 + attached + 2012-08-02T04:28:56.000Z + true + + + + paravirtual + + + + Name + test-node + + + xen + + + + + \ No newline at end of file diff --git a/apis/elasticstack/src/main/java/org/jclouds/elasticstack/compute/ElasticStackComputeServiceAdapter.java b/apis/elasticstack/src/main/java/org/jclouds/elasticstack/compute/ElasticStackComputeServiceAdapter.java index 94a46b9b8f..96ebaec1b4 100644 --- a/apis/elasticstack/src/main/java/org/jclouds/elasticstack/compute/ElasticStackComputeServiceAdapter.java +++ b/apis/elasticstack/src/main/java/org/jclouds/elasticstack/compute/ElasticStackComputeServiceAdapter.java @@ -120,6 +120,8 @@ public class ElasticStackComputeServiceAdapter implements throw new IllegalStateException("could not image drive in time!"); } + template.getOptions().userMetadata(ComputeServiceConstants.NODE_GROUP_KEY, tag); + Server toCreate = small(name, drive.getUuid(), defaultVncPassword).mem(template.getHardware().getRam()) .cpu((int) (template.getHardware().getProcessors().get(0).getSpeed())) .tags(template.getOptions().getTags()).userMetadata(template.getOptions().getUserMetadata()).build(); diff --git a/apis/elasticstack/src/main/java/org/jclouds/elasticstack/compute/functions/ServerInfoToNodeMetadata.java b/apis/elasticstack/src/main/java/org/jclouds/elasticstack/compute/functions/ServerInfoToNodeMetadata.java index c11ac5ba31..3518ed6059 100644 --- a/apis/elasticstack/src/main/java/org/jclouds/elasticstack/compute/functions/ServerInfoToNodeMetadata.java +++ b/apis/elasticstack/src/main/java/org/jclouds/elasticstack/compute/functions/ServerInfoToNodeMetadata.java @@ -18,6 +18,7 @@ package org.jclouds.elasticstack.compute.functions; import static com.google.common.base.Preconditions.checkNotNull; import static org.jclouds.compute.predicates.ImagePredicates.idEquals; +import static org.jclouds.compute.util.ComputeServiceUtils.groupFromMapOrName; import java.util.Map; import java.util.Set; @@ -92,7 +93,7 @@ public class ServerInfoToNodeMetadata implements Function nodeNames) { + NovaTemplateOptions options = new NovaTemplateOptions(); + return NovaTemplateOptions.class.cast(options.nodeNames(nodeNames)); + } + /** * @see TemplateOptions#overrideLoginUser */ @@ -494,6 +502,15 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable { return NovaTemplateOptions.class.cast(super.userMetadata(key, value)); } + /** + * {@inheritDoc} + */ + @Override + public NovaTemplateOptions nodeNames(Iterable nodeNames) { + return NovaTemplateOptions.class.cast(super.nodeNames(nodeNames)); + } + + /** * User data as bytes (not base64-encoded) */ diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java index 46ee0ba196..e3ea4ccb7a 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/strategy/ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddToSet.java @@ -35,6 +35,7 @@ import org.jclouds.compute.config.CustomizationResponse; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.compute.domain.Template; import org.jclouds.compute.functions.GroupNamingConvention; +import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.compute.strategy.CreateNodeWithGroupEncodedIntoName; import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap; import org.jclouds.compute.strategy.ListNodesStrategy; @@ -142,6 +143,7 @@ public class ApplyNovaTemplateOptionsCreateNodesWithGroupEncodedIntoNameThenAddT templateOptions.securityGroupNames(securityGroupName); } } + templateOptions.userMetadata(ComputeServiceConstants.NODE_GROUP_KEY, group); return super.execute(group, count, mutableTemplate, goodNodes, badNodes, customizationResponses); } diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceExpectTest.java index 6962bc3e99..188896390e 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/compute/NovaComputeServiceExpectTest.java @@ -239,7 +239,8 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe .addHeader("X-Auth-Token", authToken) .payload( payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-1\",\"imageRef\":\"14\",\"flavorRef\":\"1\",\"key_name\":\"jclouds-test-0\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}", + "{\"server\":{\"name\":\"test-1\",\"imageRef\":\"14\",\"flavorRef\":\"1\"," + + "\"metadata\":{\"jclouds-group\":\"test\"},\"key_name\":\"jclouds-test-0\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}", "application/json")).build(); HttpResponse createdServer = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") @@ -293,7 +294,8 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe .addHeader("X-Auth-Token", authToken) .payload( payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-0\",\"imageRef\":\"14\",\"flavorRef\":\"1\",\"key_name\":\"fooPair\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}", + "{\"server\":{\"name\":\"test-0\",\"imageRef\":\"14\",\"flavorRef\":\"1\"," + + "\"metadata\":{\"jclouds-group\":\"test\"},\"key_name\":\"fooPair\",\"security_groups\":[{\"name\":\"jclouds-test\"}]}}", "application/json")).build(); HttpResponse createdServer = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") @@ -343,7 +345,8 @@ public class NovaComputeServiceExpectTest extends BaseNovaComputeServiceExpectTe .addHeader("X-Auth-Token", authToken) .payload( payloadFromStringWithContentType( - "{\"server\":{\"name\":\"test-0\",\"imageRef\":\"14\",\"flavorRef\":\"1\",\"key_name\":\"fooPair\",\"security_groups\":[{\"name\":\"mygroup\"}]}}", + "{\"server\":{\"name\":\"test-0\",\"imageRef\":\"14\",\"flavorRef\":\"1\"," + + "\"metadata\":{\"jclouds-group\":\"test\"},\"key_name\":\"fooPair\",\"security_groups\":[{\"name\":\"mygroup\"}]}}", "application/json")).build(); HttpResponse createdServer = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") diff --git a/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/functions/VAppToNodeMetadata.java b/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/functions/VAppToNodeMetadata.java index 8d798a8164..bebbbcef88 100644 --- a/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/functions/VAppToNodeMetadata.java +++ b/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/functions/VAppToNodeMetadata.java @@ -21,6 +21,7 @@ import static com.google.common.base.Predicates.not; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.Iterables.filter; import static org.jclouds.compute.util.ComputeServiceUtils.addMetadataAndParseTagsFromCommaDelimitedValue; +import static org.jclouds.compute.util.ComputeServiceUtils.groupFromMapOrName; import static org.jclouds.vcloud.compute.util.VCloudComputeUtils.getCredentialsFrom; import static org.jclouds.vcloud.compute.util.VCloudComputeUtils.getIpsFromVApp; import static org.jclouds.vcloud.compute.util.VCloudComputeUtils.toComputeOs; @@ -44,6 +45,7 @@ import org.jclouds.vcloud.domain.VApp; import com.google.common.base.Function; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableMap; /** @author Adrian Cole */ @Singleton @@ -73,19 +75,26 @@ public class VAppToNodeMetadata implements Function { builder.ids(from.getHref().toASCIIString()); builder.uri(from.getHref()); builder.name(from.getName()); + String groupName = ""; + Map metadataMap; + if (!isNullOrEmpty(from.getDescription()) && from.getDescription().indexOf('=') != -1 && from.getDescription().indexOf('\n') != -1) { try { - addMetadataAndParseTagsFromCommaDelimitedValue(builder, - Splitter.on('\n').withKeyValueSeparator("=").split(from.getDescription())); + metadataMap = Splitter.on('\n').withKeyValueSeparator("=").split(from.getDescription()); + + addMetadataAndParseTagsFromCommaDelimitedValue(builder, metadataMap); } catch (IllegalArgumentException iae) { // no op + metadataMap = ImmutableMap.of(); } + } else { + metadataMap = ImmutableMap.of(); } builder.hostname(from.getName()); builder.location(findLocationForResourceInVDC.apply(from.getVDC())); - builder.group(nodeNamingConvention.groupInUniqueNameOrNull(from.getName())); + builder.group(groupFromMapOrName(metadataMap, from.getName(), nodeNamingConvention)); builder.operatingSystem(toComputeOs(from, null)); builder.hardware(hardwareForVApp.apply(from)); builder.status(vAppStatusToNodeStatus.get(from.getStatus())); diff --git a/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/options/VCloudTemplateOptions.java b/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/options/VCloudTemplateOptions.java index 4a551a4dd2..0866df76fd 100644 --- a/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/options/VCloudTemplateOptions.java +++ b/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/options/VCloudTemplateOptions.java @@ -223,6 +223,14 @@ public class VCloudTemplateOptions extends TemplateOptions implements Cloneable return VCloudTemplateOptions.class.cast(options.userMetadata(key, value)); } + /** + * @see TemplateOptions#nodeNames(Iterable) + */ + public static VCloudTemplateOptions nodeNames(Iterable nodeNames) { + VCloudTemplateOptions options = new VCloudTemplateOptions(); + return VCloudTemplateOptions.class.cast(options.nodeNames(nodeNames)); + } + } /** @@ -316,4 +324,12 @@ public class VCloudTemplateOptions extends TemplateOptions implements Cloneable return VCloudTemplateOptions.class.cast(super.userMetadata(key, value)); } + /** + * {@inheritDoc} + */ + @Override + public VCloudTemplateOptions nodeNames(Iterable nodeNames) { + return VCloudTemplateOptions.class.cast(super.nodeNames(nodeNames)); + } + } diff --git a/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/strategy/InstantiateVAppTemplateWithGroupEncodedIntoNameThenCustomizeDeployAndPowerOn.java b/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/strategy/InstantiateVAppTemplateWithGroupEncodedIntoNameThenCustomizeDeployAndPowerOn.java index 77229acc88..caecde652a 100644 --- a/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/strategy/InstantiateVAppTemplateWithGroupEncodedIntoNameThenCustomizeDeployAndPowerOn.java +++ b/apis/vcloud/src/main/java/org/jclouds/vcloud/compute/strategy/InstantiateVAppTemplateWithGroupEncodedIntoNameThenCustomizeDeployAndPowerOn.java @@ -124,6 +124,7 @@ public class InstantiateVAppTemplateWithGroupEncodedIntoNameThenCustomizeDeployA // per above check, we know there is only a single VM Vm vm = get(vAppResponse.getChildren(), 0); + template.getOptions().userMetadata(ComputeServiceConstants.NODE_GROUP_KEY, group); VCloudTemplateOptions vOptions = VCloudTemplateOptions.class.cast(template.getOptions()); // note we cannot do tasks in parallel or VCD will throw "is busy" errors diff --git a/compute/src/main/java/org/jclouds/compute/options/TemplateOptions.java b/compute/src/main/java/org/jclouds/compute/options/TemplateOptions.java index d17a6cd04f..2634a92d4e 100644 --- a/compute/src/main/java/org/jclouds/compute/options/TemplateOptions.java +++ b/compute/src/main/java/org/jclouds/compute/options/TemplateOptions.java @@ -82,6 +82,8 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable { to.userMetadata(this.getUserMetadata()); if (this.getTags().size() > 0) to.tags(getTags()); + if (!this.getNodeNames().isEmpty()) + to.nodeNames(getNodeNames()); if (!this.shouldBlockUntilRunning()) to.blockUntilRunning(false); if (!this.shouldBlockOnComplete()) @@ -296,6 +298,16 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable { throw new IllegalArgumentException("tags are immutable"); } + @Override + public Set getNodeNames() { + return delegate.getNodeNames(); + } + + @Override + public TemplateOptions nodeNames(Iterable nodeNames) { + throw new IllegalArgumentException("nodeNames are immutable"); + } + @Override public Set getGroups() { return delegate.getGroups(); @@ -303,12 +315,12 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable { @Override public TemplateOptions securityGroups(Iterable securityGroups) { - throw new IllegalArgumentException("tags are immutable"); + throw new IllegalArgumentException("security groups are immutable"); } @Override public TemplateOptions securityGroups(String... securityGroups) { - throw new IllegalArgumentException("tags are immutable"); + throw new IllegalArgumentException("security groups are immutable"); } @Override @@ -348,7 +360,8 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable { protected Map userMetadata = Maps.newLinkedHashMap(); - @Override + protected Set nodeNames = ImmutableSet.of(); + public boolean equals(Object o) { if (this == o) return true; @@ -356,15 +369,16 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable { return false; TemplateOptions that = TemplateOptions.class.cast(o); return super.equals(that) && equal(this.inboundPorts, that.inboundPorts) && equal(this.script, that.script) - && equal(this.publicKey, that.publicKey) && equal(this.privateKey, that.privateKey) - && equal(this.blockUntilRunning, that.blockUntilRunning) && equal(this.tags, that.tags) - && equal(this.securityGroups, that.securityGroups) && equal(this.userMetadata, that.userMetadata); + && equal(this.publicKey, that.publicKey) && equal(this.privateKey, that.privateKey) + && equal(this.blockUntilRunning, that.blockUntilRunning) && equal(this.tags, that.tags) + && equal(this.securityGroups, that.securityGroups) && equal(this.userMetadata, that.userMetadata) + && equal(this.nodeNames, that.nodeNames); } @Override public int hashCode() { return Objects.hashCode(super.hashCode(), inboundPorts, script, publicKey, privateKey, blockUntilRunning, tags, - securityGroups, userMetadata); + securityGroups, userMetadata, nodeNames); } @Override @@ -382,6 +396,8 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable { toString.add("blockUntilRunning", blockUntilRunning); if (tags.size() != 0) toString.add("tags", tags); + if (!nodeNames.isEmpty()) + toString.add("nodeNames", nodeNames); if (securityGroups.size() != 0) toString.add("securityGroups", securityGroups); if (userMetadata.size() != 0) @@ -401,6 +417,10 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable { return tags; } + public Set getNodeNames() { + return nodeNames; + } + public Set getGroups() { return securityGroups; } @@ -475,6 +495,19 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable { return this; } + /** + * specifies names to be used for the created nodes. + * + * Note that this does not guarantee uniqueness - if there are already existing nodes with a name + * specified here, there will still be a new node created with the same name. Also, if more + * nodes are to be created than there are names, subsequent names will use the default naming strategy + * for that cloud. + */ + public TemplateOptions nodeNames(Iterable nodeNames) { + this.nodeNames = ImmutableSet.copyOf(checkNotNull(nodeNames, "nodeNames")); + return this; + } + /** * assigns the created nodes to these security groups */ @@ -561,6 +594,14 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable { return options.tags(tags); } + /** + * @see TemplateOptions#nodeNames + */ + public static TemplateOptions nodeNames(Iterable nodeNames) { + TemplateOptions options = new TemplateOptions(); + return options.nodeNames(nodeNames); + } + /** * @see TemplateOptions#securityGroups */ diff --git a/compute/src/main/java/org/jclouds/compute/reference/ComputeServiceConstants.java b/compute/src/main/java/org/jclouds/compute/reference/ComputeServiceConstants.java index 469c22f1fe..4cf664e362 100644 --- a/compute/src/main/java/org/jclouds/compute/reference/ComputeServiceConstants.java +++ b/compute/src/main/java/org/jclouds/compute/reference/ComputeServiceConstants.java @@ -48,10 +48,10 @@ public interface ComputeServiceConstants { public static final String COMPUTE_LOGGER = "jclouds.compute"; public static final String LOCAL_PARTITION_GB_PATTERN = "disk_drive/%s/gb"; + public static final String NODE_GROUP_KEY = "jclouds-group"; @Singleton public static class NamingConvention { - @Inject(optional = true) public final Supplier randomSuffix = new Supplier() { final SecureRandom random = new SecureRandom(); diff --git a/compute/src/main/java/org/jclouds/compute/strategy/impl/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java b/compute/src/main/java/org/jclouds/compute/strategy/impl/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java index 5dc20ab382..cf22a1223c 100644 --- a/compute/src/main/java/org/jclouds/compute/strategy/impl/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java +++ b/compute/src/main/java/org/jclouds/compute/strategy/impl/CreateNodesWithGroupEncodedIntoNameThenAddToSet.java @@ -48,6 +48,8 @@ import org.jclouds.compute.strategy.ListNodesStrategy; import org.jclouds.logging.Logger; import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -169,9 +171,13 @@ public class CreateNodesWithGroupEncodedIntoNameThenAddToSet implements CreateNo } /** - * Find the next node names that can be used. These will be derived from the group and the - * template. We will pre-allocate a specified quantity, and attempt to verify that there is no - * name conflict with the current service. + * Find the next node names that can be used. If the nodeNames template option is not specified + * or is empty, these will be derived from the group and the template. We will pre-allocate a + * specified quantity, and attempt to verify that there is no name conflict with the current + * service. If the nodeNames option is specified, names from that will be used instead, without + * any check for name conflicts. + * If there are insufficient names in nodeNames, subsequent names will be generated in the + * default format. * * @param group * @param count @@ -180,6 +186,12 @@ public class CreateNodesWithGroupEncodedIntoNameThenAddToSet implements CreateNo */ protected Set getNextNames(final String group, final Template template, int count) { Set names = newLinkedHashSet(); + Set nodeNames = template.getOptions().getNodeNames(); + if (nodeNames.size() >= count) { + return ImmutableSet.copyOf(Iterables.limit(nodeNames, count)); + } else { + names.addAll(nodeNames); + } Iterable currentNodes = listNodesStrategy.listNodes(); int maxTries = 100; int currentTries = 0; diff --git a/compute/src/main/java/org/jclouds/compute/util/ComputeServiceUtils.java b/compute/src/main/java/org/jclouds/compute/util/ComputeServiceUtils.java index 1890bf59ca..3c38516535 100644 --- a/compute/src/main/java/org/jclouds/compute/util/ComputeServiceUtils.java +++ b/compute/src/main/java/org/jclouds/compute/util/ComputeServiceUtils.java @@ -40,12 +40,13 @@ import org.jclouds.compute.domain.NodeMetadataBuilder; import org.jclouds.compute.domain.OsFamily; import org.jclouds.compute.domain.Processor; import org.jclouds.compute.domain.Volume; +import org.jclouds.compute.functions.GroupNamingConvention; import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.http.HttpRequest; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.domain.Statements; -import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Splitter; @@ -282,4 +283,12 @@ public class ComputeServiceUtils { return portRanges; } + + public static String groupFromMapOrName(Map metadataMap, String nodeName, GroupNamingConvention namingConvention) { + if (metadataMap.get(ComputeServiceConstants.NODE_GROUP_KEY) != null) { + return metadataMap.get(ComputeServiceConstants.NODE_GROUP_KEY); + } else { + return namingConvention.groupInUniqueNameOrNull(nodeName); + } + } } diff --git a/compute/src/test/java/org/jclouds/compute/StubComputeServiceIntegrationTest.java b/compute/src/test/java/org/jclouds/compute/StubComputeServiceIntegrationTest.java index 257c587c47..0a65809a7e 100644 --- a/compute/src/test/java/org/jclouds/compute/StubComputeServiceIntegrationTest.java +++ b/compute/src/test/java/org/jclouds/compute/StubComputeServiceIntegrationTest.java @@ -484,6 +484,12 @@ public class StubComputeServiceIntegrationTest extends BaseComputeServiceLiveTes super.testCreateAnotherNodeWithANewContextToEnsureSharedMemIsntRequired(); } + @Test(enabled = true, dependsOnMethods = "testCreateAnotherNodeWithANewContextToEnsureSharedMemIsntRequired") + public void testCreateTwoNodesWithOneSpecifiedName() throws Exception { + super.testCreateTwoNodesWithOneSpecifiedName(); + } + + @Test(enabled = true, dependsOnMethods = "testCreateAnotherNodeWithANewContextToEnsureSharedMemIsntRequired") public void testCredentialsCache() throws Exception { super.testCredentialsCache(); diff --git a/compute/src/test/java/org/jclouds/compute/internal/BaseComputeServiceLiveTest.java b/compute/src/test/java/org/jclouds/compute/internal/BaseComputeServiceLiveTest.java index ca5102e209..d3c7a92cbd 100644 --- a/compute/src/test/java/org/jclouds/compute/internal/BaseComputeServiceLiveTest.java +++ b/compute/src/test/java/org/jclouds/compute/internal/BaseComputeServiceLiveTest.java @@ -35,6 +35,7 @@ import static java.util.logging.Logger.getAnonymousLogger; import static org.jclouds.compute.options.RunScriptOptions.Builder.nameTask; import static org.jclouds.compute.options.RunScriptOptions.Builder.wrapInInitScript; import static org.jclouds.compute.options.TemplateOptions.Builder.inboundPorts; +import static org.jclouds.compute.options.TemplateOptions.Builder.nodeNames; import static org.jclouds.compute.options.TemplateOptions.Builder.overrideLoginCredentials; import static org.jclouds.compute.options.TemplateOptions.Builder.runAsRoot; import static org.jclouds.compute.predicates.NodePredicates.TERMINATED; @@ -44,6 +45,7 @@ import static org.jclouds.compute.predicates.NodePredicates.runningInGroup; import static org.jclouds.compute.util.ComputeServiceUtils.getCores; import static org.jclouds.util.Predicates2.retry; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; @@ -362,6 +364,30 @@ public abstract class BaseComputeServiceLiveTest extends BaseComputeServiceConte checkOsMatchesTemplate(node2); } + @Test(enabled = true, dependsOnMethods = "testCreateTwoNodesWithRunScript") + public void testCreateTwoNodesWithOneSpecifiedName() throws Exception { + Set nodes; + try { + nodes = newTreeSet(client.createNodesInGroup(group, 2, nodeNames(ImmutableSet.of("first-node")))); + } catch (RunNodesException e) { + nodes = newTreeSet(concat(e.getSuccessfulNodes(), e.getNodeErrors().keySet())); + throw e; + } + + assertEquals(nodes.size(), 2, "expected two nodes but was " + nodes); + NodeMetadata node1 = Iterables.getFirst(nodes, null); + NodeMetadata node2 = Iterables.getLast(nodes, null); + // credentials aren't always the same + // assertEquals(node1.getCredentials(), node2.getCredentials()); + + assertTrue(node1.getName().equals("first-node") || node2.getName().equals("first-node"), + "one node should be named 'first-node'"); + assertFalse(node1.getName().equals("first-node") && node2.getName().equals("first-node"), + "one node should be named something other than 'first-node"); + + this.nodes.addAll(nodes); + } + private Template refreshTemplate() { return template = addRunScriptToTemplate(buildTemplate(client.templateBuilder())); } @@ -391,7 +417,7 @@ public abstract class BaseComputeServiceLiveTest extends BaseComputeServiceConte } } - @Test(enabled = true, dependsOnMethods = "testCreateTwoNodesWithRunScript") + @Test(enabled = true, dependsOnMethods = "testCreateTwoNodesWithOneSpecifiedName") public void testCreateAnotherNodeWithANewContextToEnsureSharedMemIsntRequired() throws Exception { initializeContext(); diff --git a/compute/src/test/java/org/jclouds/compute/options/TemplateOptionsTest.java b/compute/src/test/java/org/jclouds/compute/options/TemplateOptionsTest.java index 1e454a56e0..993572cc82 100644 --- a/compute/src/test/java/org/jclouds/compute/options/TemplateOptionsTest.java +++ b/compute/src/test/java/org/jclouds/compute/options/TemplateOptionsTest.java @@ -21,12 +21,17 @@ import static org.jclouds.compute.options.TemplateOptions.Builder.blockOnPort; import static org.jclouds.compute.options.TemplateOptions.Builder.blockUntilRunning; import static org.jclouds.compute.options.TemplateOptions.Builder.inboundPorts; import static org.jclouds.compute.options.TemplateOptions.Builder.installPrivateKey; +import static org.jclouds.compute.options.TemplateOptions.Builder.nodeNames; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; import java.io.IOException; +import java.util.Set; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableSet; + /** * Tests possible uses of TemplateOptions and TemplateOptions.Builder.* * @@ -184,4 +189,11 @@ public class TemplateOptionsTest { TemplateOptions options = blockUntilRunning(false); assertEquals(options.shouldBlockUntilRunning(), false); } + + @Test + public void testNodeNames() { + Set nodeNames = ImmutableSet.of("first-node", "second-node"); + TemplateOptions options = nodeNames(nodeNames); + assertTrue(options.getNodeNames().containsAll(nodeNames)); + } } diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateOptions.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateOptions.java index 18d6a6612c..bc458bcff8 100644 --- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateOptions.java +++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateOptions.java @@ -480,6 +480,14 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab AWSEC2TemplateOptions options = new AWSEC2TemplateOptions(); return options.blockUntilRunning(blockUntilRunning); } + + /** + * @see TemplateOptions#nodeNames(Iterable) + */ + public static AWSEC2TemplateOptions nodeNames(Iterable nodeNames) { + AWSEC2TemplateOptions options = new AWSEC2TemplateOptions(); + return AWSEC2TemplateOptions.class.cast(options.nodeNames(nodeNames)); + } } // methods that only facilitate returning the correct object type @@ -508,6 +516,14 @@ public class AWSEC2TemplateOptions extends EC2TemplateOptions implements Cloneab return AWSEC2TemplateOptions.class.cast(super.userMetadata(key, value)); } + /** + * {@inheritDoc} + */ + @Override + public AWSEC2TemplateOptions nodeNames(Iterable nodeNames) { + return AWSEC2TemplateOptions.class.cast(super.nodeNames(nodeNames)); + } + /** * {@inheritDoc} */ diff --git a/providers/glesys/src/main/java/org/jclouds/glesys/compute/GleSYSComputeServiceAdapter.java b/providers/glesys/src/main/java/org/jclouds/glesys/compute/GleSYSComputeServiceAdapter.java index 09a8fab101..a3f7ae5fb0 100644 --- a/providers/glesys/src/main/java/org/jclouds/glesys/compute/GleSYSComputeServiceAdapter.java +++ b/providers/glesys/src/main/java/org/jclouds/glesys/compute/GleSYSComputeServiceAdapter.java @@ -111,6 +111,7 @@ public class GleSYSComputeServiceAdapter implements ComputeServiceAdapter md = metadataAndTagsAsCommaDelimitedValue(template.getOptions()); if (md.size() > 0) { diff --git a/providers/glesys/src/main/java/org/jclouds/glesys/compute/functions/ServerDetailsToNodeMetadata.java b/providers/glesys/src/main/java/org/jclouds/glesys/compute/functions/ServerDetailsToNodeMetadata.java index 5390d0549e..1f2cc1dad6 100644 --- a/providers/glesys/src/main/java/org/jclouds/glesys/compute/functions/ServerDetailsToNodeMetadata.java +++ b/providers/glesys/src/main/java/org/jclouds/glesys/compute/functions/ServerDetailsToNodeMetadata.java @@ -22,6 +22,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.io.BaseEncoding.base16; import static org.jclouds.compute.util.ComputeServiceUtils.addMetadataAndParseTagsFromCommaDelimitedValue; +import static org.jclouds.compute.util.ComputeServiceUtils.groupFromMapOrName; import static org.jclouds.location.predicates.LocationPredicates.idEquals; import java.util.Map; @@ -109,15 +110,18 @@ public class ServerDetailsToNodeMetadata implements Function metadataMap; + // TODO: get glesys to stop stripping out equals and commas! if (!isNullOrEmpty(from.getDescription()) && from.getDescription().matches("^[0-9A-Fa-f]+$")) { String decoded = new String(base16().lowerCase().decode(from.getDescription()), UTF_8); - addMetadataAndParseTagsFromCommaDelimitedValue(builder, - Splitter.on('\n').withKeyValueSeparator("=").split(decoded)); + metadataMap = Splitter.on('\n').withKeyValueSeparator("=").split(decoded); + addMetadataAndParseTagsFromCommaDelimitedValue(builder, metadataMap); + } else { + metadataMap = ImmutableMap.of(); } - + builder.group(groupFromMapOrName(metadataMap, from.getHostname(), nodeNamingConvention)); builder.imageId(from.getTemplateName() + ""); builder.operatingSystem(parseOperatingSystem(from)); builder.hardware(new HardwareBuilder().ids(from.getId() + "").ram(from.getMemorySizeMB()) diff --git a/providers/glesys/src/main/java/org/jclouds/glesys/compute/options/GleSYSTemplateOptions.java b/providers/glesys/src/main/java/org/jclouds/glesys/compute/options/GleSYSTemplateOptions.java index 9232948a6e..48b58ddb1c 100644 --- a/providers/glesys/src/main/java/org/jclouds/glesys/compute/options/GleSYSTemplateOptions.java +++ b/providers/glesys/src/main/java/org/jclouds/glesys/compute/options/GleSYSTemplateOptions.java @@ -209,6 +209,14 @@ public class GleSYSTemplateOptions extends TemplateOptions implements Cloneable GleSYSTemplateOptions options = new GleSYSTemplateOptions(); return GleSYSTemplateOptions.class.cast(options.userMetadata(key, value)); } + + /** + * @see TemplateOptions#nodeNames(Iterable) + */ + public static GleSYSTemplateOptions nodeNames(Iterable nodeNames) { + GleSYSTemplateOptions options = new GleSYSTemplateOptions(); + return GleSYSTemplateOptions.class.cast(options.nodeNames(nodeNames)); + } } // methods that only facilitate returning the correct object type @@ -261,6 +269,14 @@ public class GleSYSTemplateOptions extends TemplateOptions implements Cloneable return GleSYSTemplateOptions.class.cast(super.userMetadata(key, value)); } + /** + * {@inheritDoc} + */ + @Override + public GleSYSTemplateOptions nodeNames(Iterable nodeNames) { + return GleSYSTemplateOptions.class.cast(super.nodeNames(nodeNames)); + } + @Override public ToStringHelper string() { ToStringHelper stringHelper = super.string(); diff --git a/providers/gogrid/src/main/java/org/jclouds/gogrid/compute/options/GoGridTemplateOptions.java b/providers/gogrid/src/main/java/org/jclouds/gogrid/compute/options/GoGridTemplateOptions.java index a7b9f93af4..87e74e1eb0 100644 --- a/providers/gogrid/src/main/java/org/jclouds/gogrid/compute/options/GoGridTemplateOptions.java +++ b/providers/gogrid/src/main/java/org/jclouds/gogrid/compute/options/GoGridTemplateOptions.java @@ -97,6 +97,14 @@ public class GoGridTemplateOptions extends TemplateOptions implements Cloneable GoGridTemplateOptions options = new GoGridTemplateOptions(); return GoGridTemplateOptions.class.cast(options.userMetadata(key, value)); } + + /** + * @see TemplateOptions#nodeNames(Iterable) + */ + public static GoGridTemplateOptions nodeNames(Iterable nodeNames) { + GoGridTemplateOptions options = new GoGridTemplateOptions(); + return GoGridTemplateOptions.class.cast(options.nodeNames(nodeNames)); + } } // methods that only facilitate returning the correct object type @@ -148,4 +156,12 @@ public class GoGridTemplateOptions extends TemplateOptions implements Cloneable public GoGridTemplateOptions userMetadata(String key, String value) { return GoGridTemplateOptions.class.cast(super.userMetadata(key, value)); } + + /** + * {@inheritDoc} + */ + @Override + public GoGridTemplateOptions nodeNames(Iterable nodeNames) { + return GoGridTemplateOptions.class.cast(super.nodeNames(nodeNames)); + } } diff --git a/providers/softlayer/src/main/java/org/jclouds/softlayer/compute/options/SoftLayerTemplateOptions.java b/providers/softlayer/src/main/java/org/jclouds/softlayer/compute/options/SoftLayerTemplateOptions.java index 7c54c0ddf5..2bbec8ab17 100644 --- a/providers/softlayer/src/main/java/org/jclouds/softlayer/compute/options/SoftLayerTemplateOptions.java +++ b/providers/softlayer/src/main/java/org/jclouds/softlayer/compute/options/SoftLayerTemplateOptions.java @@ -130,6 +130,14 @@ public class SoftLayerTemplateOptions extends TemplateOptions implements Cloneab SoftLayerTemplateOptions options = new SoftLayerTemplateOptions(); return SoftLayerTemplateOptions.class.cast(options.userMetadata(key, value)); } + + /** + * @see TemplateOptions#nodeNames(Iterable) + */ + public static SoftLayerTemplateOptions nodeNames(Iterable nodeNames) { + SoftLayerTemplateOptions options = new SoftLayerTemplateOptions(); + return SoftLayerTemplateOptions.class.cast(options.nodeNames(nodeNames)); + } } // methods that only facilitate returning the correct object type @@ -181,4 +189,12 @@ public class SoftLayerTemplateOptions extends TemplateOptions implements Cloneab public SoftLayerTemplateOptions userMetadata(String key, String value) { return SoftLayerTemplateOptions.class.cast(super.userMetadata(key, value)); } + + /** + * {@inheritDoc} + */ + @Override + public SoftLayerTemplateOptions nodeNames(Iterable nodeNames) { + return SoftLayerTemplateOptions.class.cast(super.nodeNames(nodeNames)); + } }