Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master 681b8fe90 -> e668d15e2


Windows support improvements in JcloudsLocation

- improved support for windows credentials and
  for sshHostAndPortOverride


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/ae3790ee
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/ae3790ee
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/ae3790ee

Branch: refs/heads/master
Commit: ae3790eef6a16625038cf15be481e715e32a5151
Parents: f7b9047
Author: Aled Sage <[email protected]>
Authored: Fri Jun 5 10:38:43 2015 +0100
Committer: Aled Sage <[email protected]>
Committed: Wed Jun 10 21:48:28 2015 +0100

----------------------------------------------------------------------
 .../location/basic/WinRmMachineLocation.java    |   9 +
 .../location/jclouds/JcloudsLocation.java       | 217 ++++++++++++-------
 2 files changed, 150 insertions(+), 76 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ae3790ee/core/src/main/java/brooklyn/location/basic/WinRmMachineLocation.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/brooklyn/location/basic/WinRmMachineLocation.java 
b/core/src/main/java/brooklyn/location/basic/WinRmMachineLocation.java
index ee32568..cb59102 100644
--- a/core/src/main/java/brooklyn/location/basic/WinRmMachineLocation.java
+++ b/core/src/main/java/brooklyn/location/basic/WinRmMachineLocation.java
@@ -228,6 +228,15 @@ public class WinRmMachineLocation extends AbstractLocation 
implements MachineLoc
         }
     }
 
+    @Override
+    public void init() {
+        super.init();
+        
+        getRequiredConfig(ADDRESS);
+        getRequiredConfig(USER);
+        getRequiredConfig(PASSWORD);
+    }
+
     public String getUser() {
         return config().get(USER);
     }

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/ae3790ee/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
index e0e7990..c665ba7 100644
--- 
a/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
+++ 
b/locations/jclouds/src/main/java/brooklyn/location/jclouds/JcloudsLocation.java
@@ -64,6 +64,7 @@ import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.NodeMetadata.Status;
 import org.jclouds.compute.domain.NodeMetadataBuilder;
+import org.jclouds.compute.domain.OperatingSystem;
 import org.jclouds.compute.domain.OsFamily;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.domain.TemplateBuilder;
@@ -321,6 +322,36 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
         return (String) config.getWithDeprecation(USER, JCLOUDS_KEY_USERNAME);
     }
 
+    public boolean isWindows(Template template, ConfigBag config) {
+        return isWindows(template.getImage(), config);
+    }
+    
+    /**
+     * Whether VMs provisioned from this image will be Windows. Assume windows 
if the image
+     * explicitly says so, or if the config explicitly says {@link 
JcloudsLocationConfig#OS_FAMILY} 
+     * is Windows.
+     * 
+     * We believe the config (e.g. from brooklyn.properties) because for some 
clouds there is 
+     * insufficient meta-data so the Image might not tell us. Thus a user can 
work around it
+     * by explicitly supplying configuration. 
+     */
+    public boolean isWindows(Image image, ConfigBag config) {
+        OsFamily confFamily = config.get(OS_FAMILY);
+        OperatingSystem os = (image != null) ? image.getOperatingSystem() : 
null;
+        return (os != null) ? (OsFamily.WINDOWS == os.getFamily()) : 
(OsFamily.WINDOWS == confFamily);
+    }
+
+    /**
+     * Whether the given VM is Windows.
+     * 
+     * @see {@link #isWindows(Image, ConfigBag)}
+     */
+    public boolean isWindows(NodeMetadata node, ConfigBag config) {
+        OsFamily confFamily = config.get(OS_FAMILY);
+        OperatingSystem os = (node != null) ? node.getOperatingSystem() : null;
+        return (os != null) ? (OsFamily.WINDOWS == os.getFamily()) : 
(OsFamily.WINDOWS == confFamily);
+    }
+
     protected Semaphore getMachineCreationSemaphore() {
         return checkNotNull(getConfig(MACHINE_CREATION_SEMAPHORE), 
MACHINE_CREATION_SEMAPHORE.getName());
     }
@@ -564,6 +595,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
 
         setCreationString(setup);
         boolean waitForSshable = 
!"false".equalsIgnoreCase(setup.get(WAIT_FOR_SSHABLE));
+        boolean waitForWinRmable = 
!"false".equalsIgnoreCase(setup.get(WAIT_FOR_WINRM_AVAILABLE));
         boolean usePortForwarding = setup.get(USE_PORT_FORWARDING);
         boolean skipJcloudsSshing = 
Boolean.FALSE.equals(setup.get(USE_JCLOUDS_SSH_INIT)) || usePortForwarding;
         JcloudsPortForwarderExtension portForwarder = 
setup.get(PORT_FORWARDER);
@@ -598,8 +630,15 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
             try {
                 // Setup the template
                 template = buildTemplate(computeService, setup);
-                if (waitForSshable && !skipJcloudsSshing) {
-                    userCredentials = initTemplateForCreateUser(template, 
setup);
+                boolean expectWindows = isWindows(template, setup);
+                if (!skipJcloudsSshing) {
+                    if (expectWindows) {
+                        // TODO Was this too early to look at 
template.getImage? e.g. customizeTemplate could subsequently modify it.
+                        LOG.warn("Ignoring invalid configuration for Windows 
provisioning of "+template.getImage()+": "+USE_JCLOUDS_SSH_INIT.getName()+" 
should be false");
+                        skipJcloudsSshing = true;
+                    } else if (waitForSshable) {
+                        userCredentials = initTemplateForCreateUser(template, 
setup);
+                    }
                 }
 
                 templateTimestamp = Duration.of(provisioningStopwatch);
@@ -642,24 +681,15 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
             if (node == null)
                 throw new IllegalStateException("No nodes returned by jclouds 
create-nodes in " + setup.getDescription());
 
-            boolean windows = 
node.getOperatingSystem().getFamily().equals(OsFamily.WINDOWS);
+            boolean windows = isWindows(node, setup);
 
-            if (windows) {
-                // FIXME Allow for user-specified credentials:
-                //  - want to support setting up a given user on the VM (or 
resetting the Administrator password)
-                //  - and support vCloudDirector use-case where template 
contains username/password, so don't just use
-                //    what is returned by jclouds.
-                setup.put(USER, node.getCredentials().getUser());
-                setup.put(PASSWORD, 
node.getCredentials().getOptionalPassword().orNull());
-            }
-
-            // TODO How do we influence the node.getLoginPort, so it is set 
correctly for Windows?
+            // FIXME How do we influence the node.getLoginPort, so it is set 
correctly for Windows?
             // Setup port-forwarding, if required
             Optional<HostAndPort> sshHostAndPortOverride;
             if (usePortForwarding) {
                 sshHostAndPortOverride = 
Optional.of(portForwarder.openPortForwarding(
                         node,
-                        node.getLoginPort(),
+                        (windows ? 5985 : node.getLoginPort()),
                         Optional.<Integer>absent(),
                         Protocol.TCP,
                         Cidr.UNIVERSAL));
@@ -667,14 +697,15 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
                 sshHostAndPortOverride = Optional.absent();
             }
 
-            if (waitForSshable) {
-                if (windows) {
-                    // TODO Should we generalise `waitForSshable` to a 
`waitForReachable`?
-                    // TODO Does jclouds support any windows user setup?
-                    waitForWinRmAvailable(node, node.getCredentials(), setup);
-                } else if (skipJcloudsSshing) {
-                    // once that host:port is definitely reachable, we can 
create the user
-                    waitForReachable(computeService, node, 
sshHostAndPortOverride, node.getCredentials(), setup);
+            if (skipJcloudsSshing) {
+                boolean waitForConnectable = (windows) ? waitForWinRmable : 
waitForSshable;
+                if (waitForConnectable) {
+                    if (windows) {
+                        // TODO Does jclouds support any windows user setup?
+                        waitForWinRmAvailable(computeService, node, 
sshHostAndPortOverride, node.getCredentials(), setup);
+                    } else {
+                        waitForSshable(computeService, node, 
sshHostAndPortOverride, node.getCredentials(), setup);
+                    }
                     userCredentials = createUser(computeService, node, 
sshHostAndPortOverride, setup);
                 }
             }
@@ -704,7 +735,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
 
             // Wait for the VM to be reachable over SSH
             if (waitForSshable && !windows) {
-                waitForReachable(computeService, node, sshHostAndPortOverride, 
userCredentials, setup);
+                waitForSshable(computeService, node, sshHostAndPortOverride, 
userCredentials, setup);
             } else {
                 LOG.debug("Skipping ssh check for {} ({}) due to config 
waitForSshable=false", node, setup.getDescription());
             }
@@ -1313,8 +1344,8 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
         }
         TemplateOptions options = template.getOptions();
 
-        OsFamily osFamily = (image.getOperatingSystem() != null) ? 
image.getOperatingSystem().getFamily() : null;
-        if (OsFamily.WINDOWS == osFamily) {
+        boolean windows = isWindows(template, config);
+        if (windows) {
             if 
(!(config.containsKey(JcloudsLocationConfig.USER_METADATA_STRING) || 
config.containsKey(JcloudsLocationConfig.USER_METADATA_MAP))) {
                 config.put(JcloudsLocationConfig.USER_METADATA_STRING, 
WinRmMachineLocation.getDefaultUserMetadataString());
             }
@@ -1397,54 +1428,61 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
                         : org.jclouds.scriptbuilder.domain.OsFamily.UNIX;
             }
 
-            List<String> commands = Lists.newArrayList();
-            for (Statement statement : userCreation.statements) {
-                InitAdminAccess initAdminAccess = new InitAdminAccess(new 
AdminAccessConfiguration.Default());
-                initAdminAccess.visit(statement);
-                commands.add(statement.render(scriptOsFamily));
-            }
-
-            LoginCredentials initialCredentials = node.getCredentials();
-            Optional<String> initialPassword = 
initialCredentials.getOptionalPassword();
-            Optional<String> initialPrivateKey = 
initialCredentials.getOptionalPrivateKey();
-            String initialUser = initialCredentials.getUser();
-            String address = hostAndPortOverride.isPresent() ? 
hostAndPortOverride.get().getHostText() : 
JcloudsUtil.getFirstReachableAddress(computeService.getContext(), node);
-            int port = hostAndPortOverride.isPresent() ? 
hostAndPortOverride.get().getPort() : node.getLoginPort();
-
-            Map<String,Object> sshProps = 
Maps.newLinkedHashMap(config.getAllConfig());
-            sshProps.put("user", initialUser);
-            sshProps.put("address", address);
-            sshProps.put("port", port);
-            if (initialPassword.isPresent()) sshProps.put("password", 
initialPassword.get());
-            if (initialPrivateKey.isPresent()) sshProps.put("privateKeyData", 
initialPrivateKey.get());
-
-            // TODO Retrying lots of times as workaround for vcloud-director. 
There the guest customizations
-            // can cause the VM to reboot shortly after it was ssh'able.
-            Map<String,Object> execProps = Maps.newLinkedHashMap();
-            execProps.put(ShellTool.PROP_RUN_AS_ROOT.getName(), true);
-            execProps.put(SshTool.PROP_SSH_TRIES.getName(), 50);
-            execProps.put(SshTool.PROP_SSH_TRIES_TIMEOUT.getName(), 
10*60*1000);
-
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("VM {}: executing user creation/setup via {}@{}:{}; 
commands: {}", new Object[] {
-                        config.getDescription(), initialUser, address, port, 
commands});
-            }
+            boolean windows = isWindows(node, config);
 
-            SshMachineLocation sshLoc = null;
-            try {
-                if (isManaged()) {
-                    sshLoc = 
getManagementContext().getLocationManager().createLocation(sshProps, 
SshMachineLocation.class);
-                } else {
-                    sshLoc = new SshMachineLocation(sshProps);
+            if (windows) {
+                LOG.warn("Unable to execute statements on WinRM in 
JcloudsLocation; skipping for "+node+": "+userCreation.statements);
+                
+            } else {
+                List<String> commands = Lists.newArrayList();
+                for (Statement statement : userCreation.statements) {
+                    InitAdminAccess initAdminAccess = new InitAdminAccess(new 
AdminAccessConfiguration.Default());
+                    initAdminAccess.visit(statement);
+                    commands.add(statement.render(scriptOsFamily));
                 }
 
-                int exitcode = sshLoc.execScript(execProps, "create-user", 
commands);
-                if (exitcode != 0) {
-                    LOG.warn("exit code {} when creating user for {}; usage 
may subsequently fail", exitcode, node);
+                LoginCredentials initialCredentials = node.getCredentials();
+                Optional<String> initialPassword = 
initialCredentials.getOptionalPassword();
+                Optional<String> initialPrivateKey = 
initialCredentials.getOptionalPrivateKey();
+                String initialUser = initialCredentials.getUser();
+                String address = hostAndPortOverride.isPresent() ? 
hostAndPortOverride.get().getHostText() : 
JcloudsUtil.getFirstReachableAddress(computeService.getContext(), node);
+                int port = hostAndPortOverride.isPresent() ? 
hostAndPortOverride.get().getPort() : node.getLoginPort();
+                
+                Map<String,Object> sshProps = 
Maps.newLinkedHashMap(config.getAllConfig());
+                sshProps.put("user", initialUser);
+                sshProps.put("address", address);
+                sshProps.put("port", port);
+                if (initialPassword.isPresent()) sshProps.put("password", 
initialPassword.get());
+                if (initialPrivateKey.isPresent()) 
sshProps.put("privateKeyData", initialPrivateKey.get());
+    
+                // TODO Retrying lots of times as workaround for 
vcloud-director. There the guest customizations
+                // can cause the VM to reboot shortly after it was ssh'able.
+                Map<String,Object> execProps = Maps.newLinkedHashMap();
+                execProps.put(ShellTool.PROP_RUN_AS_ROOT.getName(), true);
+                execProps.put(SshTool.PROP_SSH_TRIES.getName(), 50);
+                execProps.put(SshTool.PROP_SSH_TRIES_TIMEOUT.getName(), 
10*60*1000);
+    
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("VM {}: executing user creation/setup via 
{}@{}:{}; commands: {}", new Object[] {
+                            config.getDescription(), initialUser, address, 
port, commands});
+                }
+    
+                SshMachineLocation sshLoc = null;
+                try {
+                    if (isManaged()) {
+                        sshLoc = 
getManagementContext().getLocationManager().createLocation(sshProps, 
SshMachineLocation.class);
+                    } else {
+                        sshLoc = new SshMachineLocation(sshProps);
+                    }
+    
+                    int exitcode = sshLoc.execScript(execProps, "create-user", 
commands);
+                    if (exitcode != 0) {
+                        LOG.warn("exit code {} when creating user for {}; 
usage may subsequently fail", exitcode, node);
+                    }
+                } finally {
+                    
getManagementContext().getLocationManager().unmanage(sshLoc);
+                    Streams.closeQuietly(sshLoc);
                 }
-            } finally {
-                getManagementContext().getLocationManager().unmanage(sshLoc);
-                Streams.closeQuietly(sshLoc);
             }
         }
 
@@ -1527,7 +1565,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
     protected UserCreation createUserStatements(@Nullable Image image, 
ConfigBag config) {
         //NB: private key is not installed remotely, just used to get/validate 
the public key
 
-        LoginCredentials createdUserCreds = null;
+        boolean windows = isWindows(image, config);
         String user = getUser(config);
         String explicitLoginUser = config.get(LOGIN_USER);
         String loginUser = groovyTruth(explicitLoginUser) ? explicitLoginUser 
: (image != null && image.getDefaultCredentials() != null) ? 
image.getDefaultCredentials().identity : null;
@@ -1537,6 +1575,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
         credential.checkNoErrors().logAnyWarnings();
         String passwordToSet = Strings.isNonBlank(credential.getPassword()) ? 
credential.getPassword() : Identifiers.makeRandomId(12);
         List<Statement> statements = Lists.newArrayList();
+        LoginCredentials createdUserCreds = null;
 
         if (dontCreateUser) {
             // dontCreateUser:
@@ -1559,6 +1598,22 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
                 }
             }
 
+        } else if (windows) {
+            // TODO Generate statements to create the user.
+            // createdUserCreds returned from this method will be null;
+            // we will use the creds returned by jclouds on the node
+            LOG.warn("Not creating or configuring user on Windows VM, despite 
"+DONT_CREATE_USER.getName()+" set to false");
+
+            // TODO extractVmCredentials() will use user:publicKeyData 
defaults, if we don't override this.
+            // For linux, how would we configure Brooklyn to use the 
node.getCredentials() - i.e. the version
+            // that the cloud automatically generated?
+            if (config.get(USER) != null) config.put(USER, "");
+            if (config.get(PASSWORD) != null) config.put(PASSWORD, "");
+            if (config.get(PRIVATE_KEY_DATA) != null) 
config.put(PRIVATE_KEY_DATA, "");
+            if (config.get(PRIVATE_KEY_FILE) != null) 
config.put(PRIVATE_KEY_FILE, "");
+            if (config.get(PUBLIC_KEY_DATA) != null) 
config.put(PUBLIC_KEY_DATA, "");
+            if (config.get(PUBLIC_KEY_FILE) != null) 
config.put(PUBLIC_KEY_FILE, "");
+
         } else if (Strings.isBlank(user) || user.equals(loginUser) || 
user.equals(ROOT_USERNAME)) {
             // For subsequent ssh'ing, we'll be using the loginUser
             if (Strings.isBlank(user)) {
@@ -2186,7 +2241,7 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
         return null;
     }
 
-    private void waitForWinRmAvailable(final NodeMetadata node, final 
LoginCredentials expectedCredentials, ConfigBag setup) {
+    protected void waitForWinRmAvailable(final ComputeService computeService, 
final NodeMetadata node, Optional<HostAndPort> hostAndPortOverride, final 
LoginCredentials expectedCredentials, ConfigBag setup) {
         String waitForWinrmAvailable = setup.get(WAIT_FOR_WINRM_AVAILABLE);
         checkArgument(!"false".equalsIgnoreCase(waitForWinrmAvailable), 
"waitForWinRmAvailable called despite waitForWinRmAvailable=%s", 
waitForWinrmAvailable);
         Duration timeout = null;
@@ -2195,27 +2250,37 @@ public class JcloudsLocation extends 
AbstractCloudMachineProvisioningLocation im
         } catch (Exception e) {
             // TODO will this just be a NumberFormatException? If so, catch 
that specificially
             // normal if 'true'; just fall back to default
+            Exceptions.propagateIfFatal(e);
         }
         if (timeout == null) {
             timeout = 
Duration.parse(WAIT_FOR_WINRM_AVAILABLE.getDefaultValue());
         }
         
-        // TODO Use getFirstReachableAddress, when have getLoginPort set 
correctly
         String user = expectedCredentials.getUser();
         String password = expectedCredentials.getOptionalPassword().orNull();
-        String vmIp = node.getPublicAddresses().iterator().next();
-        final Session session = WinRMFactory.INSTANCE.createSession(vmIp, 
user, password);
+        int vmPort = hostAndPortOverride.isPresent() 
+                ? hostAndPortOverride.get().getPortOrDefault(5985) 
+                : 5985; // WinRM port
+        String vmIp = hostAndPortOverride.isPresent() 
+                ? hostAndPortOverride.get().getHostText() 
+                : JcloudsUtil.getFirstReachableAddress(
+                        computeService.getContext(), 
+                        
NodeMetadataBuilder.fromNodeMetadata(node).loginPort(vmPort).build());
+
+        final Session session = 
WinRMFactory.INSTANCE.createSession(vmIp+":"+vmPort, user, password);
+
+        if (vmIp==null) LOG.warn("Unable to extract IP for "+node+" 
("+setup.getDescription()+"): subsequent connection attempt will likely fail");
 
         Callable<Boolean> checker = new Callable<Boolean>() {
             public Boolean call() {
                 return session.run_cmd("hostname").getStatusCode() == 0;
             }};
-        String connectionDetails = user+"@"+vmIp;
+        String connectionDetails = user + "@" + vmIp + ":" + vmPort;
 
         waitForReachable(checker, connectionDetails, expectedCredentials, 
setup, timeout);
     }
 
-    protected void waitForReachable(final ComputeService computeService, final 
NodeMetadata node, Optional<HostAndPort> hostAndPortOverride, final 
LoginCredentials expectedCredentials, ConfigBag setup) {
+    protected void waitForSshable(final ComputeService computeService, final 
NodeMetadata node, Optional<HostAndPort> hostAndPortOverride, final 
LoginCredentials expectedCredentials, ConfigBag setup) {
         String waitForSshable = setup.get(WAIT_FOR_SSHABLE);
         checkArgument(!"false".equalsIgnoreCase(waitForSshable), 
"waitForReachable called despite waitForSshable=%s", waitForSshable);
 

Reply via email to