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: + // [email protected]=38.909202 + // [email protected]=-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; + } + }
