wsman.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. # Copyright 2016 Cloudbase Solutions Srl
  2. # All Rights Reserved.
  3. import base64
  4. from oslo_log import log as logging
  5. from winrm import protocol
  6. from coriolis import exception
  7. from coriolis import utils
  8. AUTH_BASIC = "basic"
  9. AUTH_KERBEROS = "kerberos"
  10. AUTH_CERTIFICATE = "certificate"
  11. CODEPAGE_UTF8 = 65001
  12. LOG = logging.getLogger(__name__)
  13. class WSManConnection(object):
  14. def __init__(self):
  15. self._protocol = None
  16. EOL = "\r\n"
  17. @utils.retry_on_error()
  18. def connect(self, url, username, auth=None, password=None,
  19. cert_pem=None, cert_key_pem=None):
  20. protocol.Protocol.DEFAULT_TIMEOUT = 3600
  21. if not auth:
  22. if cert_pem:
  23. auth = AUTH_CERTIFICATE
  24. else:
  25. auth = AUTH_BASIC
  26. auth_transport_map = {AUTH_BASIC: 'plaintext',
  27. AUTH_KERBEROS: 'kerberos',
  28. AUTH_CERTIFICATE: 'ssl'}
  29. self._protocol = protocol.Protocol(
  30. endpoint=url,
  31. transport=auth_transport_map[auth],
  32. username=username,
  33. password=password,
  34. cert_pem=cert_pem,
  35. cert_key_pem=cert_key_pem)
  36. def disconnect(self):
  37. self._protocol = None
  38. @utils.retry_on_error()
  39. def _exec_command(self, cmd, args=[]):
  40. shell_id = self._protocol.open_shell(codepage=CODEPAGE_UTF8)
  41. try:
  42. command_id = self._protocol.run_command(shell_id, cmd, args)
  43. try:
  44. (std_out,
  45. std_err,
  46. exit_code) = self._protocol.get_command_output(
  47. shell_id, command_id)
  48. finally:
  49. self._protocol.cleanup_command(shell_id, command_id)
  50. return (std_out, std_err, exit_code)
  51. finally:
  52. self._protocol.close_shell(shell_id)
  53. def exec_command(self, cmd, args=[]):
  54. LOG.debug("Executing WSMAN command: %s", str([cmd] + args))
  55. std_out, std_err, exit_code = self._exec_command(cmd, args)
  56. if exit_code:
  57. raise exception.CoriolisException(
  58. "Command \"%s\" failed with exit code: %s\n"
  59. "stdout: %s\nstd_err: %s" %
  60. (str([cmd] + args), exit_code, std_out, std_err))
  61. return std_out
  62. def exec_ps_command(self, cmd, ignore_stdout=False):
  63. LOG.debug("Executing PS command: %s", cmd)
  64. base64_cmd = base64.b64encode(cmd.encode('utf-16le')).decode()
  65. return self.exec_command(
  66. "powershell.exe", ["-EncodedCommand", base64_cmd])[:-2]
  67. def test_path(self, remote_path):
  68. ret_val = self.exec_ps_command("Test-Path -Path \"%s\"" % remote_path)
  69. return ret_val == "True"
  70. def download_file(self, url, remote_path):
  71. LOG.debug("Downloading: \"%(url)s\" to \"%(path)s\"",
  72. {"url": url, "path": remote_path})
  73. # Nano Server does not have Invoke-WebRequest and additionally
  74. # this is also faster
  75. self.exec_ps_command(
  76. "[Net.ServicePointManager]::SecurityProtocol = "
  77. "[Net.SecurityProtocolType]::Tls12;"
  78. "if(!([System.Management.Automation.PSTypeName]'"
  79. "System.Net.Http.HttpClient').Type) {$assembly = "
  80. "[System.Reflection.Assembly]::LoadWithPartialName("
  81. "'System.Net.Http')}; (new-object System.Net.Http.HttpClient)."
  82. "GetStreamAsync('%(url)s').Result.CopyTo("
  83. "(New-Object IO.FileStream '%(outfile)s', Create, Write, None), "
  84. "1MB)" % {"url": url, "outfile": remote_path},
  85. ignore_stdout=True)
  86. def write_file(self, remote_path, content):
  87. self.exec_ps_command(
  88. "[IO.File]::WriteAllBytes('%s', [Convert]::FromBase64String('%s'))"
  89. % (remote_path, base64.b64encode(content).decode()),
  90. ignore_stdout=True)