NetApp ONTAP: Add support for filtering API tracing

API Tracing is valuable when diagnosing problems or
unexpected behaviors with the ONTAP Manila driver.

However, turning it on may spam logs and make it rather
harder to trace through specific API calls.

Change-Id: I3c91638138ec1a2652efdadbc86176afa295abd8
This commit is contained in:
Goutham Pacha Ravi 2017-08-27 03:22:57 -04:00
parent 9f622e2a50
commit 148f77f184
10 changed files with 77 additions and 22 deletions

View File

@ -19,6 +19,7 @@ Contains classes required to issue API calls to Data ONTAP and OnCommand DFM.
"""
import copy
import re
from lxml import etree
from oslo_log import log
@ -27,6 +28,7 @@ from six.moves import urllib
from manila import exception
from manila.i18n import _
from manila.share.drivers.netapp import utils
LOG = log.getLogger(__name__)
@ -69,7 +71,8 @@ class NaServer(object):
def __init__(self, host, server_type=SERVER_TYPE_FILER,
transport_type=TRANSPORT_TYPE_HTTP,
style=STYLE_LOGIN_PASSWORD, username=None,
password=None, port=None, trace=False):
password=None, port=None, trace=False,
api_trace_pattern=utils.API_TRACE_PATTERN):
self._host = host
self.set_server_type(server_type)
self.set_transport_type(transport_type)
@ -79,8 +82,8 @@ class NaServer(object):
self._username = username
self._password = password
self._trace = trace
self._api_trace_pattern = api_trace_pattern
self._refresh_conn = True
self._trace = trace
LOG.debug('Using NetApp controller: %s', self._host)
@ -213,19 +216,18 @@ class NaServer(object):
self._password = password
self._refresh_conn = True
def set_trace(self, trace=True):
"""Enable or disable the API tracing facility."""
self._trace = trace
def invoke_elem(self, na_element, enable_tunneling=False):
"""Invoke the API on the server."""
if na_element and not isinstance(na_element, NaElement):
ValueError('NaElement must be supplied to invoke API')
request, request_element = self._create_request(na_element,
enable_tunneling)
if self._trace:
api_name = na_element.get_name()
api_name_matches_regex = (re.match(self._api_trace_pattern, api_name)
is not None)
if self._trace and api_name_matches_regex:
LOG.debug("Request: %s", request_element.to_string(pretty=True))
if (not hasattr(self, '_opener') or not self._opener
@ -246,7 +248,7 @@ class NaServer(object):
response_xml = response.read()
response_element = self._get_result(response_xml)
if self._trace:
if self._trace and api_name_matches_regex:
LOG.debug("Response: %s", response_element.to_string(pretty=True))
return response_element

View File

@ -32,7 +32,9 @@ class NetAppBaseClient(object):
port=kwargs['port'],
username=kwargs['username'],
password=kwargs['password'],
trace=kwargs.get('trace', False))
trace=kwargs.get('trace', False),
api_trace_pattern=kwargs.get('api_trace_pattern',
na_utils.API_TRACE_PATTERN))
def get_ontapi_version(self, cached=True):
"""Gets the supported ontapi version."""

View File

@ -117,7 +117,8 @@ class NetAppCmodeFileStorageLibrary(object):
self._app_version = kwargs.get('app_version', 'unknown')
na_utils.setup_tracing(self.configuration.netapp_trace_flags)
na_utils.setup_tracing(self.configuration.netapp_trace_flags,
self.configuration.netapp_api_trace_pattern)
self._backend_name = self.configuration.safe_get(
'share_backend_name') or driver_name
@ -150,7 +151,8 @@ class NetAppCmodeFileStorageLibrary(object):
hostname=self.configuration.netapp_server_hostname,
port=self.configuration.netapp_server_port,
vserver=vserver,
trace=na_utils.TRACE_API)
trace=na_utils.TRACE_API,
api_trace_pattern=na_utils.API_TRACE_PATTERN)
self._clients[vserver] = client
return client

View File

@ -115,7 +115,16 @@ netapp_support_opts = [
cfg.StrOpt('netapp_trace_flags',
help=('Comma-separated list of options that control which '
'trace info is written to the debug logs. Values '
'include method and api.')), ]
'include method and api. API logging can further be '
'filtered with the '
'``netapp_api_trace_pattern option``.')),
cfg.StrOpt('netapp_api_trace_pattern',
default='(.*)',
help=('A regular expression to limit the API tracing. This '
'option is honored only if enabling ``api`` tracing '
'with the ``netapp_trace_flags`` option. By default, '
'all APIs will be traced.')),
]
netapp_data_motion_opts = [
cfg.IntOpt('netapp_snapmirror_quiesce_timeout',

View File

@ -18,6 +18,7 @@
import collections
import decimal
import platform
import re
from oslo_concurrency import processutils as putils
from oslo_log import log
@ -33,6 +34,7 @@ LOG = log.getLogger(__name__)
VALID_TRACE_FLAGS = ['method', 'api']
TRACE_METHOD = False
TRACE_API = False
API_TRACE_PATTERN = '(.*)'
def validate_driver_instantiation(**kwargs):
@ -65,16 +67,24 @@ def round_down(value, precision='0.00'):
decimal.Decimal(precision), rounding=decimal.ROUND_DOWN))
def setup_tracing(trace_flags_string):
def setup_tracing(trace_flags_string, api_trace_pattern=API_TRACE_PATTERN):
global TRACE_METHOD
global TRACE_API
global API_TRACE_PATTERN
TRACE_METHOD = False
TRACE_API = False
API_TRACE_PATTERN = api_trace_pattern
if trace_flags_string:
flags = trace_flags_string.split(',')
flags = [flag.strip() for flag in flags]
for invalid_flag in list(set(flags) - set(VALID_TRACE_FLAGS)):
LOG.warning('Invalid trace flag: %s', invalid_flag)
try:
re.compile(api_trace_pattern)
except re.error:
msg = _('Cannot parse the API trace pattern. %s is not a '
'valid python regular expression.') % api_trace_pattern
raise exception.BadConfigurationException(reason=msg)
TRACE_METHOD = 'method' in flags
TRACE_API = 'api' in flags

View File

@ -24,7 +24,8 @@ CONNECTION_INFO = {
'transport_type': 'https',
'port': 443,
'username': 'admin',
'password': 'passw0rd'
'password': 'passw0rd',
'api_trace_pattern': '(.*)',
}
CLUSTER_NAME = 'fake_cluster'
@ -2395,7 +2396,7 @@ QOS_POLICY_GROUP_GET_ITER_RESPONSE = etree.XML("""
'max_througput': QOS_MAX_THROUGHPUT,
})
FAKE_VOL_XML = """<volume-info xmlns='http://www.netapp.com/filer/admin'>
FAKE_VOL_XML = """<volume-info>
<name>open123</name>
<state>online</state>
<size-total>0</size-total>

View File

@ -220,10 +220,20 @@ class NetAppApiServerTests(test.TestCase):
na_element)
self.assertEqual('unknown', exception.code)
def test_invoke_elem_valid(self):
@ddt.data({'trace_enabled': False,
'trace_pattern': '(.*)', 'log': False},
{'trace_enabled': True,
'trace_pattern': '(?!(volume)).*', 'log': False},
{'trace_enabled': True,
'trace_pattern': '(.*)', 'log': True},
{'trace_enabled': True,
'trace_pattern': '^volume-(info|get-iter)$', 'log': True})
@ddt.unpack
def test_invoke_elem_valid(self, trace_enabled, trace_pattern, log):
"""Tests the method invoke_elem with valid parameters"""
na_element = fake.FAKE_NA_ELEMENT
self.root._trace = True
self.root._trace = trace_enabled
self.root._api_trace_pattern = trace_pattern
self.mock_object(self.root, '_create_request', mock.Mock(
return_value=('abc', fake.FAKE_NA_ELEMENT)))
self.mock_object(api, 'LOG')
@ -237,4 +247,5 @@ class NetAppApiServerTests(test.TestCase):
self.root.invoke_elem(na_element)
self.assertEqual(2, api.LOG.debug.call_count)
expected_log_count = 2 if log else 0
self.assertEqual(expected_log_count, api.LOG.debug.call_count)

View File

@ -83,7 +83,8 @@ CLIENT_KWARGS = {
'vserver': None,
'transport_type': 'https',
'password': 'pass',
'port': '443'
'port': '443',
'api_trace_pattern': '(.*)',
}
SHARE = {

View File

@ -18,6 +18,7 @@ Mock unit tests for the NetApp driver utility module
import platform
import ddt
import mock
from oslo_concurrency import processutils as putils
from oslo_log import log
@ -28,6 +29,7 @@ from manila import test
from manila import version
@ddt.ddt
class NetAppDriverUtilsTestCase(test.TestCase):
def setUp(self):
@ -56,19 +58,22 @@ class NetAppDriverUtilsTestCase(test.TestCase):
self.assertAlmostEqual(na_utils.round_down(-5.567, '0'), -5)
def test_setup_tracing(self):
na_utils.setup_tracing(None)
na_utils.setup_tracing(None, api_trace_pattern='(.*)')
self.assertFalse(na_utils.TRACE_API)
self.assertFalse(na_utils.TRACE_METHOD)
self.assertEqual('(.*)', na_utils.API_TRACE_PATTERN)
self.assertEqual(0, na_utils.LOG.warning.call_count)
na_utils.setup_tracing('method')
self.assertFalse(na_utils.TRACE_API)
self.assertTrue(na_utils.TRACE_METHOD)
self.assertEqual('(.*)', na_utils.API_TRACE_PATTERN)
self.assertEqual(0, na_utils.LOG.warning.call_count)
na_utils.setup_tracing('method,api')
na_utils.setup_tracing('method,api', api_trace_pattern='(^fancy-api$)')
self.assertTrue(na_utils.TRACE_API)
self.assertTrue(na_utils.TRACE_METHOD)
self.assertEqual('(^fancy-api$)', na_utils.API_TRACE_PATTERN)
self.assertEqual(0, na_utils.LOG.warning.call_count)
def test_setup_tracing_invalid_key(self):
@ -78,6 +83,12 @@ class NetAppDriverUtilsTestCase(test.TestCase):
self.assertTrue(na_utils.TRACE_METHOD)
self.assertEqual(1, na_utils.LOG.warning.call_count)
@ddt.data('?!(bad', '(reg]+', 'eX?!)')
def test_setup_tracing_invalid_regex(self, regex):
self.assertRaises(exception.BadConfigurationException,
na_utils.setup_tracing, 'method,api',
api_trace_pattern=regex)
@na_utils.trace
def _trace_test_method(*args, **kwargs):
return 'OK'

View File

@ -0,0 +1,6 @@
---
features:
- The NetApp driver supports a new configuration option
``netapp_api_trace_pattern`` to enable filtering backend API
interactions to log. This option must be specified in the backend
section when desired and it accepts a valid python regular expression.