From e008d4a68db314d07d7d18a0d3f8d8ff98976852 Mon Sep 17 00:00:00 2001 From: Valentin Aitken Date: Fri, 20 May 2016 23:37:46 +0300 Subject: [PATCH] Ability to perform installation on a Windows Blueprint which changes Computer Name and requires reboot - update the `install.reboot.required` action to use computername when running the install command and the reboot command - set small and reasonable default value for connection retries of winrm4j client Use domain name after restart --- .../AbstractSoftwareProcessWinRmDriver.java | 45 ++++++++++++++++--- .../software/base/VanillaWindowsProcess.java | 8 +++- .../VanillaWindowsProcessWinRmDriver.java | 32 +++++++++---- .../base/lifecycle/WinRmExecuteHelper.java | 8 ++++ .../util/core/internal/winrm/WinRmTool.java | 5 ++- .../internal/winrm/winrm4j/Winrm4jTool.java | 5 ++- 6 files changed, 85 insertions(+), 18 deletions(-) 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 979158fc3f..a10f6996b1 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 @@ -29,6 +29,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import org.apache.cxf.interceptor.Fault; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +57,8 @@ import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; +import javax.xml.ws.WebServiceException; + public abstract class AbstractSoftwareProcessWinRmDriver extends AbstractSoftwareProcessDriver implements NativeWindowsScriptRunner { private static final Logger LOG = LoggerFactory.getLogger(AbstractSoftwareProcessWinRmDriver.class); @@ -161,7 +164,12 @@ public WinRmMachineLocation getMachine() { } protected int executeCommandInTask(String command, String psCommand, String phase) { + return executeCommandInTask(command, psCommand, phase, null); + } + + protected int executeCommandInTask(String command, String psCommand, String phase, String ntDomain) { return newScript(phase) + .setNtDomain(ntDomain) .setCommand(command) .setPsCommand(psCommand) .failOnNonZeroResultCode() @@ -278,10 +286,16 @@ public Integer executeNativeOrPsCommand(Map flags, String regularCommand, String } WinRmToolResponse response; + + ImmutableMap.Builder winrmProps = ImmutableMap.builder(); + if (flags.get(WinRmTool.COMPUTER_NAME) != null) { + winrmProps.put(WinRmTool.COMPUTER_NAME, flags.get(WinRmTool.COMPUTER_NAME)); + } + if (Strings.isBlank(regularCommand)) { - response = getLocation().executePsScript(ImmutableList.of(powerShellCommand)); + response = getLocation().executePsScript(winrmProps.build(), ImmutableList.of(powerShellCommand)); } else { - response = getLocation().executeCommand(ImmutableList.of(regularCommand)); + response = getLocation().executeCommand(winrmProps.build(), ImmutableList.of(regularCommand)); } if (currentTask != null) { @@ -321,16 +335,37 @@ public int copyTo(InputStream source, String destination) { } public void rebootAndWait() { + rebootAndWait(null); + } + + public void rebootAndWait(String hostname) { try { - executePsScriptNoRetry(ImmutableList.of("Restart-Computer -Force")); + if (hostname != null) { + getLocation().executePsScript(ImmutableMap.of(WinRmTool.COMPUTER_NAME, hostname), ImmutableList.of("Restart-Computer -Force")); + } else { + getLocation().executePsScript(ImmutableList.of("Restart-Computer -Force")); + } } catch (Exception e) { - // Restarting the computer will cause the command to fail; ignore the exception and continue - Exceptions.propagateIfFatal(e); + Throwable interestingCause = findExceptionCausedByWindowsRestart(e); + if (interestingCause != null) { + LOG.debug("Restarting... exception while closing winrm session from the restart command {}", getEntity(), e); + } else { + throw e; + } } + waitForWinRmStatus(false, entity.getConfig(VanillaWindowsProcess.REBOOT_BEGUN_TIMEOUT)); waitForWinRmStatus(true, entity.getConfig(VanillaWindowsProcess.REBOOT_COMPLETED_TIMEOUT)).getWithError(); } + /** + * If machine is restarting, then will get WinRM IOExceptions + */ + protected Throwable findExceptionCausedByWindowsRestart(Exception e) { + return Exceptions.getFirstThrowableOfType(e, WebServiceException.class) != null ? + Exceptions.getFirstThrowableOfType(e, WebServiceException.class/*Wraps Soap exceptions*/) : Exceptions.getFirstThrowableOfType(e, Fault.class/*Wraps IO exceptions*/); + } + private String getDirectory(String fileName) { return fileName.substring(0, fileName.lastIndexOf("\\")); } diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java index 0555d390aa..795d4dce26 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcess.java @@ -20,6 +20,7 @@ import java.util.Collection; +import com.google.common.annotations.Beta; import org.apache.brooklyn.api.catalog.Catalog; import org.apache.brooklyn.api.catalog.CatalogConfig; import org.apache.brooklyn.api.entity.ImplementedBy; @@ -96,9 +97,12 @@ public interface VanillaWindowsProcess extends AbstractVanillaProcess { ConfigKey PRE_INSTALL_REBOOT_REQUIRED = ConfigKeys.newBooleanConfigKey("pre.install.reboot.required", "indicates that a reboot should be performed after the pre-install command is run", false); - + + @Beta ConfigKey INSTALL_REBOOT_REQUIRED = ConfigKeys.newBooleanConfigKey("install.reboot.required", - "indicates that a reboot should be performed after the install command is run", false); + "indicates that a reboot should be performed after the install command is run." + + "When running the install command and the reboot command this parameter adds computername when authenticating.", + false); ConfigKey CUSTOMIZE_REBOOT_REQUIRED = ConfigKeys.newBooleanConfigKey("customize.reboot.required", "indicates that a reboot should be performed after the customize command is run", false); diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinRmDriver.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinRmDriver.java index 28971e4633..5f7e72dd59 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinRmDriver.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/VanillaWindowsProcessWinRmDriver.java @@ -20,6 +20,7 @@ import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.core.entity.Attributes; +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.exceptions.Exceptions; @@ -49,15 +50,27 @@ public void start() { @Override public void install() { + // TODO: At some point in the future, this should probably be refactored to get the name of the machine in WinRmMachineLocation and set it as the hostname sensor + String hostname = null; + if (entity.getConfig(VanillaWindowsProcess.INSTALL_REBOOT_REQUIRED)) { + WinRmExecuteHelper checkHostnameTask = newScript("Checking hostname") + .setCommand("hostname") + .failOnNonZeroResultCode() + .gatherOutput(); + checkHostnameTask.execute(); + hostname = Strings.trimEnd(checkHostnameTask.getResultStdout()); + } + // TODO: Follow install path of VanillaSoftwareProcessSshDriver if(Strings.isNonBlank(getEntity().getConfig(VanillaWindowsProcess.INSTALL_COMMAND)) || Strings.isNonBlank(getEntity().getConfig(VanillaWindowsProcess.INSTALL_POWERSHELL_COMMAND))) { executeCommandInTask( getEntity().getConfig(VanillaWindowsProcess.INSTALL_COMMAND), getEntity().getConfig(VanillaWindowsProcess.INSTALL_POWERSHELL_COMMAND), - "install-command"); + "install-command", + hostname); } if (entity.getConfig(VanillaWindowsProcess.INSTALL_REBOOT_REQUIRED)) { - rebootAndWait(); + rebootAndWait(hostname); } } @@ -76,10 +89,13 @@ public void customize() { @Override public void launch() { - executeCommandInTask( - getEntity().getConfig(VanillaWindowsProcess.LAUNCH_COMMAND), - getEntity().getConfig(VanillaWindowsProcess.LAUNCH_POWERSHELL_COMMAND), - "launch-command"); + if (Strings.isNonBlank(getEntity().getConfig(VanillaWindowsProcess.LAUNCH_COMMAND)) || + Strings.isNonBlank(getEntity().getConfig(VanillaWindowsProcess.LAUNCH_POWERSHELL_COMMAND))) { + executeCommandInTask( + getEntity().getConfig(VanillaWindowsProcess.LAUNCH_COMMAND), + getEntity().getConfig(VanillaWindowsProcess.LAUNCH_POWERSHELL_COMMAND), + "launch-command"); + } } @Override @@ -90,9 +106,7 @@ public boolean isRunning() { getEntity().getConfig(VanillaWindowsProcess.CHECK_RUNNING_COMMAND), getEntity().getConfig(VanillaWindowsProcess.CHECK_RUNNING_POWERSHELL_COMMAND), "is-running-command"); } catch (Exception e) { - // If machine is restarting, then will get WinRM IOExceptions - don't propagate such exceptions - Throwable interestingCause = Exceptions.getFirstThrowableOfType(e, WebServiceException.class) != null ? - Exceptions.getFirstThrowableOfType(e, WebServiceException.class/*Wraps Soap exceptions*/) : Exceptions.getFirstThrowableOfType(e, Fault.class/*Wraps IO exceptions*/); + Throwable interestingCause = findExceptionCausedByWindowsRestart(e); if (interestingCause != null) { LOG.warn(getEntity() + " isRunning check failed. Executing WinRM command failed.", e); return false; diff --git a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/WinRmExecuteHelper.java b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/WinRmExecuteHelper.java index 65d05abac3..1bdd097bb5 100644 --- a/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/WinRmExecuteHelper.java +++ b/software/base/src/main/java/org/apache/brooklyn/entity/software/base/lifecycle/WinRmExecuteHelper.java @@ -32,6 +32,7 @@ import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.mgmt.TaskQueueingContext; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; +import org.apache.brooklyn.util.core.internal.winrm.WinRmTool; import org.apache.brooklyn.util.core.task.DynamicTasks; import org.apache.brooklyn.util.core.task.TaskBuilder; import org.apache.brooklyn.util.core.task.Tasks; @@ -54,6 +55,7 @@ public class WinRmExecuteHelper { protected final NativeWindowsScriptRunner runner; public final String summary; + private String domain; private String command; private String psCommand; @@ -67,6 +69,11 @@ public WinRmExecuteHelper(NativeWindowsScriptRunner runner, String summary) { this.summary = summary; } + public WinRmExecuteHelper setNtDomain(String domain) { + this.domain = domain; + return this; + } + public WinRmExecuteHelper setCommand(String command) { this.command = command; return this; @@ -142,6 +149,7 @@ public int executeInternal() { flags.put("out", stdout); flags.put("err", stderr); } + flags.put(WinRmTool.COMPUTER_NAME, domain); result = runner.executeNativeOrPsCommand(flags, command, psCommand, summary, false); if (!resultCodeCheck.apply(result)) { throw logWithDetailsAndThrow(format("Execution failed, invalid result %s for %s", result, summary), null); 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 index a09da48f3f..cd6b5eeef1 100644 --- 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 @@ -46,7 +46,7 @@ public interface WinRmTool { ConfigKey PROP_HOST = newStringConfigKey("host", "Host to connect to (required)", null); ConfigKey PROP_PORT = ConfigKeys.newIntegerConfigKey("port", "WinRM port to use when connecting to the remote machine"); ConfigKey USE_HTTPS_WINRM = ConfigKeys.newBooleanConfigKey("winrm.useHttps", "The parameter tells the machine sensors whether the winrm port is over https. If the parameter is true then 5986 will be used as a winrm port.", false); - ConfigKey RETRIES_OF_NETWORK_FAILURES = ConfigKeys.newIntegerConfigKey("retriesOfNetworkFailures", "The parameter sets the number of retries for connection failures. If you use high value, consider taking care for the machine's network."); + ConfigKey RETRIES_OF_NETWORK_FAILURES = ConfigKeys.newIntegerConfigKey("retriesOfNetworkFailures", "The parameter sets the number of retries for connection failures. If you use high value, consider taking care for the machine's network.", 4); /** * Flag which tells winrm whether to use Basic Authentication * or Negotiate plus NTLM. @@ -55,6 +55,9 @@ public interface WinRmTool { */ @Beta ConfigKey USE_NTLM = ConfigKeys.newBooleanConfigKey("winrm.useNtlm", "The parameter configures tells the machine sensors whether the winrm port is over https. If the parameter is true then 5986 will be used as a winrm port.", true); + + @Beta + ConfigKey COMPUTER_NAME = ConfigKeys.newStringConfigKey("winrm.computerName", "Windows Computer Name to use for authentication."); ConfigKey PROP_USER = newStringConfigKey("user", "User to connect as", null); ConfigKey PROP_PASSWORD = newStringConfigKey("password", "Password to use to connect", null); ConfigKey OPERATION_TIMEOUT = newStringConfigKey("winrm.operationTimeout", "WinRM OperationTimeout. If no output is available before the wsman:OperationTimeout expires, " + diff --git a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java index 078ecdf9c3..42bb4cb7c2 100644 --- a/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java +++ b/software/winrm/src/main/java/org/apache/brooklyn/util/core/internal/winrm/winrm4j/Winrm4jTool.java @@ -32,6 +32,7 @@ import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.core.internal.winrm.WinRmException; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import org.apache.brooklyn.util.time.Time; import org.apache.commons.codec.binary.Base64; @@ -63,6 +64,7 @@ public class Winrm4jTool implements org.apache.brooklyn.util.core.internal.winrm private final ConfigBag bag; private final String host; private final Integer port; + private final String computerName; private final String user; private final String password; private final int execTries; @@ -83,6 +85,7 @@ public Winrm4jTool(ConfigBag config) { port = config.get(PROP_PORT); useSecureWinrm = config.get(USE_HTTPS_WINRM); authenticationScheme = config.get(USE_NTLM) ? AuthSchemes.NTLM : null; + computerName = Strings.isNonBlank(config.get(COMPUTER_NAME)) ? config.get(COMPUTER_NAME) : null; user = getRequiredConfig(config, PROP_USER); password = getRequiredConfig(config, PROP_PASSWORD); execTries = getRequiredConfig(config, PROP_EXEC_TRIES); @@ -195,7 +198,7 @@ private org.apache.brooklyn.util.core.internal.winrm.WinRmToolResponse exec(Func } private io.cloudsoft.winrm4j.winrm.WinRmTool connect() { - WinRmTool.Builder builder = WinRmTool.Builder.builder(host, user, password); + WinRmTool.Builder builder = WinRmTool.Builder.builder(host, computerName, user, password); builder.setAuthenticationScheme(authenticationScheme); builder.useHttps(useSecureWinrm); builder.port(port);