Merge "NetApp ONTAP: Add support for filtering API tracing"
This commit is contained in:
@@ -19,6 +19,7 @@ Contains classes required to issue API calls to Data ONTAP and OnCommand DFM.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import re
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@@ -27,6 +28,7 @@ from six.moves import urllib
|
|||||||
|
|
||||||
from manila import exception
|
from manila import exception
|
||||||
from manila.i18n import _
|
from manila.i18n import _
|
||||||
|
from manila.share.drivers.netapp import utils
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@@ -69,7 +71,8 @@ class NaServer(object):
|
|||||||
def __init__(self, host, server_type=SERVER_TYPE_FILER,
|
def __init__(self, host, server_type=SERVER_TYPE_FILER,
|
||||||
transport_type=TRANSPORT_TYPE_HTTP,
|
transport_type=TRANSPORT_TYPE_HTTP,
|
||||||
style=STYLE_LOGIN_PASSWORD, username=None,
|
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._host = host
|
||||||
self.set_server_type(server_type)
|
self.set_server_type(server_type)
|
||||||
self.set_transport_type(transport_type)
|
self.set_transport_type(transport_type)
|
||||||
@@ -79,8 +82,8 @@ class NaServer(object):
|
|||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
self._trace = trace
|
self._trace = trace
|
||||||
|
self._api_trace_pattern = api_trace_pattern
|
||||||
self._refresh_conn = True
|
self._refresh_conn = True
|
||||||
self._trace = trace
|
|
||||||
|
|
||||||
LOG.debug('Using NetApp controller: %s', self._host)
|
LOG.debug('Using NetApp controller: %s', self._host)
|
||||||
|
|
||||||
@@ -213,19 +216,18 @@ class NaServer(object):
|
|||||||
self._password = password
|
self._password = password
|
||||||
self._refresh_conn = True
|
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):
|
def invoke_elem(self, na_element, enable_tunneling=False):
|
||||||
"""Invoke the API on the server."""
|
"""Invoke the API on the server."""
|
||||||
if na_element and not isinstance(na_element, NaElement):
|
if na_element and not isinstance(na_element, NaElement):
|
||||||
ValueError('NaElement must be supplied to invoke API')
|
ValueError('NaElement must be supplied to invoke API')
|
||||||
|
|
||||||
request, request_element = self._create_request(na_element,
|
request, request_element = self._create_request(na_element,
|
||||||
enable_tunneling)
|
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))
|
LOG.debug("Request: %s", request_element.to_string(pretty=True))
|
||||||
|
|
||||||
if (not hasattr(self, '_opener') or not self._opener
|
if (not hasattr(self, '_opener') or not self._opener
|
||||||
@@ -246,7 +248,7 @@ class NaServer(object):
|
|||||||
response_xml = response.read()
|
response_xml = response.read()
|
||||||
response_element = self._get_result(response_xml)
|
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))
|
LOG.debug("Response: %s", response_element.to_string(pretty=True))
|
||||||
|
|
||||||
return response_element
|
return response_element
|
||||||
|
@@ -32,7 +32,9 @@ class NetAppBaseClient(object):
|
|||||||
port=kwargs['port'],
|
port=kwargs['port'],
|
||||||
username=kwargs['username'],
|
username=kwargs['username'],
|
||||||
password=kwargs['password'],
|
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):
|
def get_ontapi_version(self, cached=True):
|
||||||
"""Gets the supported ontapi version."""
|
"""Gets the supported ontapi version."""
|
||||||
|
@@ -117,7 +117,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
|
|
||||||
self._app_version = kwargs.get('app_version', 'unknown')
|
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(
|
self._backend_name = self.configuration.safe_get(
|
||||||
'share_backend_name') or driver_name
|
'share_backend_name') or driver_name
|
||||||
|
|
||||||
@@ -150,7 +151,8 @@ class NetAppCmodeFileStorageLibrary(object):
|
|||||||
hostname=self.configuration.netapp_server_hostname,
|
hostname=self.configuration.netapp_server_hostname,
|
||||||
port=self.configuration.netapp_server_port,
|
port=self.configuration.netapp_server_port,
|
||||||
vserver=vserver,
|
vserver=vserver,
|
||||||
trace=na_utils.TRACE_API)
|
trace=na_utils.TRACE_API,
|
||||||
|
api_trace_pattern=na_utils.API_TRACE_PATTERN)
|
||||||
self._clients[vserver] = client
|
self._clients[vserver] = client
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
@@ -115,7 +115,16 @@ netapp_support_opts = [
|
|||||||
cfg.StrOpt('netapp_trace_flags',
|
cfg.StrOpt('netapp_trace_flags',
|
||||||
help=('Comma-separated list of options that control which '
|
help=('Comma-separated list of options that control which '
|
||||||
'trace info is written to the debug logs. Values '
|
'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 = [
|
netapp_data_motion_opts = [
|
||||||
cfg.IntOpt('netapp_snapmirror_quiesce_timeout',
|
cfg.IntOpt('netapp_snapmirror_quiesce_timeout',
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
import collections
|
import collections
|
||||||
import decimal
|
import decimal
|
||||||
import platform
|
import platform
|
||||||
|
import re
|
||||||
|
|
||||||
from oslo_concurrency import processutils as putils
|
from oslo_concurrency import processutils as putils
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@@ -33,6 +34,7 @@ LOG = log.getLogger(__name__)
|
|||||||
VALID_TRACE_FLAGS = ['method', 'api']
|
VALID_TRACE_FLAGS = ['method', 'api']
|
||||||
TRACE_METHOD = False
|
TRACE_METHOD = False
|
||||||
TRACE_API = False
|
TRACE_API = False
|
||||||
|
API_TRACE_PATTERN = '(.*)'
|
||||||
|
|
||||||
|
|
||||||
def validate_driver_instantiation(**kwargs):
|
def validate_driver_instantiation(**kwargs):
|
||||||
@@ -65,16 +67,24 @@ def round_down(value, precision='0.00'):
|
|||||||
decimal.Decimal(precision), rounding=decimal.ROUND_DOWN))
|
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_METHOD
|
||||||
global TRACE_API
|
global TRACE_API
|
||||||
|
global API_TRACE_PATTERN
|
||||||
TRACE_METHOD = False
|
TRACE_METHOD = False
|
||||||
TRACE_API = False
|
TRACE_API = False
|
||||||
|
API_TRACE_PATTERN = api_trace_pattern
|
||||||
if trace_flags_string:
|
if trace_flags_string:
|
||||||
flags = trace_flags_string.split(',')
|
flags = trace_flags_string.split(',')
|
||||||
flags = [flag.strip() for flag in flags]
|
flags = [flag.strip() for flag in flags]
|
||||||
for invalid_flag in list(set(flags) - set(VALID_TRACE_FLAGS)):
|
for invalid_flag in list(set(flags) - set(VALID_TRACE_FLAGS)):
|
||||||
LOG.warning('Invalid trace flag: %s', invalid_flag)
|
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_METHOD = 'method' in flags
|
||||||
TRACE_API = 'api' in flags
|
TRACE_API = 'api' in flags
|
||||||
|
|
||||||
|
@@ -24,7 +24,8 @@ CONNECTION_INFO = {
|
|||||||
'transport_type': 'https',
|
'transport_type': 'https',
|
||||||
'port': 443,
|
'port': 443,
|
||||||
'username': 'admin',
|
'username': 'admin',
|
||||||
'password': 'passw0rd'
|
'password': 'passw0rd',
|
||||||
|
'api_trace_pattern': '(.*)',
|
||||||
}
|
}
|
||||||
|
|
||||||
CLUSTER_NAME = 'fake_cluster'
|
CLUSTER_NAME = 'fake_cluster'
|
||||||
@@ -2395,7 +2396,7 @@ QOS_POLICY_GROUP_GET_ITER_RESPONSE = etree.XML("""
|
|||||||
'max_througput': QOS_MAX_THROUGHPUT,
|
'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>
|
<name>open123</name>
|
||||||
<state>online</state>
|
<state>online</state>
|
||||||
<size-total>0</size-total>
|
<size-total>0</size-total>
|
||||||
|
@@ -220,10 +220,20 @@ class NetAppApiServerTests(test.TestCase):
|
|||||||
na_element)
|
na_element)
|
||||||
self.assertEqual('unknown', exception.code)
|
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"""
|
"""Tests the method invoke_elem with valid parameters"""
|
||||||
na_element = fake.FAKE_NA_ELEMENT
|
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(
|
self.mock_object(self.root, '_create_request', mock.Mock(
|
||||||
return_value=('abc', fake.FAKE_NA_ELEMENT)))
|
return_value=('abc', fake.FAKE_NA_ELEMENT)))
|
||||||
self.mock_object(api, 'LOG')
|
self.mock_object(api, 'LOG')
|
||||||
@@ -237,4 +247,5 @@ class NetAppApiServerTests(test.TestCase):
|
|||||||
|
|
||||||
self.root.invoke_elem(na_element)
|
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)
|
||||||
|
@@ -83,7 +83,8 @@ CLIENT_KWARGS = {
|
|||||||
'vserver': None,
|
'vserver': None,
|
||||||
'transport_type': 'https',
|
'transport_type': 'https',
|
||||||
'password': 'pass',
|
'password': 'pass',
|
||||||
'port': '443'
|
'port': '443',
|
||||||
|
'api_trace_pattern': '(.*)',
|
||||||
}
|
}
|
||||||
|
|
||||||
SHARE = {
|
SHARE = {
|
||||||
|
@@ -18,6 +18,7 @@ Mock unit tests for the NetApp driver utility module
|
|||||||
|
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
from oslo_concurrency import processutils as putils
|
from oslo_concurrency import processutils as putils
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@@ -28,6 +29,7 @@ from manila import test
|
|||||||
from manila import version
|
from manila import version
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
class NetAppDriverUtilsTestCase(test.TestCase):
|
class NetAppDriverUtilsTestCase(test.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -56,19 +58,22 @@ class NetAppDriverUtilsTestCase(test.TestCase):
|
|||||||
self.assertAlmostEqual(na_utils.round_down(-5.567, '0'), -5)
|
self.assertAlmostEqual(na_utils.round_down(-5.567, '0'), -5)
|
||||||
|
|
||||||
def test_setup_tracing(self):
|
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_API)
|
||||||
self.assertFalse(na_utils.TRACE_METHOD)
|
self.assertFalse(na_utils.TRACE_METHOD)
|
||||||
|
self.assertEqual('(.*)', na_utils.API_TRACE_PATTERN)
|
||||||
self.assertEqual(0, na_utils.LOG.warning.call_count)
|
self.assertEqual(0, na_utils.LOG.warning.call_count)
|
||||||
|
|
||||||
na_utils.setup_tracing('method')
|
na_utils.setup_tracing('method')
|
||||||
self.assertFalse(na_utils.TRACE_API)
|
self.assertFalse(na_utils.TRACE_API)
|
||||||
self.assertTrue(na_utils.TRACE_METHOD)
|
self.assertTrue(na_utils.TRACE_METHOD)
|
||||||
|
self.assertEqual('(.*)', na_utils.API_TRACE_PATTERN)
|
||||||
self.assertEqual(0, na_utils.LOG.warning.call_count)
|
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_API)
|
||||||
self.assertTrue(na_utils.TRACE_METHOD)
|
self.assertTrue(na_utils.TRACE_METHOD)
|
||||||
|
self.assertEqual('(^fancy-api$)', na_utils.API_TRACE_PATTERN)
|
||||||
self.assertEqual(0, na_utils.LOG.warning.call_count)
|
self.assertEqual(0, na_utils.LOG.warning.call_count)
|
||||||
|
|
||||||
def test_setup_tracing_invalid_key(self):
|
def test_setup_tracing_invalid_key(self):
|
||||||
@@ -78,6 +83,12 @@ class NetAppDriverUtilsTestCase(test.TestCase):
|
|||||||
self.assertTrue(na_utils.TRACE_METHOD)
|
self.assertTrue(na_utils.TRACE_METHOD)
|
||||||
self.assertEqual(1, na_utils.LOG.warning.call_count)
|
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
|
@na_utils.trace
|
||||||
def _trace_test_method(*args, **kwargs):
|
def _trace_test_method(*args, **kwargs):
|
||||||
return 'OK'
|
return 'OK'
|
||||||
|
@@ -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.
|
Reference in New Issue
Block a user