mirror of https://github.com/apache/jclouds.git
JCLOUDS-381. Allow explicit naming of nodes via TemplateOptions.
This commit is contained in:
parent
3ecbf90847
commit
23e43b2c8d
|
@ -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<Server, NodeMetadata> {
|
|||
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));
|
||||
|
|
|
@ -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<Server> 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())));
|
||||
|
|
|
@ -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<String> 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<String> nodeNames) {
|
||||
return CloudSigmaTemplateOptions.class.cast(super.nodeNames(nodeNames));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -104,7 +104,9 @@ public class VirtualMachineToNodeMetadata implements Function<VirtualMachine, No
|
|||
// on hosts not started with jclouds
|
||||
builder.hostname(from.getDisplayName());
|
||||
builder.location(FluentIterable.from(locations.get()).firstMatch(idEquals(from.getZoneId())).orNull());
|
||||
if (from.getDisplayName() != null) {
|
||||
if (from.getGroup() != null) {
|
||||
builder.group(from.getGroup());
|
||||
} else if (from.getDisplayName() != null) {
|
||||
builder.group(nodeNamingConvention.groupInUniqueNameOrNull(from.getDisplayName()));
|
||||
}
|
||||
Image image = FluentIterable.from(images.get()).firstMatch(new Predicate<Image>() {
|
||||
|
|
|
@ -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<String> 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<String> nodeNames) {
|
||||
return CloudStackTemplateOptions.class.cast(super.nodeNames(nodeNames));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> 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<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> 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<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> 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<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> 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<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> 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<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> builder()
|
||||
.put(listTemplates, listTemplatesResponse)
|
||||
.put(listOsTypes, listOsTypesResponse)
|
||||
|
|
|
@ -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<String, String> 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<NodeMetadata> addTagsToInstancesInRegion(Map<String, String> common, Set<? extends NodeMetadata> input,
|
||||
String region, String group) {
|
||||
private Set<NodeMetadata> addTagsAndNamesToInstancesInRegion(Map<String, String> common, Set<String> nodeNames,
|
||||
Set<? extends NodeMetadata> input, String region,
|
||||
String group) {
|
||||
Map<String, ? extends NodeMetadata> instancesById = Maps.uniqueIndex(input, instanceId);
|
||||
ImmutableSet.Builder<NodeMetadata> builder = ImmutableSet.<NodeMetadata> builder();
|
||||
|
||||
if (generateInstanceNames && !common.containsKey("Name")) {
|
||||
for (Map.Entry<String, ? extends NodeMetadata> 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<String, String> tags = ImmutableMap.<String, String> 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)));
|
||||
|
|
|
@ -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<String> 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<String> nodeNames) {
|
||||
return EC2TemplateOptions.class.cast(super.nodeNames(nodeNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -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<HttpRequest, HttpResponse> requestResponseMap = ImmutableMap.<HttpRequest, HttpResponse> 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 {
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2011-05-15/">
|
||||
<requestId>f6d3252e-35e5-4ef5-b2c5-62da95dd829b</requestId>
|
||||
<reservationSet>
|
||||
<item>
|
||||
<reservationId>r-205ad944</reservationId>
|
||||
<ownerId>993194456877</ownerId>
|
||||
<groupSet>
|
||||
<item>
|
||||
<groupId>sg-3c6ef654</groupId>
|
||||
<groupName>jclouds#mygroup2</groupName>
|
||||
</item>
|
||||
</groupSet>
|
||||
<instancesSet>
|
||||
<item>
|
||||
<instanceId>i-2baa5550</instanceId>
|
||||
<imageId>ami-aecd60c7</imageId>
|
||||
<instanceState>
|
||||
<code>16</code>
|
||||
<name>running</name>
|
||||
</instanceState>
|
||||
<privateDnsName>ip-10-28-89-195.ec2.internal</privateDnsName>
|
||||
<dnsName>ec2-50-16-1-166.compute-1.amazonaws.com</dnsName>
|
||||
<reason/>
|
||||
<keyName>jclouds#mygroup2#81</keyName>
|
||||
<amiLaunchIndex>0</amiLaunchIndex>
|
||||
<productCodes/>
|
||||
<instanceType>t1.micro</instanceType>
|
||||
<launchTime>2012-08-02T04:28:30.000Z</launchTime>
|
||||
<placement>
|
||||
<availabilityZone>us-east-1e</availabilityZone>
|
||||
<groupName/>
|
||||
<tenancy>default</tenancy>
|
||||
</placement>
|
||||
<kernelId>aki-88aa75e1</kernelId>
|
||||
<monitoring>
|
||||
<state>disabled</state>
|
||||
</monitoring>
|
||||
<privateIpAddress>10.28.89.195</privateIpAddress>
|
||||
<ipAddress>50.16.1.166</ipAddress>
|
||||
<groupSet>
|
||||
<item>
|
||||
<groupId>sg-3c6ef654</groupId>
|
||||
<groupName>jclouds#mygroup2</groupName>
|
||||
</item>
|
||||
</groupSet>
|
||||
<architecture>x86_64</architecture>
|
||||
<rootDeviceType>ebs</rootDeviceType>
|
||||
<rootDeviceName>/dev/sda1</rootDeviceName>
|
||||
<blockDeviceMapping>
|
||||
<item>
|
||||
<deviceName>/dev/sda1</deviceName>
|
||||
<ebs>
|
||||
<volumeId>vol-f2d7c993</volumeId>
|
||||
<status>attached</status>
|
||||
<attachTime>2012-08-02T04:28:56.000Z</attachTime>
|
||||
<deleteOnTermination>true</deleteOnTermination>
|
||||
</ebs>
|
||||
</item>
|
||||
</blockDeviceMapping>
|
||||
<virtualizationType>paravirtual</virtualizationType>
|
||||
<clientToken/>
|
||||
<tagSet>
|
||||
<item>
|
||||
<key>Name</key>
|
||||
<value>test-node</value>
|
||||
</item>
|
||||
</tagSet>
|
||||
<hypervisor>xen</hypervisor>
|
||||
</item>
|
||||
</instancesSet>
|
||||
</item>
|
||||
</reservationSet>
|
||||
</DescribeInstancesResponse>
|
|
@ -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();
|
||||
|
|
|
@ -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<ServerInfo, NodeMetada
|
|||
builder.ids(from.getUuid());
|
||||
builder.name(from.getName());
|
||||
builder.location(locationSupplier.get());
|
||||
builder.group(nodeNamingConvention.groupInUniqueNameOrNull(from.getName()));
|
||||
builder.group(groupFromMapOrName(from.getUserMetadata(), from.getName(), nodeNamingConvention));
|
||||
builder.tags(from.getTags());
|
||||
builder.userMetadata(from.getUserMetadata());
|
||||
String imageId = getImageIdFromServer.apply(from);
|
||||
|
|
|
@ -22,6 +22,7 @@ import static com.google.common.collect.Iterables.filter;
|
|||
import static com.google.common.collect.Iterables.find;
|
||||
import static com.google.common.collect.Iterables.transform;
|
||||
import static org.jclouds.compute.util.ComputeServiceUtils.addMetadataAndParseTagsFromCommaDelimitedValue;
|
||||
import static org.jclouds.compute.util.ComputeServiceUtils.groupFromMapOrName;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.util.Map;
|
||||
|
@ -100,8 +101,8 @@ public class ServerInZoneToNodeMetadata implements Function<ServerInZone, NodeMe
|
|||
builder.hostname(from.getName());
|
||||
builder.location(from.getHostId() != null ? new LocationBuilder().scope(LocationScope.HOST).id(from.getHostId())
|
||||
.description(from.getHostId()).parent(zone).build() : zone);
|
||||
builder.group(groupFromMapOrName(from.getMetadata(), from.getName(), nodeNamingConvention));
|
||||
addMetadataAndParseTagsFromCommaDelimitedValue(builder, from.getMetadata());
|
||||
builder.group(nodeNamingConvention.groupInUniqueNameOrNull(from.getName()));
|
||||
builder.imageId(ZoneAndId.fromZoneAndId(serverInZone.getZone(), from.getImage().getId()).slashEncode());
|
||||
builder.operatingSystem(findOperatingSystemForServerOrNull(serverInZone));
|
||||
builder.hardware(findHardwareForServerOrNull(serverInZone));
|
||||
|
|
|
@ -299,6 +299,14 @@ public class NovaTemplateOptions extends TemplateOptions implements Cloneable {
|
|||
return NovaTemplateOptions.class.cast(options.userMetadata(userMetadata));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TemplateOptions#nodeNames(Iterable)
|
||||
*/
|
||||
public static NovaTemplateOptions nodeNames(Iterable<String> 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<String> nodeNames) {
|
||||
return NovaTemplateOptions.class.cast(super.nodeNames(nodeNames));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* User data as bytes (not base64-encoded)
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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<VApp, NodeMetadata> {
|
|||
builder.ids(from.getHref().toASCIIString());
|
||||
builder.uri(from.getHref());
|
||||
builder.name(from.getName());
|
||||
String groupName = "";
|
||||
Map<String, String> 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()));
|
||||
|
|
|
@ -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<String> 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<String> nodeNames) {
|
||||
return VCloudTemplateOptions.class.cast(super.nodeNames(nodeNames));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<String> getNodeNames() {
|
||||
return delegate.getNodeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemplateOptions nodeNames(Iterable<String> nodeNames) {
|
||||
throw new IllegalArgumentException("nodeNames are immutable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getGroups() {
|
||||
return delegate.getGroups();
|
||||
|
@ -303,12 +315,12 @@ public class TemplateOptions extends RunScriptOptions implements Cloneable {
|
|||
|
||||
@Override
|
||||
public TemplateOptions securityGroups(Iterable<String> 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<String, String> userMetadata = Maps.newLinkedHashMap();
|
||||
|
||||
@Override
|
||||
protected Set<String> 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<String> getNodeNames() {
|
||||
return nodeNames;
|
||||
}
|
||||
|
||||
public Set<String> 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<String> 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<String> nodeNames) {
|
||||
TemplateOptions options = new TemplateOptions();
|
||||
return options.nodeNames(nodeNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TemplateOptions#securityGroups
|
||||
*/
|
||||
|
|
|
@ -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<String> randomSuffix = new Supplier<String>() {
|
||||
final SecureRandom random = new SecureRandom();
|
||||
|
|
|
@ -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<String> getNextNames(final String group, final Template template, int count) {
|
||||
Set<String> names = newLinkedHashSet();
|
||||
Set<String> nodeNames = template.getOptions().getNodeNames();
|
||||
if (nodeNames.size() >= count) {
|
||||
return ImmutableSet.copyOf(Iterables.limit(nodeNames, count));
|
||||
} else {
|
||||
names.addAll(nodeNames);
|
||||
}
|
||||
Iterable<? extends ComputeMetadata> currentNodes = listNodesStrategy.listNodes();
|
||||
int maxTries = 100;
|
||||
int currentTries = 0;
|
||||
|
|
|
@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<? extends NodeMetadata> 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();
|
||||
|
||||
|
|
|
@ -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<String> nodeNames = ImmutableSet.of("first-node", "second-node");
|
||||
TemplateOptions options = nodeNames(nodeNames);
|
||||
assertTrue(options.getNodeNames().containsAll(nodeNames));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> nodeNames) {
|
||||
return AWSEC2TemplateOptions.class.cast(super.nodeNames(nodeNames));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
|
|
@ -111,6 +111,7 @@ public class GleSYSComputeServiceAdapter implements ComputeServiceAdapter<Server
|
|||
|
||||
CreateServerOptions createServerOptions = new CreateServerOptions();
|
||||
createServerOptions.ip(templateOptions.getIp());
|
||||
template.getOptions().userMetadata(ComputeServiceConstants.NODE_GROUP_KEY, group);
|
||||
|
||||
Map<String, String> md = metadataAndTagsAsCommaDelimitedValue(template.getOptions());
|
||||
if (md.size() > 0) {
|
||||
|
|
|
@ -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<ServerDetails, Node
|
|||
builder.hostname(from.getHostname());
|
||||
Location location = FluentIterable.from(locations.get()).firstMatch(idEquals(from.getDatacenter())).orNull();
|
||||
checkState(location != null, "no location matched ServerDetails %s", from);
|
||||
builder.group(nodeNamingConvention.groupInUniqueNameOrNull(from.getHostname()));
|
||||
|
||||
|
||||
Map<String, String> 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())
|
||||
|
|
|
@ -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<String> 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<String> nodeNames) {
|
||||
return GleSYSTemplateOptions.class.cast(super.nodeNames(nodeNames));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToStringHelper string() {
|
||||
ToStringHelper stringHelper = super.string();
|
||||
|
|
|
@ -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<String> 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<String> nodeNames) {
|
||||
return GoGridTemplateOptions.class.cast(super.nodeNames(nodeNames));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> nodeNames) {
|
||||
return SoftLayerTemplateOptions.class.cast(super.nodeNames(nodeNames));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue