Coverage for manila/share/drivers/dell_emc/common/enas/connector.py: 97%
99 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2016 Dell Inc. or its subsidiaries.
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.
16from http import cookiejar as http_cookiejar
17import shlex
18from urllib import error as url_error
19from urllib import request as url_request
21from oslo_concurrency import processutils
22from oslo_log import log
23from oslo_utils import excutils
25from manila import exception
26from manila.i18n import _
27from manila.share.drivers.dell_emc.common.enas import constants
28from manila.share.drivers.dell_emc.common.enas import utils as enas_utils
29from manila import ssh_utils
31LOG = log.getLogger(__name__)
34class XMLAPIConnector(object):
35 def __init__(self, configuration, debug=True):
36 super(XMLAPIConnector, self).__init__()
37 self.storage_ip = enas_utils.convert_ipv6_format_if_needed(
38 configuration.emc_nas_server)
39 self.username = configuration.emc_nas_login
40 self.password = configuration.emc_nas_password
41 self.debug = debug
42 self.auth_url = 'https://' + self.storage_ip + '/Login'
43 self._url = 'https://{}/servlets/CelerraManagementServices'.format(
44 self.storage_ip)
45 context = enas_utils.create_ssl_context(configuration)
46 if context: 46 ↛ 49line 46 didn't jump to line 49 because the condition on line 46 was always true
47 https_handler = url_request.HTTPSHandler(context=context)
48 else:
49 https_handler = url_request.HTTPSHandler()
50 cookie_handler = url_request.HTTPCookieProcessor(
51 http_cookiejar.CookieJar())
52 self.url_opener = url_request.build_opener(https_handler,
53 cookie_handler)
54 self._do_setup()
56 def _do_setup(self):
57 credential = ('user=' + self.username
58 + '&password=' + self.password
59 + '&Login=Login')
60 req = url_request.Request(self.auth_url, credential.encode(),
61 constants.CONTENT_TYPE_URLENCODE)
62 resp = self.url_opener.open(req)
63 resp_body = resp.read()
64 self._http_log_resp(resp, resp_body)
66 def _http_log_req(self, req):
67 if not self.debug:
68 return
70 string_parts = ['curl -i']
71 string_parts.append(' -X %s' % req.get_method())
73 for k in req.headers:
74 header = ' -H "%s: %s"' % (k, req.headers[k])
75 string_parts.append(header)
77 if req.data: 77 ↛ 79line 77 didn't jump to line 79 because the condition on line 77 was always true
78 string_parts.append(" -d '%s'" % req.data)
79 string_parts.append(' ' + req.get_full_url())
80 LOG.debug("\nREQ: %s.\n", "".join(string_parts))
82 def _http_log_resp(self, resp, body):
83 if not self.debug:
84 return
86 headers = str(resp.headers).replace('\n', '\\n')
88 LOG.debug(
89 'RESP: [%(code)s] %(resp_hdrs)s\n'
90 'RESP BODY: %(resp_b)s.\n',
91 {
92 'code': resp.getcode(),
93 'resp_hdrs': headers,
94 'resp_b': body,
95 }
96 )
98 def _request(self, req_body=None, method=None,
99 header=constants.CONTENT_TYPE_URLENCODE):
100 req = url_request.Request(self._url, req_body.encode(), header)
101 if method not in (None, 'GET', 'POST'):
102 req.get_method = lambda: method
103 self._http_log_req(req)
104 try:
105 resp = self.url_opener.open(req)
106 resp_body = resp.read()
107 self._http_log_resp(resp, resp_body)
108 except url_error.HTTPError as http_err:
109 if '403' == str(http_err.code):
110 raise exception.NotAuthorized()
111 else:
112 err = {'errorCode': -1,
113 'httpStatusCode': http_err.code,
114 'messages': str(http_err),
115 'request': req_body}
116 msg = (_("The request is invalid. Reason: %(reason)s") %
117 {'reason': err})
118 raise exception.ManilaException(message=msg)
120 return resp_body
122 def request(self, req_body=None, method=None,
123 header=constants.CONTENT_TYPE_URLENCODE):
124 try:
125 resp_body = self._request(req_body, method, header)
126 except exception.NotAuthorized:
127 LOG.debug("Login again because client certification "
128 "may be expired.")
129 self._do_setup()
130 resp_body = self._request(req_body, method, header)
132 return resp_body
135class SSHConnector(object):
136 def __init__(self, configuration, debug=True):
137 super(SSHConnector, self).__init__()
138 self.storage_ip = configuration.emc_nas_server
139 self.username = configuration.emc_nas_login
140 self.password = configuration.emc_nas_password
141 self.debug = debug
143 self.sshpool = ssh_utils.SSHPool(ip=self.storage_ip,
144 port=22,
145 conn_timeout=None,
146 login=self.username,
147 password=self.password)
149 def run_ssh(self, cmd_list, check_exit_code=False):
150 command = ' '.join(shlex.quote(cmd_arg) for cmd_arg in cmd_list)
152 with self.sshpool.item() as ssh:
153 try:
154 out, err = processutils.ssh_execute(
155 ssh, command, check_exit_code=check_exit_code)
156 self.log_request(command, out, err)
158 return out, err
159 except processutils.ProcessExecutionError as e:
160 with excutils.save_and_reraise_exception():
161 LOG.error('Error running SSH command: %(cmd)s. '
162 'Error: %(excmsg)s.',
163 {'cmd': command, 'excmsg': e})
165 def log_request(self, cmd, out, err):
166 if not self.debug:
167 return
169 LOG.debug("\nSSH command: %s.\n", cmd)
170 LOG.debug("SSH command output: out=%(out)s, err=%(err)s.\n",
171 {'out': out, 'err': err})