You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/12/23 12:06:30 UTC
[07/71] [abbrv] incubator-brooklyn git commit: Merge commit 'e430723'
into reorg2
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java
index 0000000,173b695..a0fa874
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java
+++ b/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsMachineLocation.java
@@@ -1,0 -1,44 +1,61 @@@
+ /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ package org.apache.brooklyn.location.jclouds;
+
+ import org.apache.brooklyn.api.location.MachineLocation;
+ import org.apache.brooklyn.core.location.HasSubnetHostname;
+ import org.jclouds.compute.domain.NodeMetadata;
+ import org.jclouds.compute.domain.Template;
+
++import com.google.common.base.Optional;
++
+ public interface JcloudsMachineLocation extends MachineLocation, HasSubnetHostname {
+
+ @Override
+ public JcloudsLocation getParent();
+
++ public Optional<NodeMetadata> getOptionalNode();
++
++ /**
++ * @deprecated since 0.9.0; instead use {@link #getOptionalNode()}. After rebind, the node will
++ * not be available if the VM is no longer running.
++ *
++ * @throws IllegalStateException If the node is not available (i.e. not cached, and cannot be
++ * found from cloud provider).
++ */
++ @Deprecated
+ public NodeMetadata getNode();
+
++ /**
++ * @deprecated since 0.9.0; instead use {@link #getOptionalNode()}. After rebind, the node will not
++ * be available if the VM is no longer running.
++ */
++ @Deprecated
+ public Template getTemplate();
+
+ public String getJcloudsId();
+
+ /** In most clouds, the public hostname is the only way to ensure VMs in different zones can access each other. */
+ @Override
+ public String getSubnetHostname();
+
+ String getUser();
+
+ int getPort();
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
index 0000000,2db43d1..cddc98b
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
+++ b/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsSshMachineLocation.java
@@@ -1,0 -1,336 +1,596 @@@
+ /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ package org.apache.brooklyn.location.jclouds;
+
+ import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+
-import java.net.InetAddress;
-import java.net.UnknownHostException;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+ import java.util.concurrent.ExecutionException;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.TimeoutException;
+
+ import javax.annotation.Nullable;
+
+ import org.apache.brooklyn.api.location.HardwareDetails;
+ import org.apache.brooklyn.api.location.MachineDetails;
+ import org.apache.brooklyn.api.location.OsDetails;
+ import org.apache.brooklyn.core.location.BasicHardwareDetails;
+ import org.apache.brooklyn.core.location.BasicMachineDetails;
+ import org.apache.brooklyn.core.location.BasicOsDetails;
++import org.apache.brooklyn.core.location.LocationConfigUtils;
++import org.apache.brooklyn.core.location.LocationConfigUtils.OsCredential;
+ import org.apache.brooklyn.location.ssh.SshMachineLocation;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.apache.brooklyn.util.net.Networking;
+ import org.apache.brooklyn.util.text.Strings;
+ import org.jclouds.compute.ComputeServiceContext;
+ import org.jclouds.compute.callables.RunScriptOnNode;
+ import org.jclouds.compute.domain.ExecResponse;
+ import org.jclouds.compute.domain.Hardware;
++import org.jclouds.compute.domain.Image;
+ import org.jclouds.compute.domain.NodeMetadata;
+ import org.jclouds.compute.domain.OperatingSystem;
+ import org.jclouds.compute.domain.OsFamily;
+ import org.jclouds.compute.domain.Processor;
+ import org.jclouds.compute.domain.Template;
+ import org.jclouds.compute.options.RunScriptOptions;
+ import org.jclouds.domain.LoginCredentials;
+ import org.jclouds.scriptbuilder.domain.InterpretableStatement;
+ import org.jclouds.scriptbuilder.domain.Statement;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+
++import com.google.common.annotations.VisibleForTesting;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Optional;
+ import com.google.common.base.Throwables;
+ import com.google.common.collect.ImmutableMap;
++import com.google.common.collect.ImmutableSet;
+ import com.google.common.net.HostAndPort;
+ import com.google.common.util.concurrent.ListenableFuture;
+
+ public class JcloudsSshMachineLocation extends SshMachineLocation implements JcloudsMachineLocation {
+
+ private static final Logger LOG = LoggerFactory.getLogger(JcloudsSshMachineLocation.class);
+
+ @SetFromFlag
+ JcloudsLocation jcloudsParent;
+
++ /**
++ * @deprecated since 0.9.0; the node should not be persisted.
++ */
+ @SetFromFlag
++ @Deprecated
+ NodeMetadata node;
+
++ /**
++ * @deprecated since 0.9.0; the template should not be persisted.
++ */
+ @SetFromFlag
++ @Deprecated
+ Template template;
+
++ @SetFromFlag
++ String nodeId;
++
++ @SetFromFlag
++ String imageId;
++
++ @SetFromFlag
++ Set<String> privateAddresses;
++
++ @SetFromFlag
++ Set<String> publicAddresses;
++
++ @SetFromFlag
++ String hostname;
++
++ // Populated lazily, on first call to getSubnetHostname()
++ @SetFromFlag
++ String privateHostname;
++
++ /**
++ * Historically, "node" and "template" were persisted. However that is a very bad idea!
++ * It means we pull in lots of jclouds classes into the persisted state. We are at an
++ * intermediate stage, where we want to handle rebinding to old state that has "node"
++ * and new state that should not. We therefore leave in the {@code @SetFromFlag} on node
++ * so that we read it back, but we ensure the value is null when we write it out!
++ *
++ * TODO We will rename these to get rid of the ugly underscore when the old node/template
++ * fields are deleted.
++ */
++ private transient Optional<NodeMetadata> _node;
++
++ private transient Optional<Template> _template;
++
++ private transient Optional<Image> _image;
++
+ private RunScriptOnNode.Factory runScriptFactory;
+
+ public JcloudsSshMachineLocation() {
+ }
+
+ /**
+ * @deprecated since 0.6; use LocationSpec (which calls no-arg constructor)
+ */
+ @Deprecated
+ public JcloudsSshMachineLocation(Map<?,?> flags, JcloudsLocation jcloudsParent, NodeMetadata node) {
+ super(flags);
+ this.jcloudsParent = jcloudsParent;
- this.node = node;
+
+ init();
+ }
+
+ @Override
+ public void init() {
+ if (jcloudsParent != null) {
+ super.init();
+ ComputeServiceContext context = jcloudsParent.getComputeService().getContext();
+ runScriptFactory = context.utils().injector().getInstance(RunScriptOnNode.Factory.class);
++ if (node != null) {
++ setNode(node);
++ }
++ if (template != null) {
++ setTemplate(template);
++ }
+ } else {
+ // TODO Need to fix the rebind-detection, and not call init() on rebind.
+ // This will all change when locations become entities.
+ if (LOG.isDebugEnabled()) LOG.debug("Not doing init() of {} because parent not set; presuming rebinding", this);
+ }
+ }
+
+ @Override
+ public void rebind() {
+ super.rebind();
+ ComputeServiceContext context = jcloudsParent.getComputeService().getContext();
+ runScriptFactory = context.utils().injector().getInstance(RunScriptOnNode.Factory.class);
++
++ if (node != null) {
++ setNode(node);
++ node = null;
++ }
++
++ if (template != null) {
++ setTemplate(template);
++ template = null;
++ }
+ }
+
+ @Override
+ public String toVerboseString() {
+ return Objects.toStringHelper(this).omitNullValues()
+ .add("id", getId()).add("name", getDisplayName())
+ .add("user", getUser()).add("address", getAddress()).add("port", getConfig(SSH_PORT))
- .add("node", getNode())
- .add("jcloudsId", getJcloudsId())
- .add("privateAddresses", node.getPrivateAddresses())
- .add("publicAddresses", node.getPublicAddresses())
++ .add("node", _node)
++ .add("nodeId", getJcloudsId())
++ .add("imageId", getImageId())
++ .add("privateAddresses", getPrivateAddresses())
++ .add("publicAddresses", getPublicAddresses())
+ .add("parentLocation", getParent())
- .add("osDetails", getOsDetails())
++ .add("osDetails", getOptionalOsDetails())
+ .toString();
+ }
+
++ protected void setNode(NodeMetadata node) {
++ this.node = null;
++ nodeId = node.getId();
++ imageId = node.getImageId();
++ privateAddresses = node.getPrivateAddresses();
++ publicAddresses = node.getPublicAddresses();
++ hostname = node.getHostname();
++ _node = Optional.of(node);
++ }
++
++ protected void setTemplate(Template template) {
++ this.template = null;
++ _template = Optional.of(template);
++ _image = Optional.fromNullable(template.getImage());
++ }
++
++ @Override
++ public Optional<NodeMetadata> getOptionalNode() {
++ if (_node == null) {
++ _node = Optional.fromNullable(getParent().getComputeService().getNodeMetadata(nodeId));
++ }
++ return _node;
++ }
++
++ protected Optional<Image> getOptionalImage() {
++ if (_image == null) {
++ _image = Optional.fromNullable(getParent().getComputeService().getImage(imageId));
++ }
++ return _image;
++ }
++
++ /**
++ * @since 0.9.0
++ * @deprecated since 0.9.0 (only added as aid until the deprecated {@link #getTemplate()} is deleted)
++ */
++ @Deprecated
++ protected Optional<Template> getOptionalTemplate() {
++ if (_template == null) {
++ _template = Optional.absent();
++ }
++ return _template;
++ }
++
++ /**
++ * @deprecated since 0.9.0
++ */
+ @Override
++ @Deprecated
+ public NodeMetadata getNode() {
- return node;
++ Optional<NodeMetadata> result = getOptionalNode();
++ if (result.isPresent()) {
++ return result.get();
++ } else {
++ throw new IllegalStateException("Node "+nodeId+" not present in "+getParent());
++ }
++ }
++
++ @VisibleForTesting
++ Optional<NodeMetadata> peekNode() {
++ return _node;
+ }
+
++ /**
++ * @deprecated since 0.9.0
++ */
+ @Override
++ @Deprecated
+ public Template getTemplate() {
- return template;
++ Optional<Template> result = getOptionalTemplate();
++ if (result.isPresent()) {
++ String msg = "Deprecated use of getTemplate() for "+this;
++ LOG.warn(msg + " - see debug log for stacktrace");
++ LOG.debug(msg, new Exception("for stacktrace"));
++ return result.get();
++ } else {
++ throw new IllegalStateException("Template for "+nodeId+" (in "+getParent()+") not cached (deprecated use of getTemplate())");
++ }
+ }
+
+ @Override
+ public JcloudsLocation getParent() {
+ return jcloudsParent;
+ }
+
+ @Override
+ public String getHostname() {
- return node.getHostname();
++ // Changed behaviour in Brooklyn 0.9.0. Previously it just did node.getHostname(), which
++ // was wrong on some clouds (e.g. vcloud-director, where VMs are often given a random
++ // hostname that does not resolve on the VM and is not in any DNS).
++ // Now delegates to jcloudsParent.getPublicHostname(node).
++ if (privateHostname == null) {
++ Optional<NodeMetadata> node = getOptionalNode();
++ if (node.isPresent()) {
++ HostAndPort sshHostAndPort = getSshHostAndPort();
++ LoginCredentials creds = getLoginCredentials();
++ hostname = jcloudsParent.getPublicHostname(node.get(), Optional.of(sshHostAndPort), creds, config().getBag());
++ requestPersist();
++
++ } else {
++ // Fallback: impl taken (mostly) from jcloudsParent.getPublicHostnameGeneric(NodeMetadata, ConfigBag).
++ // But we won't have a node object (e.g. after rebind, and VM has been terminated).
++ // We also resort to address.getHostAddress as final fallback.
++ if (groovyTruth(getPublicAddresses())) {
++ hostname = getPublicAddresses().iterator().next();
++ } else if (groovyTruth(getPrivateAddresses())) {
++ hostname = getPrivateAddresses().iterator().next();
++ } else {
++ hostname = getAddress().getHostAddress();
++ }
++ }
++ LOG.debug("Resolved hostname {} for {}", hostname, this);
++ requestPersist();
++ }
++ return hostname;
+ }
+
- /** In most clouds, the public hostname is the only way to ensure VMs in different zones can access each other. */
++ /** In clouds like AWS, the public hostname is the only way to ensure VMs in different zones can access each other. */
+ @Override
+ public Set<String> getPublicAddresses() {
- return node.getPublicAddresses();
++ return (publicAddresses == null) ? ImmutableSet.<String>of() : publicAddresses;
+ }
+
+ @Override
+ public Set<String> getPrivateAddresses() {
- return node.getPrivateAddresses();
++ return (privateAddresses == null) ? ImmutableSet.<String>of() : privateAddresses;
+ }
+
+ @Override
+ public String getSubnetHostname() {
- String privateHostname = jcloudsParent.getPrivateHostname(node, Optional.<HostAndPort>absent(), config().getBag());
++ if (privateHostname == null) {
++ Optional<NodeMetadata> node = getOptionalNode();
++ if (node.isPresent()) {
++ // Prefer jcloudsLocation.getPrivateHostname(): it handles AWS hostname in a special way,
++ // by querying AWS for the hostname that resolves both inside and outside of the region.
++ // If we can't get the node (i.e. the cloud provider doesn't know that id, because it has
++ // been terminated), then we don't care as much about getting the right id!
++ HostAndPort sshHostAndPort = getSshHostAndPort();
++ LoginCredentials creds = getLoginCredentials();
++ privateHostname = jcloudsParent.getPrivateHostname(node.get(), Optional.of(sshHostAndPort), creds, config().getBag());
++
++ } else {
++ // Fallback: impl taken from jcloudsParent.getPrivateHostnameGeneric(NodeMetadata, ConfigBag).
++ // But we won't have a node object (e.g. after rebind, and VM has been terminated).
++ //prefer the private address to the hostname because hostname is sometimes wrong/abbreviated
++ //(see that javadoc; also e.g. on rackspace/cloudstack, the hostname is not registered with any DNS).
++ //Don't return local-only address (e.g. never 127.0.0.1)
++ for (String p : getPrivateAddresses()) {
++ if (Networking.isLocalOnly(p)) continue;
++ privateHostname = p;
++ break;
++ }
++ if (Strings.isBlank(privateHostname) && groovyTruth(getPublicAddresses())) {
++ privateHostname = getPublicAddresses().iterator().next();
++ } else if (Strings.isBlank(privateHostname)) {
++ privateHostname = getHostname();
++ }
++ }
++ requestPersist();
++ LOG.debug("Resolved subnet hostname {} for {}", privateHostname, this);
++ }
++
+ return privateHostname;
+ }
+
+ @Override
+ public String getSubnetIp() {
++ // Previous to Brooklyn 0.9.0, this could return the hostname or would try to do
++ // jcloudsParent.getPublicHostname, and return the resolved IP. That was clearly
++ // not a "subnet ip"!
+ Optional<String> privateAddress = getPrivateAddress();
+ if (privateAddress.isPresent()) {
+ return privateAddress.get();
+ }
-
- String hostname = jcloudsParent.getPublicHostname(node, Optional.<HostAndPort>absent(), config().getBag());
- if (hostname != null && !Networking.isValidIp4(hostname)) {
- try {
- return InetAddress.getByName(hostname).getHostAddress();
- } catch (UnknownHostException e) {
- LOG.debug("Cannot resolve IP for hostname {} of machine {} (so returning hostname): {}", new Object[] {hostname, this, e});
- }
++ if (groovyTruth(node.getPublicAddresses())) {
++ return node.getPublicAddresses().iterator().next();
+ }
- return hostname;
++ return null;
+ }
+
+ protected Optional<String> getPrivateAddress() {
- if (groovyTruth(node.getPrivateAddresses())) {
- for (String p : node.getPrivateAddresses()) {
- // disallow local only addresses
- if (Networking.isLocalOnly(p)) continue;
- // other things may be public or private, but either way, return it
- return Optional.of(p);
- }
++ for (String p : getPrivateAddresses()) {
++ // disallow local only addresses
++ if (Networking.isLocalOnly(p)) continue;
++ // other things may be public or private, but either way, return it
++ return Optional.of(p);
+ }
+ return Optional.absent();
+ }
+
+ @Override
+ public String getJcloudsId() {
- return node.getId();
++ return nodeId;
+ }
+
++ protected String getImageId() {
++ return imageId;
++ }
++
+ /** executes the given statements on the server using jclouds ScriptBuilder,
+ * wrapping in a script which is polled periodically.
+ * the output is returned once the script completes (disadvantage compared to other methods)
+ * but the process is nohupped and the SSH session is not kept,
+ * so very useful for long-running processes
++ *
++ * @deprecated since 0.9.0; use standard {@link #execScript(String, List)} and the other variants.
+ */
++ @Deprecated
+ public ListenableFuture<ExecResponse> submitRunScript(String ...statements) {
+ return submitRunScript(new InterpretableStatement(statements));
+ }
++
++ /**
++ * @deprecated since 0.9.0; use standard {@link #execScript(String, List)} and the other variants.
++ */
++ @Deprecated
+ public ListenableFuture<ExecResponse> submitRunScript(Statement script) {
+ return submitRunScript(script, new RunScriptOptions());
+ }
++
++ /**
++ * @deprecated since 0.9.0; use standard {@link #execScript(String, List)} and the other variants.
++ */
++ @Deprecated
+ public ListenableFuture<ExecResponse> submitRunScript(Statement script, RunScriptOptions options) {
- return runScriptFactory.submit(node, script, options);
++ Optional<NodeMetadata> node = getOptionalNode();
++ if (node.isPresent()) {
++ return runScriptFactory.submit(node.get(), script, options);
++ } else {
++ throw new IllegalStateException("Node "+nodeId+" not present in "+getParent());
++ }
+ }
- /** uses submitRunScript to execute the commands, and throws error if it fails or returns non-zero */
++
++ /**
++ * Uses submitRunScript to execute the commands, and throws error if it fails or returns non-zero
++ *
++ * @deprecated since 0.9.0; use standard {@link #execScript(String, List)} and the other variants.
++ */
++ @Deprecated
+ public void execRemoteScript(String ...commands) {
+ try {
+ ExecResponse result = submitRunScript(commands).get();
+ if (result.getExitStatus()!=0)
+ throw new IllegalStateException("Error running remote commands (code "+result.getExitStatus()+"): "+commands);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw Throwables.propagate(e);
+ } catch (ExecutionException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ /**
+ * Retrieves the password for this VM, if one exists. The behaviour/implementation is different for different clouds.
+ * e.g. on Rackspace, the password for a windows VM is available immediately; on AWS-EC2, for a Windows VM you need
+ * to poll repeatedly until the password is available which can take up to 15 minutes.
++ *
++ * @deprecated since 0.9.0; use the machine to execute commands, so no need to extract the password
+ */
++ @Deprecated
+ public String waitForPassword() {
- // TODO Hacky; don't want aws specific stuff here but what to do?!
- if (jcloudsParent.getProvider().equals("aws-ec2")) {
- try {
- return JcloudsUtil.waitForPasswordOnAws(jcloudsParent.getComputeService(), node, 15, TimeUnit.MINUTES);
- } catch (TimeoutException e) {
- throw Throwables.propagate(e);
++ Optional<NodeMetadata> node = getOptionalNode();
++ if (node.isPresent()) {
++ // TODO Hacky; don't want aws specific stuff here but what to do?!
++ if (jcloudsParent.getProvider().equals("aws-ec2")) {
++ try {
++ return JcloudsUtil.waitForPasswordOnAws(jcloudsParent.getComputeService(), node.get(), 15, TimeUnit.MINUTES);
++ } catch (TimeoutException e) {
++ throw Throwables.propagate(e);
++ }
++ } else {
++ LoginCredentials credentials = node.get().getCredentials();
++ return (credentials != null) ? credentials.getOptionalPassword().orNull() : null;
+ }
+ } else {
- LoginCredentials credentials = node.getCredentials();
- return (credentials != null) ? credentials.getPassword() : null;
++ throw new IllegalStateException("Node "+nodeId+" not present in "+getParent());
++ }
++ }
++
++ private LoginCredentials getLoginCredentials() {
++ OsCredential creds = LocationConfigUtils.getOsCredential(config().getBag());
++
++ return LoginCredentials.builder()
++ .user(getUser())
++ .privateKey(creds.hasKey() ? creds.getPrivateKeyData() : null)
++ .password(creds.hasPassword() ? creds.getPassword() : null)
++ .build();
++ }
++
++ /**
++ * Returns the known OsDetails, without any attempt to retrieve them if not known.
++ */
++ protected Optional<OsDetails> getOptionalOsDetails() {
++ Optional<MachineDetails> machineDetails = getOptionalMachineDetails();
++ OsDetails result = machineDetails.isPresent() ? machineDetails.get().getOsDetails() : null;
++ return Optional.fromNullable(result);
++ }
++
++ protected Optional<OperatingSystem> getOptionalOperatingSystem() {
++ Optional<NodeMetadata> node = getOptionalNode();
++
++ OperatingSystem os = node.isPresent() ? node.get().getOperatingSystem() : null;
++ if (os == null) {
++ // some nodes (eg cloudstack, gce) might not get OS available on the node,
++ // so also try taking it from the image if available
++ Optional<Image> image = getOptionalImage();
++ if (image.isPresent()) {
++ os = image.get().getOperatingSystem();
++ }
+ }
++ return Optional.fromNullable(os);
+ }
+
++ protected Optional<Hardware> getOptionalHardware() {
++ Optional<NodeMetadata> node = getOptionalNode();
++ return Optional.fromNullable(node.isPresent() ? node.get().getHardware() : null);
++ }
++
+ @Override
+ protected MachineDetails inferMachineDetails() {
+ Optional<String> name = Optional.absent();
+ Optional<String> version = Optional.absent();
+ Optional<String> architecture = Optional.absent();
-
- OperatingSystem os = node.getOperatingSystem();
- if (os == null && getTemplate() != null && getTemplate().getImage() != null)
- // some nodes (eg cloudstack, gce) might not get OS available on the node,
- // so also try taking it from the template if available
- os = getTemplate().getImage().getOperatingSystem();
-
- if (os != null) {
++ Optional<OperatingSystem> os = getOptionalOperatingSystem();
++ Optional<Hardware> hardware = getOptionalHardware();
++
++ if (os.isPresent()) {
+ // Note using family rather than name. Name is often unset.
- name = Optional.fromNullable(os.getFamily() != null && !OsFamily.UNRECOGNIZED.equals(os.getFamily()) ? os.getFamily().toString() : null);
- version = Optional.fromNullable(!Strings.isBlank(os.getVersion()) ? os.getVersion() : null);
+ // Using is64Bit rather then getArch because getArch often returns "paravirtual"
- architecture = Optional.fromNullable(os.is64Bit() ? BasicOsDetails.OsArchs.X_86_64 : BasicOsDetails.OsArchs.I386);
++ OsFamily family = os.get().getFamily();
++ String versionRaw = os.get().getVersion();
++ boolean is64Bit = os.get().is64Bit();
++ name = Optional.fromNullable(family != null && !OsFamily.UNRECOGNIZED.equals(family) ? family.toString() : null);
++ version = Optional.fromNullable(Strings.isNonBlank(versionRaw) ? versionRaw : null);
++ architecture = Optional.fromNullable(is64Bit ? BasicOsDetails.OsArchs.X_86_64 : BasicOsDetails.OsArchs.I386);
+ }
+
- Hardware hardware = node.getHardware();
- Optional<Integer> ram = hardware==null ? Optional.<Integer>absent() : Optional.fromNullable(hardware.getRam());
- Optional<Integer> cpus = hardware==null ? Optional.<Integer>absent() : Optional.fromNullable(hardware.getProcessors() != null ? hardware.getProcessors().size() : null);
++ Optional<Integer> ram = hardware.isPresent() ? Optional.fromNullable(hardware.get().getRam()) : Optional.<Integer>absent();
++ Optional<Integer> cpus = hardware.isPresent() ? Optional.fromNullable(hardware.get().getProcessors() != null ? hardware.get().getProcessors().size() : null) : Optional.<Integer>absent();
+
+ // Skip superclass' SSH to machine if all data is present, otherwise defer to super
+ if (name.isPresent() && version.isPresent() && architecture.isPresent() && ram.isPresent() && cpus.isPresent()) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Gathered machine details from Jclouds, skipping SSH test on {}", this);
+ }
+ OsDetails osD = new BasicOsDetails(name.get(), architecture.get(), version.get());
+ HardwareDetails hwD = new BasicHardwareDetails(cpus.get(), ram.get());
+ return new BasicMachineDetails(hwD, osD);
+ } else if ("false".equalsIgnoreCase(getConfig(JcloudsLocation.WAIT_FOR_SSHABLE))) {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Machine details for {} missing from Jclouds, but skipping SSH test because waitForSshable=false. name={}, version={}, " +
+ "arch={}, ram={}, #cpus={}",
+ new Object[]{this, name, version, architecture, ram, cpus});
+ }
+ OsDetails osD = new BasicOsDetails(name.orNull(), architecture.orNull(), version.orNull());
+ HardwareDetails hwD = new BasicHardwareDetails(cpus.orNull(), ram.orNull());
+ return new BasicMachineDetails(hwD, osD);
+ } else {
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Machine details for {} missing from Jclouds, using SSH test instead. name={}, version={}, " +
+ "arch={}, ram={}, #cpus={}",
+ new Object[]{this, name, version, architecture, ram, cpus});
+ }
+ return super.inferMachineDetails();
+ }
+ }
+
+ @Override
+ public Map<String, String> toMetadataRecord() {
- Hardware hardware = node.getHardware();
- List<? extends Processor> processors = (hardware != null) ? hardware.getProcessors() : null;
++ Optional<NodeMetadata> node = getOptionalNode();
++
++ Optional<Hardware> hardware = getOptionalHardware();
++ List<? extends Processor> processors = hardware.isPresent() ? hardware.get().getProcessors() : null;
+
+ ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
+ builder.putAll(super.toMetadataRecord());
+ putIfNotNull(builder, "provider", getParent().getProvider());
+ putIfNotNull(builder, "account", getParent().getIdentity());
- putIfNotNull(builder, "serverId", node.getProviderId());
- putIfNotNull(builder, "imageId", node.getImageId());
- putIfNotNull(builder, "instanceTypeName", (hardware != null ? hardware.getName() : null));
- putIfNotNull(builder, "instanceTypeId", (hardware != null ? hardware.getProviderId() : null));
- putIfNotNull(builder, "ram", "" + (hardware != null ? hardware.getRam() : null));
++ putIfNotNull(builder, "region", getParent().getRegion());
++ putIfNotNull(builder, "serverId", getJcloudsId());
++ putIfNotNull(builder, "imageId", getImageId());
++ putIfNotNull(builder, "instanceTypeName", (hardware.isPresent() ? hardware.get().getName() : null));
++ putIfNotNull(builder, "instanceTypeId", (hardware.isPresent() ? hardware.get().getProviderId() : null));
++ putIfNotNull(builder, "ram", "" + (hardware.isPresent() ? hardware.get().getRam() : null));
+ putIfNotNull(builder, "cpus", "" + (processors != null ? processors.size() : null));
+
+ try {
+ OsDetails osDetails = getOsDetails();
+ putIfNotNull(builder, "osName", osDetails.getName());
+ putIfNotNull(builder, "osArch", osDetails.getArch());
+ putIfNotNull(builder, "is64bit", osDetails.is64bit() ? "true" : "false");
+ } catch (Exception e) {
+ Exceptions.propagateIfFatal(e);
+ LOG.warn("Unable to get OS Details for "+node+"; continuing", e);
+ }
+
+ return builder.build();
+ }
+
+ private void putIfNotNull(ImmutableMap.Builder<String, String> builder, String key, @Nullable String value) {
+ if (value != null) builder.put(key, value);
+ }
+
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
index 0000000,7b84432..b58e783
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
+++ b/brooklyn-server/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsWinRmMachineLocation.java
@@@ -1,0 -1,153 +1,308 @@@
+ /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ package org.apache.brooklyn.location.jclouds;
+
+ import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth;
+
+ import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.Iterator;
+ import java.util.Set;
+
+ import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
+ import org.apache.brooklyn.util.core.flags.SetFromFlag;
+ import org.apache.brooklyn.util.net.Networking;
++import org.jclouds.compute.ComputeServiceContext;
++import org.jclouds.compute.callables.RunScriptOnNode;
++import org.jclouds.compute.domain.Image;
+ import org.jclouds.compute.domain.NodeMetadata;
+ import org.jclouds.compute.domain.Template;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+
++import com.google.common.annotations.VisibleForTesting;
+ import com.google.common.base.Objects;
+ import com.google.common.base.Optional;
-import com.google.common.net.HostAndPort;
++import com.google.common.collect.ImmutableSet;
+
+ public class JcloudsWinRmMachineLocation extends WinRmMachineLocation implements JcloudsMachineLocation {
+
+ private static final Logger LOG = LoggerFactory.getLogger(JcloudsWinRmMachineLocation.class);
+
+ @SetFromFlag
+ JcloudsLocation jcloudsParent;
+
++ /**
++ * @deprecated since 0.9.0; the node should not be persisted.
++ */
+ @SetFromFlag
++ @Deprecated
+ NodeMetadata node;
+
++ /**
++ * @deprecated since 0.9.0; the template should not be persisted.
++ */
+ @SetFromFlag
++ @Deprecated
+ Template template;
++
++ @SetFromFlag
++ String nodeId;
++
++ @SetFromFlag
++ String imageId;
++
++ @SetFromFlag
++ Set<String> privateAddresses;
++
++ @SetFromFlag
++ Set<String> publicAddresses;
++
++ @SetFromFlag
++ String hostname;
++
++ /**
++ * Historically, "node" and "template" were persisted. However that is a very bad idea!
++ * It means we pull in lots of jclouds classes into the persisted state. We are at an
++ * intermediate stage, where we want to handle rebinding to old state that has "node"
++ * and new state that should not. We therefore leave in the {@code @SetFromFlag} on node
++ * so that we read it back, but we ensure the value is null when we write it out!
++ *
++ * TODO We will rename these to get rid of the ugly underscore when the old node/template
++ * fields are deleted.
++ */
++ private transient Optional<NodeMetadata> _node;
++
++ private transient Optional<Template> _template;
+
++ private transient Optional<Image> _image;
++
++ private transient String _privateHostname;
++
+ public JcloudsWinRmMachineLocation() {
+ }
+
+ @Override
++ public void init() {
++ if (jcloudsParent != null) {
++ super.init();
++ if (node != null) {
++ setNode(node);
++ }
++ if (template != null) {
++ setTemplate(template);
++ }
++ } else {
++ // TODO Need to fix the rebind-detection, and not call init() on rebind.
++ // This will all change when locations become entities.
++ if (LOG.isDebugEnabled()) LOG.debug("Not doing init() of {} because parent not set; presuming rebinding", this);
++ }
++ }
++
++ @Override
++ public void rebind() {
++ super.rebind();
++
++ if (node != null) {
++ setNode(node);
++ node = null;
++ }
++
++ if (template != null) {
++ setTemplate(template);
++ template = null;
++ }
++ }
++
++ @Override
+ public String toVerboseString() {
+ return Objects.toStringHelper(this).omitNullValues()
+ .add("id", getId()).add("name", getDisplayName())
+ .add("user", getUser())
+ .add("address", getAddress())
+ .add("port", getPort())
- .add("node", getNode())
- .add("jcloudsId", getJcloudsId())
- .add("privateAddresses", node.getPrivateAddresses())
- .add("publicAddresses", node.getPublicAddresses())
++ .add("node", _node)
++ .add("nodeId", getJcloudsId())
++ .add("imageId", getImageId())
++ .add("privateAddresses", getPrivateAddresses())
++ .add("publicAddresses", getPublicAddresses())
+ .add("parentLocation", getParent())
+ .add("osDetails", getOsDetails())
+ .toString();
+ }
+
++ protected void setNode(NodeMetadata node) {
++ this.node = null;
++ nodeId = node.getId();
++ imageId = node.getImageId();
++ privateAddresses = node.getPrivateAddresses();
++ publicAddresses = node.getPublicAddresses();
++ hostname = node.getHostname();
++ _node = Optional.of(node);
++ }
++
++ protected void setTemplate(Template template) {
++ this.template = null;
++ _template = Optional.of(template);
++ _image = Optional.fromNullable(template.getImage());
++ }
++
+ @Override
+ public int getPort() {
+ return getConfig(WINRM_PORT);
+ }
+
+ @Override
- public NodeMetadata getNode() {
- return node;
++ public JcloudsLocation getParent() {
++ return jcloudsParent;
+ }
+
+ @Override
- public Template getTemplate() {
- return template;
++ public Optional<NodeMetadata> getOptionalNode() {
++ if (_node == null) {
++ _node = Optional.fromNullable(getParent().getComputeService().getNodeMetadata(nodeId));
++ }
++ return _node;
+ }
-
++
++ @VisibleForTesting
++ Optional<NodeMetadata> peekNode() {
++ return _node;
++ }
++
++ protected Optional<Image> getOptionalImage() {
++ if (_image == null) {
++ _image = Optional.fromNullable(getParent().getComputeService().getImage(imageId));
++ }
++ return _image;
++ }
++
++ /**
++ * @since 0.9.0
++ * @deprecated since 0.9.0 (only added as aid until the deprecated {@link #getTemplate()} is deleted)
++ */
++ @Deprecated
++ protected Optional<Template> getOptionalTemplate() {
++ if (_template == null) {
++ _template = Optional.absent();
++ }
++ return _template;
++ }
++
++ /**
++ * @deprecated since 0.9.0
++ */
+ @Override
- public JcloudsLocation getParent() {
- return jcloudsParent;
++ @Deprecated
++ public NodeMetadata getNode() {
++ Optional<NodeMetadata> result = getOptionalNode();
++ if (result.isPresent()) {
++ return result.get();
++ } else {
++ throw new IllegalStateException("Node "+nodeId+" not present in "+getParent());
++ }
+ }
-
++
++ /**
++ * @deprecated since 0.9.0
++ */
++ @Override
++ @Deprecated
++ public Template getTemplate() {
++ Optional<Template> result = getOptionalTemplate();
++ if (result.isPresent()) {
++ String msg = "Deprecated use of getTemplate() for "+this;
++ LOG.warn(msg + " - see debug log for stacktrace");
++ LOG.debug(msg, new Exception("for stacktrace"));
++ return result.get();
++ } else {
++ throw new IllegalStateException("Template for "+nodeId+" (in "+getParent()+") not cached (deprecated use of getTemplate())");
++ }
++ }
++
+ @Override
+ public String getHostname() {
++ if (hostname != null) {
++ return hostname;
++ }
+ InetAddress address = getAddress();
+ return (address != null) ? address.getHostAddress() : null;
+ }
-
++
++
++ /** In clouds like AWS, the public hostname is the only way to ensure VMs in different zones can access each other. */
+ @Override
+ public Set<String> getPublicAddresses() {
- return node.getPublicAddresses();
++ return (publicAddresses == null) ? ImmutableSet.<String>of() : publicAddresses;
+ }
+
+ @Override
+ public Set<String> getPrivateAddresses() {
- return node.getPrivateAddresses();
++ return (privateAddresses == null) ? ImmutableSet.<String>of() : privateAddresses;
+ }
+
++
+ @Override
+ public String getSubnetHostname() {
- // TODO: TEMP FIX: WAS:
- // String publicHostname = jcloudsParent.getPublicHostname(node, Optional.<HostAndPort>absent(), config().getBag());
- // but this causes a call to JcloudsUtil.getFirstReachableAddress, which searches for accessible SSH service.
- // This workaround is good for public nodes but not private-subnet ones.
- return getHostname();
++ // Same impl as JcloudsSshMachineLocation
++ if (_privateHostname == null) {
++ for (String p : getPrivateAddresses()) {
++ if (Networking.isLocalOnly(p)) continue;
++ _privateHostname = p;
++ }
++ if (groovyTruth(getPublicAddresses())) {
++ _privateHostname = getPublicAddresses().iterator().next();
++ } else if (groovyTruth(getHostname())) {
++ _privateHostname = getHostname();
++ } else {
++ return null;
++ }
++ }
++ return _privateHostname;
+ }
+
+ @Override
+ public String getSubnetIp() {
++ // Same impl as JcloudsSshMachineLocation
+ Optional<String> privateAddress = getPrivateAddress();
+ if (privateAddress.isPresent()) {
+ return privateAddress.get();
+ }
-
- String hostname = jcloudsParent.getPublicHostname(node, Optional.<HostAndPort>absent(), config().getBag());
- if (hostname != null && !Networking.isValidIp4(hostname)) {
- try {
- return InetAddress.getByName(hostname).getHostAddress();
- } catch (UnknownHostException e) {
- LOG.debug("Cannot resolve IP for hostname {} of machine {} (so returning hostname): {}", new Object[] {hostname, this, e});
- }
++ if (groovyTruth(node.getPublicAddresses())) {
++ return node.getPublicAddresses().iterator().next();
+ }
- return hostname;
++ return null;
+ }
+
+ protected Optional<String> getPrivateAddress() {
- if (groovyTruth(node.getPrivateAddresses())) {
- Iterator<String> pi = node.getPrivateAddresses().iterator();
- while (pi.hasNext()) {
- String p = pi.next();
- // disallow local only addresses
- if (Networking.isLocalOnly(p)) continue;
- // other things may be public or private, but either way, return it
- return Optional.of(p);
- }
++ // Same impl as JcloudsSshMachineLocation
++ for (String p : getPrivateAddresses()) {
++ if (Networking.isLocalOnly(p)) continue;
++ return Optional.of(p);
+ }
+ return Optional.absent();
+ }
+
+ @Override
+ public String getJcloudsId() {
- return node.getId();
++ return nodeId;
++ }
++
++ protected String getImageId() {
++ return imageId;
+ }
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
index 0000000,851505d..2a8900a
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
+++ b/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationTest.java
@@@ -1,0 -1,604 +1,610 @@@
+ /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ package org.apache.brooklyn.location.jclouds;
+
+ import java.util.Collection;
+ import java.util.Map;
+ import java.util.concurrent.CountDownLatch;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicInteger;
+
+ import javax.annotation.Nullable;
+
+ import org.apache.brooklyn.api.location.LocationSpec;
+ import org.apache.brooklyn.api.location.MachineLocation;
+ import org.apache.brooklyn.api.location.MachineLocationCustomizer;
+ import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+ import org.apache.brooklyn.config.ConfigKey;
+ import org.apache.brooklyn.core.config.ConfigKeys;
+ import org.apache.brooklyn.core.entity.Entities;
+ import org.apache.brooklyn.core.internal.BrooklynProperties;
+ import org.apache.brooklyn.core.location.LocationConfigKeys;
+ import org.apache.brooklyn.core.location.cloud.names.CustomMachineNamer;
+ import org.apache.brooklyn.core.location.geo.HostGeoInfo;
+ import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
+ import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
+ import org.apache.brooklyn.location.jclouds.JcloudsLocation.UserCreation;
+ import org.apache.brooklyn.test.Asserts;
+ import org.apache.brooklyn.util.collections.MutableMap;
+ import org.apache.brooklyn.util.core.config.ConfigBag;
+ import org.apache.brooklyn.util.exceptions.Exceptions;
+ import org.jclouds.scriptbuilder.domain.OsFamily;
+ import org.jclouds.scriptbuilder.domain.StatementList;
+ import org.mockito.Mockito;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.Assert;
+ import org.testng.annotations.AfterMethod;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
+
+ import com.google.common.base.Function;
+ import com.google.common.base.Predicate;
+ import com.google.common.collect.ContiguousSet;
+ import com.google.common.collect.DiscreteDomain;
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableMap;
++import com.google.common.collect.ImmutableSet;
+ import com.google.common.collect.Lists;
+ import com.google.common.collect.Range;
+ import com.google.common.primitives.Ints;
+ import com.google.common.reflect.TypeToken;
+
+ /**
+ * @author Shane Witbeck
+ */
+ public class JcloudsLocationTest implements JcloudsLocationConfig {
+
+ private static final Logger log = LoggerFactory.getLogger(JcloudsLocationTest.class);
+
+ public static Predicate<ConfigBag> checkerFor(final String user, final Integer minRam, final Integer minCores) {
+ return new Predicate<ConfigBag>() {
+ @Override
+ public boolean apply(@Nullable ConfigBag input) {
+ Assert.assertEquals(input.get(USER), user);
+ Assert.assertEquals(input.get(MIN_RAM), minRam);
+ Assert.assertEquals(input.get(MIN_CORES), minCores);
+ return true;
+ }
+ };
+ }
+
+ public static Predicate<ConfigBag> templateCheckerFor(final String ports) {
+ return new Predicate<ConfigBag>() {
+ @Override
+ public boolean apply(@Nullable ConfigBag input) {
+ Assert.assertEquals(input.get(INBOUND_PORTS), ports);
+ return false;
+ }
+ };
+ }
+
+ private LocalManagementContext managementContext;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
- managementContext = LocalManagementContextForTests.newInstance(BrooklynProperties.Factory.builderEmpty().build());
++ managementContext = LocalManagementContextForTests.newInstance(BrooklynProperties.Factory.newDefault());
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearUp() throws Exception {
+ if (managementContext != null) Entities.destroyAll(managementContext);
+ }
+
+ @Test
+ public void testCreateWithFlagsDirectly() throws Exception {
+ BailOutJcloudsLocation jcl = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+ jcl.tryObtainAndCheck(MutableMap.of(MIN_CORES, 2), checkerFor("fred", 16, 2));
+ }
+
+ @Test
+ public void testCreateWithFlagsDirectlyAndOverride() throws Exception {
+ BailOutJcloudsLocation jcl = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+ jcl.tryObtainAndCheck(MutableMap.of(MIN_CORES, 2, MIN_RAM, 8), checkerFor("fred", 8, 2));
+ }
+
+ @Test
+ public void testCreateWithFlagsSubLocation() throws Exception {
+ BailOutJcloudsLocation jcl = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+ jcl = (BailOutJcloudsLocation) jcl.newSubLocation(MutableMap.of(USER, "jon", MIN_CORES, 2));
+ jcl.tryObtainAndCheck(MutableMap.of(MIN_CORES, 3), checkerFor("jon", 16, 3));
+ }
+
+ @Test
+ public void testSingleInttoIntPortArray() {
+ int port = 1;
+ int[] intArray = new int[] {1};
+ Assert.assertEquals(JcloudsLocation.toIntPortArray(port), intArray);
+ }
+
+ @Test
+ public void testSingleStringtoIntPortArray() {
+ String portString = "1";
+ int[] intArray = new int[] {1};
+ Assert.assertEquals(JcloudsLocation.toIntPortArray(portString), intArray);
+ }
+
+ @Test
+ public void testStringListWithBracketstoIntPortArray() {
+ String listString = "[1, 2, 3, 4]";
+ int[] intArray = new int[] {1, 2, 3, 4};
+ Assert.assertEquals(JcloudsLocation.toIntPortArray(listString), intArray);
+ }
+
+ @Test
+ public void testStringListWithoutBracketstoIntPortArray() {
+ String listString = "1, 2, 3, 4";
+ int[] intArray = new int[] {1, 2, 3, 4};
+ Assert.assertEquals(JcloudsLocation.toIntPortArray(listString), intArray);
+ }
+
+ @Test
+ public void testEmptyStringListtoIntPortArray() {
+ String listString = "[]";
+ int[] intArray = new int[] {};
+ Assert.assertEquals(JcloudsLocation.toIntPortArray(listString), intArray);
+ }
+
+ @Test
+ public void testIntArraytoIntPortArray() {
+ int[] intArray = new int[] {1, 2, 3, 4};
+ Assert.assertEquals(JcloudsLocation.toIntPortArray(intArray), intArray);
+ }
+
+ @Test
+ public void testObjectArrayOfIntegerstoIntPortArray() {
+ Object[] integerObjectArray = new Object[] {1, 2, 3, 4};
+ int[] intArray = new int[] {1, 2, 3, 4};
+ Assert.assertEquals(JcloudsLocation.toIntPortArray(integerObjectArray), intArray);
+ }
+
+ @Test
+ public void testObjectArrayOfStringstoIntPortArray() {
+ Object[] stringObjectArray = new Object[] {"1", "2", "3", "4"};
+ int[] intArray = new int[] {1, 2, 3, 4};
+ Assert.assertEquals(JcloudsLocation.toIntPortArray(stringObjectArray), intArray);
+ }
+
+ @Test
+ public void testStringArraytoIntPortArray() {
+ String[] stringArray = new String[] {"1", "2", "3"};
+ int[] intArray = new int[] {1, 2, 3};
+ Assert.assertEquals(JcloudsLocation.toIntPortArray(stringArray), intArray);
+ }
+
+ @Test
+ public void testStringPortRangetoIntPortArray() {
+ String portRange = "1-100";
+ int[] intArray = Ints.toArray(ContiguousSet.create(Range.closed(1, 100), DiscreteDomain.integers()));
+ Assert.assertEquals(intArray, JcloudsLocation.toIntPortArray(portRange));
+ }
+
+ @Test
+ public void testStringPortPlustoIntPortArray() {
+ String portPlus = "100+";
+ int[] intArray = Ints.toArray(ContiguousSet.create(Range.closed(100, 65535), DiscreteDomain.integers()));
+ Assert.assertEquals(intArray, JcloudsLocation.toIntPortArray(portPlus));
+ }
+
+ @Test
+ public void testCombinationOfInputstoIntPortArray() {
+ Collection<Object> portInputs = Lists.newLinkedList();
+ portInputs.add(1);
+ portInputs.add("2");
+ portInputs.add("3-100");
+ portInputs.add("101,102,103");
+ portInputs.add("[104,105,106]");
+ portInputs.add(new int[] {107, 108, 109});
+ portInputs.add(new String[] {"110", "111", "112"});
+ portInputs.add(new Object[] {113, 114, 115});
+
+ int[] intArray = Ints.toArray(ContiguousSet.create(Range.closed(1, 115), DiscreteDomain.integers()));
+ Assert.assertEquals(intArray, JcloudsLocation.toIntPortArray(portInputs));
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testMalformedStringNumbertoIntPortArray() {
+ String numberStr = "1i";
+ JcloudsLocation.toIntPortArray(numberStr);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testMalformedStringRangetoIntPortArray() {
+ String rangeString = "1-";
+ JcloudsLocation.toIntPortArray(rangeString);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testMalformedStringListWithBracketstoIntPortArray() {
+ String listString = "[1,2,e]";
+ JcloudsLocation.toIntPortArray(listString);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testMalformedStringListWithoutBracketstoIntPortArray() {
+ String listString = "1,2,e";
+ JcloudsLocation.toIntPortArray(listString);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testMalformedStringArraytoIntPortArray() {
+ String[] stringArray = new String[] {"1", "2", "e"};
+ JcloudsLocation.toIntPortArray(stringArray);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testIllegalObjectArrayOfDoublestoIntPortArray() {
+ Object[] doubleObjectArray = new Object[] {1.0, 2.0, 3.0};
+ JcloudsLocation.toIntPortArray(doubleObjectArray);
+ }
+
+ @Test
+ public void testVMCreationIsRetriedOnFailure() {
+ final AtomicInteger count = new AtomicInteger();
+ Function<ConfigBag, Void> countingInterceptor = new Function<ConfigBag, Void>() {
+ @Override public Void apply(ConfigBag input) {
+ count.incrementAndGet();
+ return null;
+ }
+ };
+ BailOutJcloudsLocation loc = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext, ImmutableMap.<ConfigKey<?>, Object>of(
+ MACHINE_CREATE_ATTEMPTS, 3,
+ BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, countingInterceptor));
+ loc.tryObtain();
+ Assert.assertEquals(count.get(), 3);
+ }
+
+ @Test(groups={"Live", "Live-sanity"})
+ public void testCreateWithInboundPorts() {
+ BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocationForLiveTest(managementContext);
+ jcloudsLocation = (BailOutJcloudsLocation) jcloudsLocation.newSubLocation(MutableMap.of());
+ jcloudsLocation.tryObtainAndCheck(MutableMap.of(), templateCheckerFor("[22, 80, 9999]"));
+ int[] ports = new int[] {22, 80, 9999};
+ Assert.assertEquals(jcloudsLocation.getTemplate().getOptions().getInboundPorts(), ports);
+ }
+
+ @Test(groups={"Live", "Live-sanity"})
+ public void testCreateWithInboundPortsOverride() {
+ BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocationForLiveTest(managementContext);
+ jcloudsLocation = (BailOutJcloudsLocation) jcloudsLocation.newSubLocation(MutableMap.of());
+ jcloudsLocation.tryObtainAndCheck(MutableMap.of(INBOUND_PORTS, "[23, 81, 9998]"), templateCheckerFor("[23, 81, 9998]"));
+ int[] ports = new int[] {23, 81, 9998};
+ Assert.assertEquals(jcloudsLocation.getTemplate().getOptions().getInboundPorts(), ports);
+ }
+
+ @Test
+ public void testCreateWithMaxConcurrentCallsUnboundedByDefault() throws Exception {
+ final int numCalls = 20;
+ ConcurrencyTracker interceptor = new ConcurrencyTracker();
+ ExecutorService executor = Executors.newCachedThreadPool();
+
+ try {
+ final BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+ managementContext, ImmutableMap.<ConfigKey<?>, Object>of(
+ BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, interceptor));
+ for (int i = 0; i < numCalls; i++) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ jcloudsLocation.tryObtain();
+ }
+ });
+ }
+ interceptor.assertCallCountEventually(numCalls);
+ interceptor.unblock();
+ executor.shutdown();
+ executor.awaitTermination(10, TimeUnit.SECONDS);
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ @Test(groups="Integration") // because takes 1 sec
+ public void testCreateWithMaxConcurrentCallsRespectsConfig() throws Exception {
+ final int numCalls = 4;
+ final int maxConcurrentCreations = 2;
+ ConcurrencyTracker interceptor = new ConcurrencyTracker();
+ ExecutorService executor = Executors.newCachedThreadPool();
+
+ try {
+ final BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+ managementContext, ImmutableMap.of(
+ BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, interceptor,
+ MAX_CONCURRENT_MACHINE_CREATIONS, maxConcurrentCreations));
+
+ for (int i = 0; i < numCalls; i++) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ jcloudsLocation.tryObtain();
+ }
+ });
+ }
+
+ interceptor.assertCallCountEventually(maxConcurrentCreations);
+ interceptor.assertCallCountContinually(maxConcurrentCreations);
+
+ interceptor.unblock();
+ interceptor.assertCallCountEventually(numCalls);
+ executor.shutdown();
+ executor.awaitTermination(10, TimeUnit.SECONDS);
+
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ @Test(groups="Integration") // because takes 1 sec
+ public void testCreateWithMaxConcurrentCallsAppliesToSubLocations() throws Exception {
+ final int numCalls = 4;
+ final int maxConcurrentCreations = 2;
+ ConcurrencyTracker interceptor = new ConcurrencyTracker();
+ ExecutorService executor = Executors.newCachedThreadPool();
+
+ try {
+ final BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+ managementContext, ImmutableMap.of(
+ BailOutJcloudsLocation.BUILD_TEMPLATE_INTERCEPTOR, interceptor,
+ MAX_CONCURRENT_MACHINE_CREATIONS, maxConcurrentCreations));
+
+ for (int i = 0; i < numCalls; i++) {
+ final BailOutJcloudsLocation subLocation = (BailOutJcloudsLocation) jcloudsLocation.newSubLocation(MutableMap.of());
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ subLocation.tryObtain();
+ }
+ });
+ }
+
+ interceptor.assertCallCountEventually(maxConcurrentCreations);
+ interceptor.assertCallCountContinually(maxConcurrentCreations);
+
+ interceptor.unblock();
+ interceptor.assertCallCountEventually(numCalls);
+ executor.shutdown();
+ executor.awaitTermination(10, TimeUnit.SECONDS);
+
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ @Test
+ public void testCreateWithCustomMachineNamer() {
+ final String machineNamerClass = CustomMachineNamer.class.getName();
+ BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+ managementContext, ImmutableMap.<ConfigKey<?>, Object>of(
+ LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS, machineNamerClass));
+ jcloudsLocation.tryObtainAndCheck(ImmutableMap.of(CustomMachineNamer.MACHINE_NAME_TEMPLATE, "ignored"), new Predicate<ConfigBag>() {
+ public boolean apply(ConfigBag input) {
+ Assert.assertEquals(input.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS), machineNamerClass);
+ return true;
+ }
+ });
+ }
+
+ @Test
+ public void testCreateWithCustomMachineNamerOnObtain() {
+ final String machineNamerClass = CustomMachineNamer.class.getName();
+ BailOutJcloudsLocation jcloudsLocation = BailOutJcloudsLocation.newBailOutJcloudsLocation(managementContext);
+ ImmutableMap<ConfigKey<String>, String> flags = ImmutableMap.of(
+ CustomMachineNamer.MACHINE_NAME_TEMPLATE, "ignored",
+ LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS, machineNamerClass);
+ jcloudsLocation.tryObtainAndCheck(flags, new Predicate<ConfigBag>() {
+ public boolean apply(ConfigBag input) {
+ Assert.assertEquals(input.get(LocationConfigKeys.CLOUD_MACHINE_NAMER_CLASS), machineNamerClass);
+ return true;
+ }
+ });
+ }
+
+ public static class ConcurrencyTracker implements Function<ConfigBag,Void> {
+ final AtomicInteger concurrentCallsCounter = new AtomicInteger();
+ final CountDownLatch continuationLatch = new CountDownLatch(1);
+
+ @Override public Void apply(ConfigBag input) {
+ concurrentCallsCounter.incrementAndGet();
+ try {
+ continuationLatch.await();
+ } catch (InterruptedException e) {
+ throw Exceptions.propagate(e);
+ }
+ return null;
+ }
+
+ public void unblock() {
+ continuationLatch.countDown();
+ }
+
+ public void assertCallCountEventually(final int expected) {
+ Asserts.succeedsEventually(new Runnable() {
+ @Override public void run() {
+ Assert.assertEquals(concurrentCallsCounter.get(), expected);
+ }
+ });
+ }
+
+ public void assertCallCountContinually(final int expected) {
+ Asserts.succeedsContinually(new Runnable() {
+ @Override public void run() {
+ Assert.assertEquals(concurrentCallsCounter.get(), expected);
+ }
+ });
+ }
+ }
+
+
+ @SuppressWarnings("serial")
+ public static class FakeLocalhostWithParentJcloudsLocation extends JcloudsLocation {
+ public static final ConfigKey<Function<ConfigBag,Void>> BUILD_TEMPLATE_INTERCEPTOR = ConfigKeys.newConfigKey(new TypeToken<Function<ConfigBag,Void>>() {}, "buildtemplateinterceptor");
+
+ ConfigBag lastConfigBag;
+
+ public FakeLocalhostWithParentJcloudsLocation() {
+ super();
+ }
+
+ public FakeLocalhostWithParentJcloudsLocation(Map<?, ?> conf) {
+ super(conf);
+ }
+
+ @Override
+ public JcloudsSshMachineLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
+ JcloudsSshMachineLocation result = getManagementContext().getLocationManager().createLocation(LocationSpec.create(JcloudsSshMachineLocation.class)
+ .configure("address", "127.0.0.1")
+ .configure("port", 22)
+ .configure("user", "bob")
- .configure("jcloudsParent", this));
++ .configure("jcloudsParent", this)
++ .configure("nodeId", "myNodeId")
++ .configure("imageId", "myImageId")
++ .configure("privateAddresses", ImmutableSet.of("10.0.0.1"))
++ .configure("publicAddresses", ImmutableSet.of("56.0.0.1"))
++ );
+ registerJcloudsMachineLocation("bogus", result);
+
+ // explicitly invoke this customizer, to comply with tests
+ for (JcloudsLocationCustomizer customizer : getCustomizers(config().getBag())) {
+ customizer.customize(this, null, (JcloudsMachineLocation)result);
+ }
+ for (MachineLocationCustomizer customizer : getMachineCustomizers(config().getBag())) {
+ customizer.customize((JcloudsMachineLocation)result);
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void releaseNode(String instanceId) {
+ // no-op
+ }
+ }
+
+ @Test
+ public void testInheritsGeo() throws Exception {
+ ConfigBag allConfig = ConfigBag.newInstance()
+ .configure(IMAGE_ID, "bogus")
+ .configure(CLOUD_PROVIDER, "aws-ec2")
+ .configure(CLOUD_REGION_ID, "bogus")
+ .configure(ACCESS_IDENTITY, "bogus")
+ .configure(ACCESS_CREDENTIAL, "bogus")
+ .configure(LocationConfigKeys.LATITUDE, 42d)
+ .configure(LocationConfigKeys.LONGITUDE, -20d)
+ .configure(MACHINE_CREATE_ATTEMPTS, 1);
+ FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class).configure(allConfig.getAllConfig()));
+ MachineLocation l = ll.obtain();
+ log.info("loc:" +l);
+ HostGeoInfo geo = HostGeoInfo.fromLocation(l);
+ log.info("geo: "+geo);
+ Assert.assertEquals(geo.latitude, 42d, 0.00001);
+ Assert.assertEquals(geo.longitude, -20d, 0.00001);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testInheritsGeoFromLocationMetadataProperties() throws Exception {
+ // in location-metadata.properties:
+ // brooklyn.location.jclouds.softlayer@wdc01.latitude=38.909202
+ // brooklyn.location.jclouds.softlayer@wdc01.longitude=-77.47314
+ ConfigBag allConfig = ConfigBag.newInstance()
+ .configure(IMAGE_ID, "bogus")
+ .configure(CLOUD_PROVIDER, "softlayer")
+ .configure(CLOUD_REGION_ID, "wdc01")
+ .configure(ACCESS_IDENTITY, "bogus")
+ .configure(ACCESS_CREDENTIAL, "bogus")
+ .configure(MACHINE_CREATE_ATTEMPTS, 1);
+ FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class)
+ .configure(new JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties("softlayer", "wdc01", null, managementContext.getBrooklynProperties()))
+ .configure(allConfig.getAllConfig()));
+ MachineLocation l = ll.obtain();
+ log.info("loc:" +l);
+ HostGeoInfo geo = HostGeoInfo.fromLocation(l);
+ log.info("geo: "+geo);
+ Assert.assertEquals(geo.latitude, 38.909202d, 0.00001);
+ Assert.assertEquals(geo.longitude, -77.47314d, 0.00001);
+ }
+
+ @Test
+ public void testInvokesCustomizerCallbacks() throws Exception {
+ JcloudsLocationCustomizer customizer = Mockito.mock(JcloudsLocationCustomizer.class);
+ MachineLocationCustomizer machineCustomizer = Mockito.mock(MachineLocationCustomizer.class);
+ // Mockito.when(customizer.customize(Mockito.any(JcloudsLocation.class), Mockito.any(ComputeService.class), Mockito.any(JcloudsSshMachineLocation.class)));
+ ConfigBag allConfig = ConfigBag.newInstance()
+ .configure(CLOUD_PROVIDER, "aws-ec2")
+ .configure(ACCESS_IDENTITY, "bogus")
+ .configure(ACCESS_CREDENTIAL, "bogus")
+ .configure(JcloudsLocationConfig.JCLOUDS_LOCATION_CUSTOMIZERS, ImmutableList.of(customizer))
+ .configure(JcloudsLocation.MACHINE_LOCATION_CUSTOMIZERS, ImmutableList.of(machineCustomizer))
+ .configure(MACHINE_CREATE_ATTEMPTS, 1);
+ FakeLocalhostWithParentJcloudsLocation ll = managementContext.getLocationManager().createLocation(LocationSpec.create(FakeLocalhostWithParentJcloudsLocation.class).configure(allConfig.getAllConfig()));
+ JcloudsMachineLocation l = (JcloudsMachineLocation)ll.obtain();
+ Mockito.verify(customizer, Mockito.times(1)).customize(ll, null, l);
+ Mockito.verify(customizer, Mockito.never()).preRelease(l);
+ Mockito.verify(customizer, Mockito.never()).postRelease(l);
+ Mockito.verify(machineCustomizer, Mockito.times(1)).customize(l);
+ Mockito.verify(machineCustomizer, Mockito.never()).preRelease(l);
+
+ ll.release(l);
+ Mockito.verify(customizer, Mockito.times(1)).preRelease(l);
+ Mockito.verify(customizer, Mockito.times(1)).postRelease(l);
+ Mockito.verify(machineCustomizer, Mockito.times(1)).preRelease(l);
+ }
+
+ // now test creating users
+
+ protected String getCreateUserStatementsFor(Map<ConfigKey<?>,?> config) {
+ BailOutJcloudsLocation jl = BailOutJcloudsLocation.newBailOutJcloudsLocation(
+ managementContext, MutableMap.<ConfigKey<?>, Object>builder()
+ .put(JcloudsLocationConfig.LOGIN_USER, "root").put(JcloudsLocationConfig.LOGIN_USER_PASSWORD, "m0ck")
+ .put(JcloudsLocationConfig.USER, "bob").put(JcloudsLocationConfig.LOGIN_USER_PASSWORD, "b0b")
+ .putAll(config).build());
+
+ UserCreation creation = jl.createUserStatements(null, jl.config().getBag());
+ return new StatementList(creation.statements).render(OsFamily.UNIX);
+ }
+
+ @Test
+ public void testDisablesRoot() {
+ String statements = getCreateUserStatementsFor(ImmutableMap.<ConfigKey<?>, Object>of());
+ Assert.assertTrue(statements.contains("PermitRootLogin"), "Error:\n"+statements);
+ Assert.assertTrue(statements.matches("(?s).*sudoers.*useradd.*bob.*wheel.*"), "Error:\n"+statements);
+ }
+
+ @Test
+ public void testDisableRootFalse() {
+ String statements = getCreateUserStatementsFor(ImmutableMap.<ConfigKey<?>, Object>of(
+ JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH, false));
+ Assert.assertFalse(statements.contains("PermitRootLogin"), "Error:\n"+statements);
+ Assert.assertTrue(statements.matches("(?s).*sudoers.*useradd.*bob.*wheel.*"), "Error:\n"+statements);
+ }
+
+ @Test
+ public void testDisableRootAndSudoFalse() {
+ String statements = getCreateUserStatementsFor(ImmutableMap.<ConfigKey<?>, Object>of(
+ JcloudsLocationConfig.DISABLE_ROOT_AND_PASSWORD_SSH, false,
+ JcloudsLocationConfig.GRANT_USER_SUDO, false));
+ Assert.assertFalse(statements.contains("PermitRootLogin"), "Error:\n"+statements);
+ Assert.assertFalse(statements.matches("(?s).*sudoers.*useradd.*bob.*wheel.*"), "Error:\n"+statements);
+ }
+
+ // TODO more tests, where flags come in from resolver, named locations, etc
+
+ }
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/018a0e15/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsRebindLiveTest.java
----------------------------------------------------------------------
diff --cc brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsRebindLiveTest.java
index 0000000,b81d930..de3fb1c
mode 000000,100644..100644
--- a/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsRebindLiveTest.java
+++ b/brooklyn-server/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsRebindLiveTest.java
@@@ -1,0 -1,188 +1,231 @@@
+ /*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+ package org.apache.brooklyn.location.jclouds;
+
+ import static com.google.common.base.Preconditions.checkNotNull;
+ import static org.testng.Assert.assertEquals;
++import static org.testng.Assert.assertFalse;
++import static org.testng.Assert.assertNull;
++import static org.testng.Assert.fail;
+
+ import java.util.List;
+ import java.util.Map;
+
+ import org.apache.brooklyn.api.location.LocationSpec;
++import org.apache.brooklyn.api.location.MachineLocation;
+ import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+ import org.apache.brooklyn.core.location.Locations;
+ import org.apache.brooklyn.core.mgmt.rebind.RebindTestFixtureWithApp;
+ import org.apache.brooklyn.location.ssh.SshMachineLocation;
++import org.apache.brooklyn.location.winrm.WinRmMachineLocation;
++import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse;
+ import org.apache.brooklyn.util.exceptions.CompoundRuntimeException;
+ import org.apache.brooklyn.util.stream.Streams;
+ import org.jclouds.compute.domain.NodeMetadata;
+ import org.jclouds.compute.domain.Template;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ import org.testng.Assert;
+ import org.testng.annotations.AfterMethod;
+ import org.testng.annotations.BeforeMethod;
+ import org.testng.annotations.Test;
+
+ import com.google.common.collect.ImmutableList;
+ import com.google.common.collect.ImmutableMap;
+ import com.google.common.collect.Lists;
+
+ /**
+ * Tests rebind (i.e. restarting Brooklyn server) when there are live JcloudsSshMachineLocation object(s).
+ */
+ public class JcloudsRebindLiveTest extends RebindTestFixtureWithApp {
+
+ // TODO Duplication of AbstractJcloudsLiveTest, because we're subcalling RebindTestFixture instead.
+
+ // TODO The mgmts tracking was added when I tried to combine JcloudsRebindLiveTest and JcloudsByonRebindLiveTest,
+ // but turns out that is not worth the effort!
+
+ private static final Logger LOG = LoggerFactory.getLogger(JcloudsRebindLiveTest.class);
+
+ public static final String AWS_EC2_REGION_NAME = AbstractJcloudsLiveTest.AWS_EC2_USEAST_REGION_NAME;
+ public static final String AWS_EC2_LOCATION_SPEC = "jclouds:" + AbstractJcloudsLiveTest.AWS_EC2_PROVIDER + (AWS_EC2_REGION_NAME == null ? "" : ":" + AWS_EC2_REGION_NAME);
+
+ // Image: {id=us-east-1/ami-7d7bfc14, providerId=ami-7d7bfc14, name=RightImage_CentOS_6.3_x64_v5.8.8.5, location={scope=REGION, id=us-east-1, description=us-east-1, parent=aws-ec2, iso3166Codes=[US-VA]}, os={family=centos, arch=paravirtual, version=6.0, description=rightscale-us-east/RightImage_CentOS_6.3_x64_v5.8.8.5.manifest.xml, is64Bit=true}, description=rightscale-us-east/RightImage_CentOS_6.3_x64_v5.8.8.5.manifest.xml, version=5.8.8.5, status=AVAILABLE[available], loginUser=root, userMetadata={owner=411009282317, rootDeviceType=instance-store, virtualizationType=paravirtual, hypervisor=xen}}
+ public static final String AWS_EC2_CENTOS_IMAGE_ID = "us-east-1/ami-7d7bfc14";
+
+ public static final String SOFTLAYER_LOCATION_SPEC = "jclouds:" + AbstractJcloudsLiveTest.SOFTLAYER_PROVIDER;
+
- protected List<JcloudsSshMachineLocation> machines;
++ protected List<JcloudsMachineLocation> machines;
+
+ @BeforeMethod(alwaysRun=true)
+ public void setUp() throws Exception {
+ super.setUp();
+ machines = Lists.newCopyOnWriteArrayList();
+
+ // Don't let any defaults from brooklyn.properties (except credentials) interfere with test
+ AbstractJcloudsLiveTest.stripBrooklynProperties(origManagementContext.getBrooklynProperties());
+ }
+
+ @AfterMethod(alwaysRun=true)
+ public void tearDown() throws Exception {
+ List<Exception> exceptions = Lists.newArrayList();
+ try {
+ exceptions.addAll(releaseMachineSafely(machines));
+ machines.clear();
+ } finally {
+ super.tearDown();
+ }
+
+ if (exceptions.size() > 0) {
+ throw new CompoundRuntimeException("Error in tearDown of "+getClass(), exceptions);
+ }
+ }
+
+ @Override
+ protected boolean useLiveManagementContext() {
+ return true;
+ }
+
+ @Test(groups = {"Live"})
+ public void testEc2Rebind() throws Exception {
+ ImmutableMap<String, Object> obtainFlags = ImmutableMap.<String,Object>builder()
+ .put("imageId", AWS_EC2_CENTOS_IMAGE_ID)
- .put("hardwareId", AbstractJcloudsLiveTest.AWS_EC2_SMALL_HARDWARE_ID)
++ .put("hardwareId", AbstractJcloudsLiveTest.AWS_EC2_MEDIUM_HARDWARE_ID)
+ .put("inboundPorts", ImmutableList.of(22))
+ .build();
+ runTest(AWS_EC2_LOCATION_SPEC, obtainFlags);
+ }
+
+ @Test(groups = {"Live"})
++ public void testEc2WinrmRebind() throws Exception {
++ ImmutableMap<String, Object> obtainFlags = ImmutableMap.<String,Object>builder()
++ .put("imageNameRegex", "Windows_Server-2012-R2_RTM-English-64Bit-Base-.*")
++ .put("imageOwner", "801119661308")
++ .put("hardwareId", AbstractJcloudsLiveTest.AWS_EC2_MEDIUM_HARDWARE_ID)
++ .put("useJcloudsSshInit", false)
++ .put("inboundPorts", ImmutableList.of(5985, 3389))
++ .build();
++ runTest(AWS_EC2_LOCATION_SPEC, obtainFlags);
++ }
++
++ @Test(groups = {"Live"})
+ public void testSoftlayerRebind() throws Exception {
+ runTest(SOFTLAYER_LOCATION_SPEC, ImmutableMap.of("inboundPorts", ImmutableList.of(22)));
+ }
+
+ protected void runTest(String locSpec, Map<String, ?> obtainFlags) throws Exception {
+ JcloudsLocation location = (JcloudsLocation) mgmt().getLocationRegistry().resolve(locSpec);
+
- JcloudsSshMachineLocation origMachine = obtainMachine(location, obtainFlags);
++ JcloudsMachineLocation origMachine = obtainMachine(location, obtainFlags);
+ String origHostname = origMachine.getHostname();
+ NodeMetadata origNode = origMachine.getNode();
- Template origTemplate = origMachine.getTemplate();
- assertSshable(origMachine);
++ if (origMachine instanceof JcloudsSshMachineLocation) {
++ Template origTemplate = origMachine.getTemplate(); // WinRM machines don't bother with template!
++ }
++ assertConnectable(origMachine);
+
+ rebind();
+
- // Check the machine is as before
- JcloudsSshMachineLocation newMachine = (JcloudsSshMachineLocation) newManagementContext.getLocationManager().getLocation(origMachine.getId());
++ // Check the machine is as before; but won't have persisted node+template.
++ // We'll be able to re-create the node object by querying the cloud-provider again though.
++ JcloudsMachineLocation newMachine = (JcloudsMachineLocation) newManagementContext.getLocationManager().getLocation(origMachine.getId());
+ JcloudsLocation newLocation = newMachine.getParent();
+ String newHostname = newMachine.getHostname();
- NodeMetadata newNode = newMachine.getNode();
- Template newTemplate = newMachine.getTemplate();
- assertSshable(newMachine);
++ if (newMachine instanceof JcloudsSshMachineLocation) {
++ assertFalse(((JcloudsSshMachineLocation)newMachine).getOptionalTemplate().isPresent());
++ assertNull(((JcloudsSshMachineLocation)newMachine).peekNode());
++ } else if (newMachine instanceof JcloudsWinRmMachineLocation) {
++ assertNull(((JcloudsWinRmMachineLocation)newMachine).peekNode());
++ } else {
++ fail("Unexpected new machine type: machine="+newMachine+"; type="+(newMachine == null ? null : newMachine.getClass()));
++ }
++ NodeMetadata newNode = newMachine.getOptionalNode().get();
++ assertConnectable(newMachine);
+
+ assertEquals(newHostname, origHostname);
+ assertEquals(origNode.getId(), newNode.getId());
+ }
+
+ protected void assertSshable(Map<?,?> machineConfig) {
+ SshMachineLocation machineWithThatConfig = mgmt().getLocationManager().createLocation(LocationSpec.create(SshMachineLocation.class)
+ .configure(machineConfig));
+ try {
+ assertSshable(machineWithThatConfig);
+ } finally {
+ Streams.closeQuietly(machineWithThatConfig);
+ }
+ }
+
+ protected void assertNotSshable(Map<?,?> machineConfig) {
+ try {
+ assertSshable(machineConfig);
+ Assert.fail("ssh should not have succeeded "+machineConfig);
+ } catch (Exception e) {
+ // expected
+ LOG.debug("Exception as expected when testing sshable "+machineConfig);
+ }
+ }
+
++ protected void assertConnectable(MachineLocation machine) {
++ if (machine instanceof SshMachineLocation) {
++ assertSshable((SshMachineLocation)machine);
++ } else if (machine instanceof WinRmMachineLocation) {
++ assertWinrmable((WinRmMachineLocation)machine);
++ } else {
++ throw new UnsupportedOperationException("Unsupported machine type: machine="+machine+"; type="+(machine == null ? null : machine.getClass()));
++ }
++ }
++
+ protected void assertSshable(SshMachineLocation machine) {
+ int result = machine.execScript("simplecommand", ImmutableList.of("true"));
+ assertEquals(result, 0);
+ }
+
++ protected void assertWinrmable(WinRmMachineLocation machine) {
++ WinRmToolResponse result = machine.executePsScript("echo mycmd");
++ assertEquals(result.getStatusCode(), 0, "status="+result.getStatusCode()+"; stdout="+result.getStdOut()+"; stderr="+result.getStdErr());
++ }
++
+ // Use this utility method to ensure machines are released on tearDown
- protected JcloudsSshMachineLocation obtainMachine(MachineProvisioningLocation<?> location, Map<?, ?> conf) throws Exception {
- JcloudsSshMachineLocation result = (JcloudsSshMachineLocation)location.obtain(conf);
++ protected JcloudsMachineLocation obtainMachine(MachineProvisioningLocation<?> location, Map<?, ?> conf) throws Exception {
++ JcloudsMachineLocation result = (JcloudsMachineLocation)location.obtain(conf);
+ machines.add(checkNotNull(result, "result"));
+ return result;
+ }
+
- protected void releaseMachine(JcloudsSshMachineLocation machine) {
++ protected void releaseMachine(JcloudsMachineLocation machine) {
+ if (!Locations.isManaged(machine)) return;
+ machines.remove(machine);
+ machine.getParent().release(machine);
+ }
+
- protected List<Exception> releaseMachineSafely(Iterable<? extends JcloudsSshMachineLocation> machines) {
++ protected List<Exception> releaseMachineSafely(Iterable<? extends JcloudsMachineLocation> machines) {
+ List<Exception> exceptions = Lists.newArrayList();
- List<JcloudsSshMachineLocation> machinesCopy = ImmutableList.copyOf(machines);
++ List<JcloudsMachineLocation> machinesCopy = ImmutableList.copyOf(machines);
+
- for (JcloudsSshMachineLocation machine : machinesCopy) {
++ for (JcloudsMachineLocation machine : machinesCopy) {
+ try {
+ releaseMachine(machine);
+ } catch (Exception e) {
+ LOG.warn("Error releasing machine "+machine+"; continuing...", e);
+ exceptions.add(e);
+ }
+ }
+ return exceptions;
+ }
+ }