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:
Alex Meade 2015-07-07 15:04:01 -04:00
parent ba86efba8f
commit f7ebe429c6
4 changed files with 150 additions and 20 deletions

View File

@ -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)

View File

@ -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:

View File

@ -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):

View File

@ -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."""