]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
NetApp E-Series: Add debug tracing
authorAlex Meade <mr.alex.meade@gmail.com>
Tue, 7 Jul 2015 19:04:01 +0000 (15:04 -0400)
committerAlex Meade <mr.alex.meade@gmail.com>
Wed, 8 Jul 2015 20:21:50 +0000 (20:21 +0000)
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

cinder/tests/unit/volume/drivers/netapp/eseries/test_client.py
cinder/volume/drivers/netapp/eseries/client.py
cinder/volume/drivers/netapp/eseries/host_mapper.py
cinder/volume/drivers/netapp/eseries/library.py

index 537219b7bdafde7c0d6dd1095f936646c5628de6..bd3169cac7929a7fab26741a20e8744eea1588e7 100644 (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)
index 867183c1b928c8900439e66f6ffae889cd7740d3..9a547ab15699f0ddeabb8e0c5bacccd5e5db511d 100644 (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:
index 507531fa05f7249fb0b953b8c774f12e83b388f4..29c08df30a07d8f2a850062d6c5de7e4f9c04fe6 100644 (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):
index 84d8c85d75122f415d6688d6de5233838385fba0..2b68c1b70300b4ce65bcb70887b6f92165e247d9 100644 (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."""