Adds latches for pre-install files and pre-install/install/customize reboots
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/2c162165 Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/2c162165 Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/2c162165 Branch: refs/heads/master Commit: 2c16216558ed7c0745cf386b983623151f35de27 Parents: fbb4ce9 Author: Martin Harris <[email protected]> Authored: Thu May 14 13:06:25 2015 +0100 Committer: Richard Downer <[email protected]> Committed: Thu May 28 17:27:35 2015 +0100 ---------------------------------------------------------------------- .../entity/basic/BrooklynConfigKeys.java | 1 + .../location/basic/WinRmMachineLocation.java | 59 +++++++++++++++----- .../basic/AbstractSoftwareProcessDriver.java | 38 ++++++++++--- .../AbstractSoftwareProcessWinRmDriver.java | 41 ++++++++++++++ .../brooklyn/entity/basic/SoftwareProcess.java | 25 +++++++++ .../entity/basic/VanillaWindowsProcess.java | 14 +++++ .../basic/VanillaWindowsProcessWinRmDriver.java | 17 +++++- 7 files changed, 171 insertions(+), 24 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2c162165/core/src/main/java/brooklyn/entity/basic/BrooklynConfigKeys.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/brooklyn/entity/basic/BrooklynConfigKeys.java b/core/src/main/java/brooklyn/entity/basic/BrooklynConfigKeys.java index 6038d0c..25972c6 100644 --- a/core/src/main/java/brooklyn/entity/basic/BrooklynConfigKeys.java +++ b/core/src/main/java/brooklyn/entity/basic/BrooklynConfigKeys.java @@ -145,6 +145,7 @@ public class BrooklynConfigKeys { public static final ConfigKey<Boolean> START_LATCH = newBooleanConfigKey("start.latch", "Latch for blocking start until ready"); public static final ConfigKey<Boolean> SETUP_LATCH = newBooleanConfigKey("setup.latch", "Latch for blocking setup until ready"); + public static final ConfigKey<Boolean> PRE_INSTALL_RESOURCES_LATCH = newBooleanConfigKey("resources.preInstall.latch", "Latch for blocking pre-install resources until ready"); public static final ConfigKey<Boolean> INSTALL_RESOURCES_LATCH = newBooleanConfigKey("resources.install.latch", "Latch for blocking install resources until ready"); public static final ConfigKey<Boolean> INSTALL_LATCH = newBooleanConfigKey("install.latch", "Latch for blocking install until ready"); public static final ConfigKey<Boolean> RUNTIME_RESOURCES_LATCH = newBooleanConfigKey("resources.runtime.latch", "Latch for blocking runtime resources until ready"); http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2c162165/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 d535d34..1f651e1 100644 --- a/core/src/main/java/brooklyn/location/basic/WinRmMachineLocation.java +++ b/core/src/main/java/brooklyn/location/basic/WinRmMachineLocation.java @@ -48,6 +48,7 @@ import io.cloudsoft.winrm4j.winrm.WinRmTool; import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; import com.google.common.base.Charsets; +import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -117,7 +118,7 @@ public class WinRmMachineLocation extends AbstractLocation implements MachineLoc exceptions.add(e); } } - throw Exceptions.propagate("failed to execute powershell script", exceptions); + throw Exceptions.propagate("failed to execute shell script", exceptions); } public WinRmToolResponse executePsScript(String psScript) { @@ -128,9 +129,7 @@ public class WinRmMachineLocation extends AbstractLocation implements MachineLoc Collection<Throwable> exceptions = Lists.newArrayList(); for (int i = 0; i < 10; i++) { try { - WinRmTool winRmTool = WinRmTool.connect(getHostname(), getUsername(), getPassword()); - WinRmToolResponse response = winRmTool.executePs(psScript); - return response; + return executePsScriptNoRetry(psScript); } catch (Exception e) { LOG.warn("ignoring winrm exception and retrying after 5 seconds:", e); Time.sleep(Duration.FIVE_SECONDS); @@ -140,6 +139,12 @@ public class WinRmMachineLocation extends AbstractLocation implements MachineLoc throw Exceptions.propagate("failed to execute powershell script", exceptions); } + public WinRmToolResponse executePsScriptNoRetry(List<String> psScript) { + WinRmTool winRmTool = WinRmTool.connect(getHostname(), getUsername(), getPassword()); + WinRmToolResponse response = winRmTool.executePs(psScript); + return response; + } + public int copyTo(File source, File destination) { try { return copyTo(new FileInputStream(source), destination); @@ -184,16 +189,42 @@ public class WinRmMachineLocation extends AbstractLocation implements MachineLoc public static String getDefaultUserMetadataString() { // Using an encoded command obviates the need to escape - String unencodePowershell = - "$RDP = Get-WmiObject -Class Win32_TerminalServiceSetting -ComputerName $env:computername -Namespace root\\CIMV2\\TerminalServices -Authentication PacketPrivacy\r\n" + - "$RDP.SetAllowTSConnections(1,1)\r\n" + - "Set-ExecutionPolicy Unrestricted -Force\r\n" + - "Set-Item WSMan:\\localhost\\Shell\\MaxConcurrentUsers 100\r\n" + - "Set-Item WSMan:\\localhost\\Shell\\MaxMemoryPerShellMB 0\r\n" + - "Set-Item WSMan:\\localhost\\Shell\\MaxProcessesPerShell 0\r\n" + - "Set-Item WSMan:\\localhost\\Shell\\MaxShellsPerUser 0\r\n" + - "New-ItemProperty \"HKLM:\\System\\CurrentControlSet\\Control\\LSA\" -Name \"SuppressExtendedProtection\" -Value 1 -PropertyType \"DWord\""; -// "New-ItemProperty \"HKLM:\\System\\CurrentControlSet\\Control\\LSA\" -Name \"LmCompatibilityLevel\" -Value 3 -PropertyType \"DWord\" \r\n"; + String unencodePowershell = Joiner.on("\r\n").join(ImmutableList.of( + // Allow TS connections + "$RDP = Get-WmiObject -Class Win32_TerminalServiceSetting -ComputerName $env:computername -Namespace root\\CIMV2\\TerminalServices -Authentication PacketPrivacy", + "$RDP.SetAllowTSConnections(1,1)", + "Set-ExecutionPolicy Unrestricted -Force", + // Set unlimited values for remote execution limits + "Set-Item WSMan:\\localhost\\Shell\\MaxConcurrentUsers 100", + "Set-Item WSMan:\\localhost\\Shell\\MaxMemoryPerShellMB 0", + "Set-Item WSMan:\\localhost\\Shell\\MaxProcessesPerShell 0", + "Set-Item WSMan:\\localhost\\Shell\\MaxShellsPerUser 0", + "New-ItemProperty \"HKLM:\\System\\CurrentControlSet\\Control\\LSA\" -Name \"SuppressExtendedProtection\" -Value 1 -PropertyType \"DWord\"", + // The following allows scripts to re-authenticate with local credential - this is required + // as certain operations cannot be performed with remote credentials + "$allowed = @('WSMAN/*')", + "$key = 'hklm:\\SOFTWARE\\Policies\\Microsoft\\Windows\\CredentialsDelegation'", + "if (!(Test-Path $key)) {", + " md $key", + "}", + "New-ItemProperty -Path $key -Name AllowFreshCredentials -Value 1 -PropertyType Dword -Force", + "New-ItemProperty -Path $key -Name AllowFreshCredentialsWhenNTLMOnly -Value 1 -PropertyType Dword -Force", + "$credKey = Join-Path $key 'AllowFreshCredentials'", + "if (!(Test-Path $credKey)) {", + " md $credkey", + "}", + "$ntlmKey = Join-Path $key 'AllowFreshCredentialsWhenNTLMOnly'", + "if (!(Test-Path $ntlmKey)) {", + " md $ntlmKey", + "}", + "$i = 1", + "$allowed |% {", + " # Script does not take into account existing entries in this key", + " New-ItemProperty -Path $credKey -Name $i -Value $_ -PropertyType String -Force", + " New-ItemProperty -Path $ntlmKey -Name $i -Value $_ -PropertyType String -Force", + " $i++", + "}" + )); String encoded = new String(Base64.encodeBase64(unencodePowershell.getBytes(Charsets.UTF_16LE))); return "winrm quickconfig -q & " + http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2c162165/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java index 061cd12..a5a4cde 100644 --- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java +++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessDriver.java @@ -105,6 +105,11 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr skipStart = entityStarted.or(false); } if (!skipStart) { + DynamicTasks.queue("copy-pre-install-resources", new Runnable() { public void run() { + waitForConfigKey(BrooklynConfigKeys.PRE_INSTALL_RESOURCES_LATCH); + copyPreInstallResources(); + }}); + DynamicTasks.queue("pre-install", new Runnable() { public void run() { preInstall(); }}); @@ -178,7 +183,7 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr public abstract void stop(); /** - * Implement this method in child classes to add some post-launch behavior + * Implement this method in child classes to add some pre-install behavior */ public void preInstall() {} @@ -260,6 +265,20 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr } /** + * Files and templates to be copied to the server <em>before</em> pre-install. This allows the {@link #preInstall()} + * process to have access to all required resources. + * <p> + * Will be prefixed with the entity's {@link #getInstallDir() install directory} if relative. + * + * @see SoftwareProcess#PRE_INSTALL_FILES + * @see SoftwareProcess#PRE_INSTALL_TEMPLATES + * @see #copyRuntimeResources() + */ + public void copyPreInstallResources() { + copyResources(entity.getConfig(SoftwareProcess.PRE_INSTALL_FILES), entity.getConfig(SoftwareProcess.PRE_INSTALL_TEMPLATES)); + } + + /** * Files and templates to be copied to the server <em>before</em> installation. This allows the {@link #install()} * process to have access to all required resources. * <p> @@ -270,7 +289,10 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr * @see #copyRuntimeResources() */ public void copyInstallResources() { + copyResources(entity.getConfig(SoftwareProcess.INSTALL_FILES), entity.getConfig(SoftwareProcess.INSTALL_TEMPLATES)); + } + private void copyResources(Map<String, String> files, Map<String, String> templates) { // Ensure environment variables are not looked up here, otherwise sub-classes might // lookup port numbers and fail with ugly error if port is not set; better to wait // until in Entity's code (e.g. customize) where such checks are done explicitly. @@ -279,19 +301,17 @@ public abstract class AbstractSoftwareProcessDriver implements SoftwareProcessDr // TODO see comment in copyResource, that should be queued as a task like the above // (better reporting in activities console) - Map<String, String> installFiles = entity.getConfig(SoftwareProcess.INSTALL_FILES); - if (installFiles != null && installFiles.size() > 0) { - for (String source : installFiles.keySet()) { - String target = installFiles.get(source); + if (files != null && files.size() > 0) { + for (String source : files.keySet()) { + String target = files.get(source); String destination = Os.isAbsolutish(target) ? target : Os.mergePathsUnix(getInstallDir(), target); copyResource(source, destination, true); } } - Map<String, String> installTemplates = entity.getConfig(SoftwareProcess.INSTALL_TEMPLATES); - if (installTemplates != null && installTemplates.size() > 0) { - for (String source : installTemplates.keySet()) { - String target = installTemplates.get(source); + if (templates != null && templates.size() > 0) { + for (String source : templates.keySet()) { + String target = templates.get(source); String destination = Os.isAbsolutish(target) ? target : Os.mergePathsUnix(getInstallDir(), target); copyTemplate(source, destination, true, MutableMap.<String, Object>of()); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2c162165/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessWinRmDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessWinRmDriver.java b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessWinRmDriver.java index af6a80c..92f4fa6 100644 --- a/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessWinRmDriver.java +++ b/software/base/src/main/java/brooklyn/entity/basic/AbstractSoftwareProcessWinRmDriver.java @@ -22,11 +22,18 @@ import java.io.File; import java.io.InputStream; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import org.python.core.PyException; import brooklyn.config.ConfigKey; import brooklyn.event.AttributeSensor; import brooklyn.event.basic.Sensors; import brooklyn.location.basic.WinRmMachineLocation; +import brooklyn.util.exceptions.ReferenceWithError; +import brooklyn.util.repeat.Repeater; +import brooklyn.util.time.Duration; import io.cloudsoft.winrm4j.winrm.WinRmToolResponse; import com.google.api.client.util.Strings; @@ -132,6 +139,10 @@ public abstract class AbstractSoftwareProcessWinRmDriver extends AbstractSoftwar return getLocation().executeScript(script).getStatusCode(); } + public int executePowerShellNoRetry(List<String> psScript) { + return getLocation().executePsScriptNoRetry(psScript).getStatusCode(); + } + public int executePowerShell(List<String> psScript) { return getLocation().executePsScript(psScript).getStatusCode(); } @@ -144,8 +155,38 @@ public abstract class AbstractSoftwareProcessWinRmDriver extends AbstractSoftwar return getLocation().copyTo(source, destination); } + public void rebootAndWait() { + try { + executePowerShellNoRetry(ImmutableList.of("Restart-Computer -Force")); + } catch (PyException e) { + // Restarting the computer will cause the command to fail; ignore the exception and continue + } + waitForWinRmStatus(false, entity.getConfig(VanillaWindowsProcess.REBOOT_UNAVAILABLE_TIMEOUT)); + waitForWinRmStatus(true, entity.getConfig(VanillaWindowsProcess.REBOOT_AVAILABLE_TIMEOUT)).getWithError(); + } + private String getDirectory(String fileName) { return fileName.substring(0, fileName.lastIndexOf("\\")); } + private ReferenceWithError<Boolean> waitForWinRmStatus(final boolean requiredStatus, Duration timeout) { + // TODO: Reduce / remove duplication between this and JcloudsLocation.waitForWinRmAvailable + Callable<Boolean> checker = new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + try { + return (execute(ImmutableList.of("hostname")) == 0) == requiredStatus; + } catch (Exception e) { + return !requiredStatus; + } + } + }; + + return new Repeater() + .every(1, TimeUnit.SECONDS) + .until(checker) + .limitTimeTo(timeout) + .runKeepingError(); + } + } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2c162165/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java index d91a719..fe6d052 100644 --- a/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java +++ b/software/base/src/main/java/brooklyn/entity/basic/SoftwareProcess.java @@ -114,6 +114,31 @@ public interface SoftwareProcess extends Entity, Startable { ConfigKey<String> SUGGESTED_RUN_DIR = BrooklynConfigKeys.SUGGESTED_RUN_DIR; /** + * Files to be copied to the server before pre-install. + * <p> + * Map of {@code classpath://foo/file.txt} (or other url) source to destination path, + * as {@code subdir/file} relative to installation directory or {@code /absolute/path/to/file}. + * + * @see #PRE_INSTALL_TEMPLATES + */ + @Beta + @SuppressWarnings("serial") + @SetFromFlag("preInstallFiles") + ConfigKey<Map<String, String>> PRE_INSTALL_FILES = ConfigKeys.newConfigKey(new TypeToken<Map<String, String>>() { }, + "files.preinstall", "Mapping of files, to be copied before install, to destination name relative to installDir"); + + /** + * Templates to be filled in and then copied to the server before install. + * + * @see #PRE_INSTALL_FILES + */ + @Beta + @SuppressWarnings("serial") + @SetFromFlag("preInstallTemplates") + ConfigKey<Map<String, String>> PRE_INSTALL_TEMPLATES = ConfigKeys.newConfigKey(new TypeToken<Map<String, String>>() { }, + "templates.preinstall", "Mapping of templates, to be filled in and copied before pre-install, to destination name relative to installDir"); + + /** * Files to be copied to the server before install. * <p> * Map of {@code classpath://foo/file.txt} (or other url) source to destination path, http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2c162165/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcess.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcess.java b/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcess.java index 532821d..4740bf6 100644 --- a/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcess.java +++ b/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcess.java @@ -20,9 +20,18 @@ package brooklyn.entity.basic; import brooklyn.config.ConfigKey; import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.util.time.Duration; @ImplementedBy(VanillaWindowsProcessImpl.class) public interface VanillaWindowsProcess extends AbstractVanillaProcess { + ConfigKey<String> PRE_INSTALL_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("pre.install.powershell.command", + "powershell command to run during the pre-install phase"); + ConfigKey<Boolean> 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); + ConfigKey<Boolean> INSTALL_REBOOT_REQUIRED = ConfigKeys.newBooleanConfigKey("install.reboot.required", + "indicates that a reboot should be performed after the install command is run", false); + ConfigKey<Boolean> CUSTOMIZE_REBOOT_REQUIRED = ConfigKeys.newBooleanConfigKey("customize.reboot.required", + "indicates that a reboot should be performed after the customize command is run", false); ConfigKey<String> LAUNCH_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("launch.powershell.command", "command to run to launch the process"); ConfigKey<String> CHECK_RUNNING_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("checkRunning.powershell.command", @@ -37,4 +46,9 @@ public interface VanillaWindowsProcess extends AbstractVanillaProcess { "command to run during the install phase"); ConfigKey<String> INSTALL_POWERSHELL_COMMAND = ConfigKeys.newStringConfigKey("install.powershell.command", "powershell command to run during the install phase"); + ConfigKey<Duration> REBOOT_UNAVAILABLE_TIMEOUT = ConfigKeys.newDurationConfigKey("reboot.unavailable.timeout", + "duration to wait whilst waiting for a machine to become unavailable after a reboot", Duration.TWO_MINUTES); + // If automatic updates are enabled and there are updates waiting to be installed, thirty minutes may not be sufficient... + ConfigKey<Duration> REBOOT_AVAILABLE_TIMEOUT = ConfigKeys.newDurationConfigKey("reboot.unavailable.timeout", + "duration to wait whilst waiting for a machine to become unavailable after a reboot", Duration.minutes(30)); } http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/2c162165/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcessWinRmDriver.java ---------------------------------------------------------------------- diff --git a/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcessWinRmDriver.java b/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcessWinRmDriver.java index 4e66180..db2f546 100644 --- a/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcessWinRmDriver.java +++ b/software/base/src/main/java/brooklyn/entity/basic/VanillaWindowsProcessWinRmDriver.java @@ -27,15 +27,30 @@ public class VanillaWindowsProcessWinRmDriver extends AbstractSoftwareProcessWin } @Override + public void preInstall() { + super.preInstall(); + executeCommand(VanillaWindowsProcess.PRE_INSTALL_COMMAND, VanillaWindowsProcess.PRE_INSTALL_POWERSHELL_COMMAND, true); + if (entity.getConfig(VanillaWindowsProcess.PRE_INSTALL_REBOOT_REQUIRED)) { + rebootAndWait(); + } + } + + @Override public void install() { // TODO: Follow install path of VanillaSoftwareProcessSshDriver executeCommand(VanillaWindowsProcess.INSTALL_COMMAND, VanillaWindowsProcess.INSTALL_POWERSHELL_COMMAND, true); + if (entity.getConfig(VanillaWindowsProcess.INSTALL_REBOOT_REQUIRED)) { + rebootAndWait(); + } } @Override public void customize() { // TODO: Follow customize path of VanillaSoftwareProcessSshDriver executeCommand(VanillaWindowsProcess.CUSTOMIZE_COMMAND, VanillaWindowsProcess.CUSTOMIZE_POWERSHELL_COMMAND, true); + if (entity.getConfig(VanillaWindowsProcess.CUSTOMIZE_REBOOT_REQUIRED)) { + rebootAndWait(); + } } @Override @@ -51,7 +66,7 @@ public class VanillaWindowsProcessWinRmDriver extends AbstractSoftwareProcessWin @Override public void stop() { - executeCommand(VanillaWindowsProcess.STOP_POWERSHELL_COMMAND, VanillaWindowsProcess.STOP_COMMAND, true); + executeCommand(VanillaWindowsProcess.STOP_COMMAND, VanillaWindowsProcess.STOP_POWERSHELL_COMMAND, true); } }
