Coverage for manila/share/drivers/windows/winrm_helper.py: 95%

96 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2026-02-18 22:19 +0000

1# Copyright (c) 2015 Cloudbase Solutions SRL 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16import base64 

17 

18from oslo_concurrency import processutils 

19from oslo_config import cfg 

20from oslo_log import log 

21from oslo_utils import importutils 

22from oslo_utils import strutils 

23 

24from manila import exception 

25from manila.i18n import _ 

26from manila import utils 

27 

28LOG = log.getLogger(__name__) 

29CONF = cfg.CONF 

30 

31winrm_opts = [ 

32 cfg.IntOpt( 

33 'winrm_conn_timeout', 

34 default=60, 

35 help='WinRM connection timeout.'), 

36 cfg.IntOpt( 

37 'winrm_operation_timeout', 

38 default=60, 

39 help='WinRM operation timeout.'), 

40 cfg.IntOpt( 

41 'winrm_retry_count', 

42 default=3, 

43 help='WinRM retry count.'), 

44 cfg.IntOpt( 

45 'winrm_retry_interval', 

46 default=5, 

47 help='WinRM retry interval in seconds'), 

48] 

49 

50CONF.register_opts(winrm_opts) 

51 

52DEFAULT_PORT_HTTP = 5985 

53DEFAULT_PORT_HTTPS = 5986 

54 

55TRANSPORT_PLAINTEXT = 'plaintext' 

56TRANSPORT_SSL = 'ssl' 

57 

58winrm = None 

59 

60 

61def setup_winrm(): 

62 global winrm 

63 if not winrm: 

64 try: 

65 winrm = importutils.import_module('winrm') 

66 except ImportError: 

67 raise exception.ShareBackendException( 

68 _("PyWinrm is not installed")) 

69 

70 

71class WinRMHelper(object): 

72 def __init__(self, configuration=None): 

73 if configuration: 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true

74 configuration.append_config_values(winrm_opts) 

75 self._config = configuration 

76 else: 

77 self._config = CONF 

78 

79 setup_winrm() 

80 

81 def _get_conn(self, server): 

82 auth = self._get_auth(server) 

83 conn = WinRMConnection( 

84 ip=server['ip'], 

85 conn_timeout=self._config.winrm_conn_timeout, 

86 operation_timeout=self._config.winrm_operation_timeout, 

87 **auth) 

88 return conn 

89 

90 def execute(self, server, command, check_exit_code=True, 

91 retry=True): 

92 retries = self._config.winrm_retry_count if retry else 1 

93 conn = self._get_conn(server) 

94 

95 @utils.retry(retry_param=Exception, 

96 interval=self._config.winrm_retry_interval, 

97 retries=retries) 

98 def _execute(): 

99 parsed_cmd, sanitized_cmd = self._parse_command(command) 

100 

101 LOG.debug("Executing command: %s", sanitized_cmd) 

102 (stdout, stderr, exit_code) = conn.execute(parsed_cmd) 

103 

104 sanitized_stdout = strutils.mask_password(stdout) 

105 sanitized_stderr = strutils.mask_password(stderr) 

106 LOG.debug("Executed command: %(cmd)s. Stdout: %(stdout)s. " 

107 "Stderr: %(stderr)s. Exit code %(exit_code)s", 

108 dict(cmd=sanitized_cmd, stdout=sanitized_stdout, 

109 stderr=sanitized_stderr, exit_code=exit_code)) 

110 

111 if check_exit_code and exit_code != 0: 

112 raise processutils.ProcessExecutionError( 

113 stdout=sanitized_stdout, 

114 stderr=sanitized_stderr, 

115 exit_code=exit_code, 

116 cmd=sanitized_cmd) 

117 return (stdout, stderr) 

118 return _execute() 

119 

120 def _parse_command(self, command): 

121 if isinstance(command, list) or isinstance(command, tuple): 121 ↛ 124line 121 didn't jump to line 124 because the condition on line 121 was always true

122 command = " ".join([str(c) for c in command]) 

123 

124 sanitized_cmd = strutils.mask_password(command) 

125 

126 b64_command = base64.b64encode(command.encode("utf_16_le")) 

127 command = ("powershell.exe -ExecutionPolicy RemoteSigned " 

128 "-NonInteractive -EncodedCommand %s" % b64_command) 

129 return command, sanitized_cmd 

130 

131 def _get_auth(self, server): 

132 auth = {'username': server['username']} 

133 

134 if server['use_cert_auth']: 

135 auth['cert_pem_path'] = server['cert_pem_path'] 

136 auth['cert_key_pem_path'] = server['cert_key_pem_path'] 

137 else: 

138 auth['password'] = server['password'] 

139 return auth 

140 

141 

142class WinRMConnection(object): 

143 _URL_TEMPLATE = '%(protocol)s://%(ip)s:%(port)s/wsman' 

144 

145 def __init__(self, ip=None, port=None, use_ssl=False, 

146 transport=None, username=None, password=None, 

147 cert_pem_path=None, cert_key_pem_path=None, 

148 operation_timeout=None, conn_timeout=None): 

149 setup_winrm() 

150 

151 use_cert = bool(cert_pem_path and cert_key_pem_path) 

152 transport = (TRANSPORT_SSL 

153 if use_cert else TRANSPORT_PLAINTEXT) 

154 

155 _port = port or self._get_default_port(use_cert) 

156 _url = self._get_url(ip, _port, use_cert) 

157 

158 self._conn = winrm.protocol.Protocol( 

159 endpoint=_url, transport=transport, 

160 username=username, password=password, 

161 cert_pem=cert_pem_path, cert_key_pem=cert_key_pem_path) 

162 self._conn.transport.timeout = conn_timeout 

163 self._conn.set_timeout(operation_timeout) 

164 

165 def _get_default_port(self, use_ssl): 

166 port = (DEFAULT_PORT_HTTPS 

167 if use_ssl else DEFAULT_PORT_HTTP) 

168 return port 

169 

170 def _get_url(self, ip, port, use_ssl): 

171 if not ip: 

172 err_msg = _("No IP provided.") 

173 raise exception.ShareBackendException(msg=err_msg) 

174 

175 protocol = 'https' if use_ssl else 'http' 

176 return self._URL_TEMPLATE % {'protocol': protocol, 

177 'ip': ip, 

178 'port': port} 

179 

180 def execute(self, cmd): 

181 shell_id = None 

182 cmd_id = None 

183 

184 try: 

185 shell_id = self._conn.open_shell() 

186 

187 cmd_id = self._conn.run_command(shell_id, cmd) 

188 

189 (stdout, 

190 stderr, 

191 exit_code) = self._conn.get_command_output(shell_id, cmd_id) 

192 finally: 

193 if cmd_id: 193 ↛ 195line 193 didn't jump to line 195 because the condition on line 193 was always true

194 self._conn.cleanup_command(shell_id, cmd_id) 

195 if shell_id: 195 ↛ 198line 195 didn't jump to line 198 because the condition on line 195 was always true

196 self._conn.close_shell(shell_id) 

197 

198 return (stdout, stderr, exit_code)