NetApp E-Series: Add debug tracing
Add method and api level debug tracing to the NetApp E-Series drivers using the utils.trace_method decorator. This is enabled in the driver via the 'trace_flags' configuration option. Change-Id: Ie23cbde27792001cb9424af96cba502d06555638
This commit is contained in:
parent
ba86efba8f
commit
f7ebe429c6
@ -35,7 +35,9 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
|
||||
self.my_client = client.RestClient('http', 'host', '80', '/test',
|
||||
'user', self.fake_password,
|
||||
system_id='fake_sys_id')
|
||||
self.my_client.invoke_service = mock.Mock()
|
||||
fake_response = mock.Mock()
|
||||
fake_response.status_code = 200
|
||||
self.my_client.invoke_service = mock.Mock(return_value=fake_response)
|
||||
|
||||
def test_register_storage_system_does_not_log_password(self):
|
||||
self.my_client.register_storage_system([], password=self.fake_password)
|
||||
@ -188,3 +190,101 @@ class NetAppEseriesClientDriverTestCase(test.TestCase):
|
||||
'hostGroupRef')
|
||||
|
||||
self.assertEqual([volume_mapping_2], mappings)
|
||||
|
||||
def test_to_pretty_dict_string(self):
|
||||
dict = {
|
||||
'foo': 'bar',
|
||||
'fu': {
|
||||
'nested': 'boo'
|
||||
}
|
||||
}
|
||||
expected_dict_string = ("""{
|
||||
"foo": "bar",
|
||||
"fu": {
|
||||
"nested": "boo"
|
||||
}
|
||||
}""")
|
||||
|
||||
dict_string = self.my_client._to_pretty_dict_string(dict)
|
||||
|
||||
self.assertEqual(expected_dict_string, dict_string)
|
||||
|
||||
def test_log_http_request(self):
|
||||
mock_log = self.mock_object(client, 'LOG')
|
||||
verb = "POST"
|
||||
url = "/v2/test/me"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
headers_string = """{
|
||||
"Content-Type": "application/json"
|
||||
}"""
|
||||
body = {}
|
||||
body_string = "{}"
|
||||
|
||||
self.my_client._log_http_request(verb, url, headers, body)
|
||||
|
||||
args = mock_log.debug.call_args
|
||||
log_message, log_params = args[0]
|
||||
final_msg = log_message % log_params
|
||||
self.assertIn(verb, final_msg)
|
||||
self.assertIn(url, final_msg)
|
||||
self.assertIn(headers_string, final_msg)
|
||||
self.assertIn(body_string, final_msg)
|
||||
|
||||
def test_log_http_request_no_body(self):
|
||||
mock_log = self.mock_object(client, 'LOG')
|
||||
verb = "POST"
|
||||
url = "/v2/test/me"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
headers_string = """{
|
||||
"Content-Type": "application/json"
|
||||
}"""
|
||||
body = None
|
||||
body_string = ""
|
||||
|
||||
self.my_client._log_http_request(verb, url, headers, body)
|
||||
|
||||
args = mock_log.debug.call_args
|
||||
log_message, log_params = args[0]
|
||||
final_msg = log_message % log_params
|
||||
self.assertIn(verb, final_msg)
|
||||
self.assertIn(url, final_msg)
|
||||
self.assertIn(headers_string, final_msg)
|
||||
self.assertIn(body_string, final_msg)
|
||||
|
||||
def test_log_http_response(self):
|
||||
mock_log = self.mock_object(client, 'LOG')
|
||||
status = "200"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
headers_string = """{
|
||||
"Content-Type": "application/json"
|
||||
}"""
|
||||
body = {}
|
||||
body_string = "{}"
|
||||
|
||||
self.my_client._log_http_response(status, headers, body)
|
||||
|
||||
args = mock_log.debug.call_args
|
||||
log_message, log_params = args[0]
|
||||
final_msg = log_message % log_params
|
||||
self.assertIn(status, final_msg)
|
||||
self.assertIn(headers_string, final_msg)
|
||||
self.assertIn(body_string, final_msg)
|
||||
|
||||
def test_log_http_response_no_body(self):
|
||||
mock_log = self.mock_object(client, 'LOG')
|
||||
status = "200"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
headers_string = """{
|
||||
"Content-Type": "application/json"
|
||||
}"""
|
||||
body = None
|
||||
body_string = ""
|
||||
|
||||
self.my_client._log_http_response(status, headers, body)
|
||||
|
||||
args = mock_log.debug.call_args
|
||||
log_message, log_params = args[0]
|
||||
final_msg = log_message % log_params
|
||||
self.assertIn(status, final_msg)
|
||||
self.assertIn(headers_string, final_msg)
|
||||
self.assertIn(body_string, final_msg)
|
||||
|
@ -31,6 +31,7 @@ from six.moves import urllib
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE
|
||||
import cinder.utils as cinder_utils
|
||||
from cinder.volume.drivers.netapp.eseries import utils
|
||||
|
||||
|
||||
@ -83,13 +84,8 @@ class WebserviceClient(object):
|
||||
" Error - %s."), e)
|
||||
raise exception.NetAppDriverException(
|
||||
_("Invoking web service failed."))
|
||||
self._eval_response(response)
|
||||
return response
|
||||
|
||||
def _eval_response(self, response):
|
||||
"""Evaluates response before passing result to invoker."""
|
||||
pass
|
||||
|
||||
|
||||
class RestClient(WebserviceClient):
|
||||
"""REST client specific to e-series storage service."""
|
||||
@ -125,34 +121,65 @@ class RestClient(WebserviceClient):
|
||||
def _invoke(self, method, path, data=None, use_system=True,
|
||||
timeout=None, verify=False, **kwargs):
|
||||
"""Invokes end point for resource on path."""
|
||||
scrubbed_data = copy.deepcopy(data)
|
||||
if scrubbed_data:
|
||||
if 'password' in scrubbed_data:
|
||||
scrubbed_data['password'] = "****"
|
||||
if 'storedPassword' in scrubbed_data:
|
||||
scrubbed_data['storedPassword'] = "****"
|
||||
|
||||
LOG.debug("Invoking rest with method: %(m)s, path: %(p)s,"
|
||||
" data: %(d)s, use_system: %(sys)s, timeout: %(t)s,"
|
||||
" verify: %(v)s, kwargs: %(k)s.",
|
||||
{'m': method, 'p': path, 'd': scrubbed_data,
|
||||
'sys': use_system, 't': timeout, 'v': verify, 'k': kwargs})
|
||||
url = self._get_resource_url(path, use_system, **kwargs)
|
||||
if self._content_type == 'json':
|
||||
headers = {'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'}
|
||||
if cinder_utils.TRACE_API:
|
||||
self._log_http_request(method, url, headers, data)
|
||||
data = json.dumps(data) if data else None
|
||||
res = self.invoke_service(method, url, data=data,
|
||||
headers=headers,
|
||||
timeout=timeout, verify=verify)
|
||||
return res.json() if res.text else None
|
||||
res_dict = res.json() if res.text else None
|
||||
|
||||
if cinder_utils.TRACE_API:
|
||||
self._log_http_response(res.status_code, dict(res.headers),
|
||||
res_dict)
|
||||
|
||||
self._eval_response(res)
|
||||
return res_dict
|
||||
else:
|
||||
raise exception.NetAppDriverException(
|
||||
_("Content type not supported."))
|
||||
|
||||
def _to_pretty_dict_string(self, data):
|
||||
"""Convert specified dict to pretty printed string."""
|
||||
return json.dumps(data, sort_keys=True,
|
||||
indent=2, separators=(',', ': '))
|
||||
|
||||
def _log_http_request(self, verb, url, headers, body):
|
||||
scrubbed_body = copy.deepcopy(body)
|
||||
if scrubbed_body:
|
||||
if 'password' in scrubbed_body:
|
||||
scrubbed_body['password'] = "****"
|
||||
if 'storedPassword' in scrubbed_body:
|
||||
scrubbed_body['storedPassword'] = "****"
|
||||
|
||||
params = {'verb': verb, 'path': url,
|
||||
'body': self._to_pretty_dict_string(scrubbed_body) or "",
|
||||
'headers': self._to_pretty_dict_string(headers)}
|
||||
LOG.debug("Invoking ESeries Rest API, Request:\n"
|
||||
"HTTP Verb: %(verb)s\n"
|
||||
"URL Path: %(path)s\n"
|
||||
"HTTP Headers:\n"
|
||||
"%(headers)s\n"
|
||||
"Body:\n"
|
||||
"%(body)s\n", (params))
|
||||
|
||||
def _log_http_response(self, status, headers, body):
|
||||
params = {'status': status,
|
||||
'body': self._to_pretty_dict_string(body) or "",
|
||||
'headers': self._to_pretty_dict_string(headers)}
|
||||
LOG.debug("ESeries Rest API, Response:\n"
|
||||
"HTTP Status Code: %(status)s\n"
|
||||
"HTTP Headers:\n"
|
||||
"%(headers)s\n"
|
||||
"Body:\n"
|
||||
"%(body)s\n", (params))
|
||||
|
||||
def _eval_response(self, response):
|
||||
"""Evaluates response before passing result to invoker."""
|
||||
super(RestClient, self)._eval_response(response)
|
||||
status_code = int(response.status_code)
|
||||
# codes >= 300 are not ok and to be treated as errors
|
||||
if status_code >= 300:
|
||||
|
@ -33,6 +33,7 @@ from cinder.volume.drivers.netapp.eseries import utils
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@cinder_utils.trace_method
|
||||
@cinder_utils.synchronized('map_es_volume')
|
||||
def map_volume_to_single_host(client, volume, eseries_vol, host,
|
||||
vol_map, multiattach_enabled):
|
||||
@ -83,6 +84,7 @@ def map_volume_to_single_host(client, volume, eseries_vol, host,
|
||||
raise exception.NetAppDriverException(msg % volume['id'])
|
||||
|
||||
|
||||
@cinder_utils.trace_method
|
||||
@cinder_utils.synchronized('map_es_volume')
|
||||
def map_volume_to_multiple_hosts(client, volume, eseries_vol, target_host,
|
||||
mapping):
|
||||
|
@ -52,6 +52,7 @@ CONF.register_opts(na_opts.netapp_transport_opts)
|
||||
CONF.register_opts(na_opts.netapp_san_opts)
|
||||
|
||||
|
||||
@six.add_metaclass(cinder_utils.TraceWrapperMetaclass)
|
||||
class NetAppESeriesLibrary(object):
|
||||
"""Executes commands relating to Volumes."""
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user