Adds WinRmTool interface
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/42c11132 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/42c11132 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/42c11132 Branch: refs/heads/master Commit: 42c1113258bb66ba98c7fcde6c304dad4faed38c Parents: ca89ed4 Author: Aled Sage <[email protected]> Authored: Wed Nov 18 15:33:07 2015 +0000 Committer: Aled Sage <[email protected]> Committed: Wed Nov 18 22:05:45 2015 +0000 ---------------------------------------------------------------------- .../core/entity/BrooklynConfigKeys.java | 9 + .../AbstractSoftwareProcessWinRmDriver.java | 31 ++- .../location/WinRmMachineLocationLiveTest.java | 5 +- .../windows/WindowsPerformanceCounterFeed.java | 2 +- .../location/winrm/WinRmMachineLocation.java | 265 +++++++++---------- .../util/core/internal/winrm/WinRmTool.java | 74 ++++++ .../core/internal/winrm/WinRmToolResponse.java | 46 ++++ .../internal/winrm/pywinrm/Winrm4jTool.java | 156 +++++++++++ .../WindowsPerformanceCounterFeedTest.java | 3 +- 9 files changed, 438 insertions(+), 153 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/42c11132/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java index 1185791..974f88c 100644 --- a/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java +++ b/core/src/main/java/org/apache/brooklyn/core/entity/BrooklynConfigKeys.java @@ -177,6 +177,10 @@ public class BrooklynConfigKeys { * and have this prefix pre-prended to the config keys in this class. */ public static final String BROOKLYN_SSH_CONFIG_KEY_PREFIX = "brooklyn.ssh.config."; + /** Public-facing global config keys for Brooklyn are defined in ConfigKeys, + * and have this prefix pre-prended to the config keys in this class. */ + public static final String BROOKLYN_WINRM_CONFIG_KEY_PREFIX = "brooklyn.winrm.config."; + // some checks (this line, and a few Preconditions below) that the remote values aren't null, // because they have some funny circular references static { assert BROOKLYN_SSH_CONFIG_KEY_PREFIX.equals(SshTool.BROOKLYN_CONFIG_KEY_PREFIX) : "static final initializer classload ordering problem"; } @@ -186,6 +190,11 @@ public class BrooklynConfigKeys { "SshTool implementation to use (or null for default)", null); + public static final ConfigKey<String> WINRM_TOOL_CLASS = newStringConfigKey( + BROOKLYN_WINRM_CONFIG_KEY_PREFIX + "winrmToolClass", + "WinRmTool implementation to use (or null for default)", + null); + /** * @deprecated since 0.9.0; use {@link #SSH_TOOL_CLASS} */ http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/42c11132/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessWinRmDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessWinRmDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessWinRmDriver.java index 4b3da76..6a39cc2 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessWinRmDriver.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/AbstractSoftwareProcessWinRmDriver.java @@ -18,9 +18,17 @@ */ package org.apache.brooklyn.entity.software.base; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; -import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; +import static org.apache.brooklyn.util.JavaGroovyEquivalents.elvis; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.sensor.AttributeSensor; @@ -31,6 +39,8 @@ import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.entity.software.base.lifecycle.NativeWindowsScriptRunner; import org.apache.brooklyn.entity.software.base.lifecycle.WinRmExecuteHelper; import org.apache.brooklyn.location.winrm.WinRmMachineLocation; +import org.apache.brooklyn.util.core.internal.winrm.WinRmTool; +import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.exceptions.ReferenceWithError; @@ -42,16 +52,9 @@ import org.python.core.PyException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; - -import static org.apache.brooklyn.util.JavaGroovyEquivalents.elvis; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; public abstract class AbstractSoftwareProcessWinRmDriver extends AbstractSoftwareProcessDriver implements NativeWindowsScriptRunner { private static final Logger LOG = LoggerFactory.getLogger(AbstractSoftwareProcessWinRmDriver.class); @@ -260,7 +263,7 @@ public abstract class AbstractSoftwareProcessWinRmDriver extends AbstractSoftwar } public int executePsScriptNoRetry(List<String> psScript) { - return getLocation().executePsScriptNoRetry(psScript).getStatusCode(); + return getLocation().executePsScript(ImmutableMap.of(WinRmTool.PROP_EXEC_TRIES, 1), psScript).getStatusCode(); } public int executePsScript(List<String> psScript) { http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/42c11132/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java ---------------------------------------------------------------------- diff --git a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java index 4eab220..bfca1d8 100644 --- a/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java +++ b/software/base/src/test/java/org/apache/brooklyn/entity/software/base/test/location/WinRmMachineLocationLiveTest.java @@ -39,6 +39,7 @@ import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport; import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests; import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.location.winrm.WinRmMachineLocation; +import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse; import org.apache.brooklyn.util.text.Identifiers; import org.apache.brooklyn.util.time.Time; import org.slf4j.Logger; @@ -59,8 +60,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; - /** * Tests execution of commands (batch and powershell) on Windows over WinRM, and of * file upload. @@ -72,8 +71,8 @@ import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; * Please update the docs if you encountered new situations, or change the behaviuor * of existing use-cases. */ -public class WinRmMachineLocationLiveTest { +public class WinRmMachineLocationLiveTest { private static final int MAX_EXECUTOR_THREADS = 100; /* http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/42c11132/software/winrm/src/main/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeed.java ---------------------------------------------------------------------- diff --git a/software/winrm/src/main/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeed.java b/software/winrm/src/main/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeed.java index 73a76cb..b6f8684 100644 --- a/software/winrm/src/main/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeed.java +++ b/software/winrm/src/main/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeed.java @@ -20,7 +20,6 @@ package org.apache.brooklyn.feed.windows; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; import java.util.Collection; import java.util.Iterator; @@ -49,6 +48,7 @@ import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.feed.windows.WindowsPerformanceCounterPollConfig; import org.apache.brooklyn.location.winrm.WinRmMachineLocation; import org.apache.brooklyn.util.core.flags.TypeCoercions; +import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.time.Duration; import org.slf4j.Logger; http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/42c11132/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java ---------------------------------------------------------------------- diff --git a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java index 3a7dbd5..7f2e4ed 100644 --- a/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java +++ b/software/winrm/src/main/java/org/apache/brooklyn/location/winrm/WinRmMachineLocation.java @@ -18,15 +18,11 @@ */ package org.apache.brooklyn.location.winrm; -import static com.google.common.base.Preconditions.checkNotNull; - import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.net.InetAddress; -import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -37,65 +33,74 @@ import org.apache.brooklyn.api.location.MachineDetails; import org.apache.brooklyn.api.location.MachineLocation; import org.apache.brooklyn.api.location.OsDetails; import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.config.ConfigKey.HasConfigKey; import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.config.ConfigUtils; +import org.apache.brooklyn.core.config.Sanitizer; +import org.apache.brooklyn.core.entity.BrooklynConfigKeys; import org.apache.brooklyn.core.location.AbstractLocation; import org.apache.brooklyn.core.location.access.PortForwardManager; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.core.internal.ssh.SshTool; +import org.apache.brooklyn.util.core.internal.winrm.WinRmTool; +import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse; +import org.apache.brooklyn.util.core.internal.winrm.pywinrm.Winrm4jTool; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.text.Strings; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Charsets; +import com.google.common.base.Function; import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; 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.Iterables; import com.google.common.net.HostAndPort; import com.google.common.reflect.TypeToken; -import org.apache.brooklyn.util.exceptions.Exceptions; -import org.apache.brooklyn.util.stream.Streams; -import org.apache.brooklyn.util.time.Duration; -import org.apache.brooklyn.util.time.Time; - -import io.cloudsoft.winrm4j.winrm.WinRmTool; -import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; - public class WinRmMachineLocation extends AbstractLocation implements MachineLocation { private static final Logger LOG = LoggerFactory.getLogger(WinRmMachineLocation.class); - // FIXME Respect `port` config when using {@link WinRmTool} - public static final ConfigKey<Integer> WINRM_PORT = ConfigKeys.newIntegerConfigKey( - "port", - "WinRM port to use when connecting to the remote machine", - 5985); - - // TODO merge with {link SshTool#PROP_USER} and {@link SshMachineLocation#user} - public static final ConfigKey<String> USER = ConfigKeys.newStringConfigKey("user", - "Username to use when connecting to the remote machine"); - - // TODO merge with {link SshTool#PROP_PASSWORD} - public static final ConfigKey<String> PASSWORD = ConfigKeys.newStringConfigKey("password", - "Password to use when connecting to the remote machine"); - - public static final ConfigKey<Integer> COPY_FILE_CHUNK_SIZE_BYTES = ConfigKeys.newIntegerConfigKey("windows.copy.file.size.bytes", - "Size of file chunks (in bytes) to be used when copying a file to the remote server", 1024); - - public static final ConfigKey<InetAddress> ADDRESS = ConfigKeys.newConfigKey( + public static final ConfigKey<InetAddress> ADDRESS = ConfigKeys.newConfigKey( InetAddress.class, "address", "Address of the remote machine"); + public static final ConfigKey<Integer> WINRM_PORT = WinRmTool.PROP_PORT; + + // TODO merge with {link SshTool#PROP_USER} and {@link SshMachineLocation#user}? + public static final ConfigKey<String> USER = WinRmTool.PROP_USER; + + // TODO merge with {link SshTool#PROP_PASSWORD}? + public static final ConfigKey<String> PASSWORD = WinRmTool.PROP_PASSWORD; + + // TODO Delete once winrm4j supports this better? + public static final ConfigKey<Integer> COPY_FILE_CHUNK_SIZE_BYTES = WinRmTool.COPY_FILE_CHUNK_SIZE_BYTES; + + // Note that SshTool's implementation class *must* use a different key name. Both may be used + // within a location's configuration to indicate the implementation to use for WinRmTool and + // for SshTool - that will require two different configuration values. + public static final ConfigKey<String> WINRM_TOOL_CLASS = ConfigKeys.newConfigKeyWithPrefixRemoved( + BrooklynConfigKeys.BROOKLYN_WINRM_CONFIG_KEY_PREFIX, + Preconditions.checkNotNull(BrooklynConfigKeys.WINRM_TOOL_CLASS, "static final initializer classload ordering problem")); + + /** + * @deprecated since 0.9.0; config never read; will be removed in future version. + */ + @Deprecated public static final ConfigKey<Integer> EXECUTION_ATTEMPTS = ConfigKeys.newIntegerConfigKey( "windows.exec.attempts", "Number of attempts to execute a remote command", 1); // TODO See SshTool#PROP_SSH_TRIES, where it was called "sshTries"; remove duplication? Merge into one well-named thing? - public static final ConfigKey<Integer> EXEC_TRIES = ConfigKeys.newIntegerConfigKey( - "execTries", - "Max number of times to attempt WinRM operations", - 10); + public static final ConfigKey<Integer> EXEC_TRIES = WinRmTool.PROP_EXEC_TRIES; public static final ConfigKey<Iterable<String>> PRIVATE_ADDRESSES = ConfigKeys.newConfigKey( new TypeToken<Iterable<String>>() {}, @@ -109,6 +114,43 @@ public class WinRmMachineLocation extends AbstractLocation implements MachineLoc "NAT'ed ports, giving the mapping from private TCP port to a public host:port", null); + public static final Set<HasConfigKey<?>> ALL_WINRM_CONFIG_KEYS = + ImmutableSet.<HasConfigKey<?>>builder() + .addAll(ConfigUtils.getStaticKeysOnClass(WinRmMachineLocation.class)) + .addAll(ConfigUtils.getStaticKeysOnClass(WinRmTool.class)) + .build(); + + public static final Set<String> ALL_SSH_CONFIG_KEY_NAMES = + ImmutableSet.copyOf(Iterables.transform(ALL_WINRM_CONFIG_KEYS, new Function<HasConfigKey<?>,String>() { + @Override + public String apply(HasConfigKey<?> input) { + return input.getConfigKey().getName(); + } + })); + + @Override + public void init() { + super.init(); + + // Register any pre-existing port-mappings with the PortForwardManager + Map<Integer, String> tcpPortMappings = getConfig(TCP_PORT_MAPPINGS); + if (tcpPortMappings != null) { + PortForwardManager pfm = (PortForwardManager) getManagementContext().getLocationRegistry().resolve("portForwardManager(scope=global)"); + for (Map.Entry<Integer, String> entry : tcpPortMappings.entrySet()) { + int targetPort = entry.getKey(); + HostAndPort publicEndpoint = HostAndPort.fromString(entry.getValue()); + if (!publicEndpoint.hasPort()) { + throw new IllegalArgumentException("Invalid portMapping ('"+entry.getValue()+"') for port "+targetPort+" in machine "+this); + } + pfm.associate(publicEndpoint.getHostText(), publicEndpoint, this, targetPort); + } + } + } + + public String getUser() { + return config().get(USER); + } + @Override public InetAddress getAddress() { return getConfig(ADDRESS); @@ -154,63 +196,66 @@ public class WinRmMachineLocation extends AbstractLocation implements MachineLoc } public WinRmToolResponse executeScript(List<String> script) { - int execTries = getRequiredConfig(EXEC_TRIES); - Collection<Throwable> exceptions = Lists.newArrayList(); - for (int i = 0; i < execTries; i++) { - try { - return executeScriptNoRetry(script); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - if (i == (execTries+1)) { - LOG.info("Propagating WinRM exception (attempt "+(i+1)+" of "+execTries+")", e); - } else if (i == 0) { - LOG.warn("Ignoring WinRM exception and retrying (attempt "+(i+1)+" of "+execTries+")", e); - } else { - LOG.debug("Ignoring WinRM exception and retrying (attempt "+(i+1)+" of "+execTries+")", e); - } - exceptions.add(e); - } - } - throw Exceptions.propagate("failed to execute shell script", exceptions); + return executeScript(ImmutableMap.of(), script); } - - protected WinRmToolResponse executeScriptNoRetry(List<String> script) { - WinRmTool winRmTool = WinRmTool.connect(getHostAndPort(), getUser(), getPassword()); - WinRmToolResponse response = winRmTool.executeScript(script); - return response; + + public WinRmToolResponse executeScript(Map<?,?> props, List<String> script) { + WinRmTool tool = newWinRmTool(props); + return tool.executeScript(script); } public WinRmToolResponse executePsScript(String psScript) { - return executePsScript(ImmutableList.of(psScript)); + return executePsScript(ImmutableMap.of(), ImmutableList.of(psScript)); } public WinRmToolResponse executePsScript(List<String> psScript) { - int execTries = getRequiredConfig(EXEC_TRIES); - Collection<Throwable> exceptions = Lists.newArrayList(); - for (int i = 0; i < execTries; i++) { - try { - return executePsScriptNoRetry(psScript); - } catch (Exception e) { - Exceptions.propagateIfFatal(e); - if (i == (execTries+1)) { - LOG.info("Propagating WinRM exception (attempt "+(i+1)+" of "+execTries+")", e); - } else if (i == 0) { - LOG.warn("Ignoring WinRM exception and retrying after 5 seconds (attempt "+(i+1)+" of "+execTries+")", e); - Time.sleep(Duration.FIVE_SECONDS); + return executePsScript(ImmutableMap.of(), psScript); + } + + public WinRmToolResponse executePsScript(Map<?,?> props, List<String> psScript) { + WinRmTool tool = newWinRmTool(props); + return tool.executePs(psScript); + } + + protected WinRmTool newWinRmTool(Map<?,?> props) { + // TODO See comments/TODOs in SshMachineLocation.connectSsh() + try { + ConfigBag args = new ConfigBag(); + + for (Map.Entry<String,Object> entry: config().getBag().getAllConfig().entrySet()) { + String key = entry.getKey(); + if (key.startsWith(WinRmTool.BROOKLYN_CONFIG_KEY_PREFIX)) { + key = Strings.removeFromStart(key, WinRmTool.BROOKLYN_CONFIG_KEY_PREFIX); + args.putStringKey(key, entry.getValue()); + } + } + + for (Map.Entry<String,Object> entry: config().getBag().getAllConfig().entrySet()) { + String key = entry.getKey(); + if (ALL_SSH_CONFIG_KEY_NAMES.contains(key)) { } else { - LOG.debug("Ignoring WinRM exception and retrying after 5 seconds (attempt "+(i+1)+" of "+execTries+")", e); - Time.sleep(Duration.FIVE_SECONDS); + // this key is not applicable here; ignore it + continue; } - exceptions.add(e); + args.putStringKey(key, entry.getValue()); } - } - throw Exceptions.propagate("failed to execute powershell script", exceptions); - } - public WinRmToolResponse executePsScriptNoRetry(List<String> psScript) { - WinRmTool winRmTool = WinRmTool.connect(getHostAndPort(), getUser(), getPassword()); - WinRmToolResponse response = winRmTool.executePs(psScript); - return response; + args.putAll(props); + args.configure(SshTool.PROP_HOST, getAddress().getHostAddress()); + + if (LOG.isTraceEnabled()) LOG.trace("creating WinRM session for "+Sanitizer.sanitize(args)); + + // look up tool class + String toolClass = args.get(WINRM_TOOL_CLASS); + if (toolClass == null) toolClass = Winrm4jTool.class.getName(); + WinRmTool tool = (WinRmTool) Class.forName(toolClass).getConstructor(Map.class).newInstance(args.getAllConfig()); + + if (LOG.isTraceEnabled()) LOG.trace("using ssh-tool {} (of type {}); props ", tool, toolClass); + + return tool; + } catch (Exception e) { + throw Exceptions.propagate(e); + } } public int copyTo(File source, String destination) { @@ -228,61 +273,15 @@ public class WinRmMachineLocation extends AbstractLocation implements MachineLoc } public int copyTo(InputStream source, String destination) { - executePsScript(ImmutableList.of("rm -ErrorAction SilentlyContinue " + destination)); - try { - int chunkSize = getConfig(COPY_FILE_CHUNK_SIZE_BYTES); - byte[] inputData = new byte[chunkSize]; - int bytesRead; - int expectedFileSize = 0; - while ((bytesRead = source.read(inputData)) > 0) { - byte[] chunk; - if (bytesRead == chunkSize) { - chunk = inputData; - } else { - chunk = Arrays.copyOf(inputData, bytesRead); - } - executePsScript(ImmutableList.of("If ((!(Test-Path " + destination + ")) -or ((Get-Item '" + destination + "').length -eq " + - expectedFileSize + ")) {Add-Content -Encoding Byte -path " + destination + - " -value ([System.Convert]::FromBase64String(\"" + new String(Base64.encodeBase64(chunk)) + "\"))}")); - expectedFileSize += bytesRead; - } - - return 0; - } catch (java.io.IOException e) { - throw Exceptions.propagate(e); - } - } - - @Override - public void init() { - super.init(); - - // Register any pre-existing port-mappings with the PortForwardManager - Map<Integer, String> tcpPortMappings = getConfig(TCP_PORT_MAPPINGS); - if (tcpPortMappings != null) { - PortForwardManager pfm = (PortForwardManager) getManagementContext().getLocationRegistry().resolve("portForwardManager(scope=global)"); - for (Map.Entry<Integer, String> entry : tcpPortMappings.entrySet()) { - int targetPort = entry.getKey(); - HostAndPort publicEndpoint = HostAndPort.fromString(entry.getValue()); - if (!publicEndpoint.hasPort()) { - throw new IllegalArgumentException("Invalid portMapping ('"+entry.getValue()+"') for port "+targetPort+" in machine "+this); - } - pfm.associate(publicEndpoint.getHostText(), publicEndpoint, this, targetPort); - } - } + return copyTo(ImmutableMap.of(), source, destination); } - public String getUser() { - return config().get(USER); - } - - private String getPassword() { - return config().get(PASSWORD); + + public int copyTo(Map<?,?> props, InputStream source, String destination) { + WinRmTool tool = newWinRmTool(props); + WinRmToolResponse response = tool.copyToServer(source, destination); + return response.getStatusCode(); } - private <T> T getRequiredConfig(ConfigKey<T> key) { - return checkNotNull(getConfig(key), "key %s must be set", key); - } - public static String getDefaultUserMetadataString() { // Using an encoded command obviates the need to escape String unencodePowershell = Joiner.on("\r\n").join(ImmutableList.of( http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/42c11132/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmTool.java ---------------------------------------------------------------------- diff --git a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmTool.java b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmTool.java new file mode 100644 index 0000000..6d2fb06 --- /dev/null +++ b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmTool.java @@ -0,0 +1,74 @@ +/* + * 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.util.core.internal.winrm; + +import static org.apache.brooklyn.core.config.ConfigKeys.newConfigKey; +import static org.apache.brooklyn.core.config.ConfigKeys.newIntegerConfigKey; +import static org.apache.brooklyn.core.config.ConfigKeys.newStringConfigKey; + +import java.io.InputStream; +import java.util.List; + +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.BrooklynConfigKeys; +import org.apache.brooklyn.util.time.Duration; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +@Beta +public interface WinRmTool { + + /** Public-facing global config keys for Brooklyn are defined in ConfigKeys, + * and have this prefix prepended to the config keys in this class. + * These keys are detected from entity/global config and automatically applied to ssh executions. */ + String BROOKLYN_CONFIG_KEY_PREFIX = Preconditions.checkNotNull(BrooklynConfigKeys.BROOKLYN_WINRM_CONFIG_KEY_PREFIX, + "static final initializer classload ordering problem"); + + ConfigKey<String> PROP_HOST = newStringConfigKey("host", "Host to connect to (required)", null); + ConfigKey<Integer> PROP_PORT = newIntegerConfigKey("port", "WinRM port to use when connecting to the remote machine", 5985); + ConfigKey<String> PROP_USER = newStringConfigKey("user", "User to connect as", null); + ConfigKey<String> PROP_PASSWORD = newStringConfigKey("password", "Password to use to connect", null); + + // TODO See SshTool#PROP_SSH_TRIES, where it was called "sshTries"; remove duplication? Merge into one well-named thing? + ConfigKey<Integer> PROP_EXEC_TRIES = ConfigKeys.newIntegerConfigKey( + "execTries", + "Max number of times to attempt WinRM operations", + 10); + + ConfigKey<Duration> PROP_EXEC_RETRY_DELAY = newConfigKey( + Duration.class, + "execRetryDelay", + "Max time between retries (backing off exponentially to this delay)", + Duration.TEN_SECONDS); + + // May be ignored by implementations that more efficiently copy the file. + @Beta + ConfigKey<Integer> COPY_FILE_CHUNK_SIZE_BYTES = ConfigKeys.newIntegerConfigKey( + "windows.copy.file.size.bytes", + "Size of file chunks (in bytes) to be used when copying a file to the remote server", + 1024); + + WinRmToolResponse executeScript(List<String> commands); + + WinRmToolResponse executePs(List<String> commands); + + WinRmToolResponse copyToServer(InputStream source, String destination); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/42c11132/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmToolResponse.java ---------------------------------------------------------------------- diff --git a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmToolResponse.java b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmToolResponse.java new file mode 100644 index 0000000..a3ef888 --- /dev/null +++ b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/WinRmToolResponse.java @@ -0,0 +1,46 @@ +/* + * 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.util.core.internal.winrm; + +import com.google.common.annotations.Beta; + +@Beta +public class WinRmToolResponse { + private final String stdout; + private final String stderr; + private final int statusCode; + + public WinRmToolResponse(String stdout, String stderr, int statusCode) { + this.stdout = stdout; + this.stderr = stderr; + this.statusCode = statusCode; + } + + public String getStdOut() { + return stdout; + } + + public String getStdErr() { + return stderr; + } + + public int getStatusCode() { + return statusCode; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/42c11132/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/pywinrm/Winrm4jTool.java ---------------------------------------------------------------------- diff --git a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/pywinrm/Winrm4jTool.java b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/pywinrm/Winrm4jTool.java new file mode 100644 index 0000000..9a4dfb9 --- /dev/null +++ b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/pywinrm/Winrm4jTool.java @@ -0,0 +1,156 @@ +/* + * 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.util.core.internal.winrm.pywinrm; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.Sanitizer; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; +import org.apache.commons.codec.binary.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; + +@Beta +public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm.WinRmTool { + + private static final Logger LOG = LoggerFactory.getLogger(Winrm4jTool.class); + + private final ConfigBag bag; + private final String host; + private final Integer port; + private final String user; + private final String password; + private final int execTries; + private final Duration execRetryDelay; + + public Winrm4jTool(Map<String,?> config) { + this(ConfigBag.newInstance(config)); + } + + public Winrm4jTool(ConfigBag config) { + this.bag = checkNotNull(config, "config bag"); + host = getRequiredConfig(config, PROP_HOST); + port = getRequiredConfig(config, PROP_PORT); + user = getRequiredConfig(config, PROP_USER); + password = getRequiredConfig(config, PROP_PASSWORD); + execTries = getRequiredConfig(config, PROP_EXEC_TRIES); + execRetryDelay = getRequiredConfig(config, PROP_EXEC_RETRY_DELAY); + } + + @Override + public org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse executeScript(final List<String> commands) { + return exec(new Callable<io.cloudsoft.winrm4j.winrm.WinRmToolResponse>() { + @Override public WinRmToolResponse call() throws Exception { + return connect().executeScript(commands); + } + }); + } + + @Override + public org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse executePs(final List<String> commands) { + return exec(new Callable<io.cloudsoft.winrm4j.winrm.WinRmToolResponse>() { + @Override public WinRmToolResponse call() throws Exception { + return connect().executePs(commands); + } + }); + } + + @Override + public org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse copyToServer(InputStream source, String destination) { + executePs(ImmutableList.of("rm -ErrorAction SilentlyContinue " + destination)); + try { + int chunkSize = getRequiredConfig(bag, COPY_FILE_CHUNK_SIZE_BYTES); + byte[] inputData = new byte[chunkSize]; + int bytesRead; + int expectedFileSize = 0; + while ((bytesRead = source.read(inputData)) > 0) { + byte[] chunk; + if (bytesRead == chunkSize) { + chunk = inputData; + } else { + chunk = Arrays.copyOf(inputData, bytesRead); + } + executePs(ImmutableList.of("If ((!(Test-Path " + destination + ")) -or ((Get-Item '" + destination + "').length -eq " + + expectedFileSize + ")) {Add-Content -Encoding Byte -path " + destination + + " -value ([System.Convert]::FromBase64String(\"" + new String(Base64.encodeBase64(chunk)) + "\"))}")); + expectedFileSize += bytesRead; + } + + return new org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse("", "", 0); + } catch (java.io.IOException e) { + throw Exceptions.propagate(e); + } + } + + private org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse exec(Callable<io.cloudsoft.winrm4j.winrm.WinRmToolResponse> task) { + Collection<Throwable> exceptions = Lists.newArrayList(); + for (int i = 0; i < execTries; i++) { + try { + return wrap(task.call()); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + Duration sleep = Duration.millis(Math.min(Math.pow(2, i) * 1000, execRetryDelay.toMilliseconds())); + if (i == (execTries+1)) { + LOG.info("Propagating WinRM exception (attempt "+(i+1)+" of "+execTries+")", e); + } else if (i == 0) { + LOG.warn("Ignoring WinRM exception and will retry after "+sleep+" (attempt "+(i+1)+" of "+execTries+")", e); + Time.sleep(sleep); + } else { + LOG.debug("Ignoring WinRM exception and will retry after "+sleep+" (attempt "+(i+1)+" of "+execTries+")", e); + Time.sleep(sleep); + } + exceptions.add(e); + } + } + throw Exceptions.propagate("failed to execute command", exceptions); + } + + private io.cloudsoft.winrm4j.winrm.WinRmTool connect() { + return io.cloudsoft.winrm4j.winrm.WinRmTool.connect(host+":"+port, user, password); + } + + private <T> T getRequiredConfig(ConfigBag bag, ConfigKey<T> key) { + T result = bag.get(key); + if (result == null) { + throw new IllegalArgumentException("Missing config "+key+" in "+Sanitizer.sanitize(bag)); + } + return result; + } + + private org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse wrap(io.cloudsoft.winrm4j.winrm.WinRmToolResponse resp) { + return new org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse(resp.getStdOut(), resp.getStdErr(), resp.getStatusCode()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/42c11132/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedTest.java ---------------------------------------------------------------------- diff --git a/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedTest.java b/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedTest.java index 059797b..17d7f0a 100644 --- a/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedTest.java +++ b/software/winrm/src/test/java/org/apache/brooklyn/feed/windows/WindowsPerformanceCounterFeedTest.java @@ -34,6 +34,7 @@ import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.core.test.BrooklynAppUnitTestSupport; import org.apache.brooklyn.core.test.entity.TestEntity; import org.apache.brooklyn.test.EntityTestUtils; +import org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse; import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,8 +43,6 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation; -import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet;
