From 50c65d16bee8e6e46840f232519e92d9ba9989b4 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Tue, 2 Sep 2014 11:27:51 -0700 Subject: [PATCH] BSN: Add context to backend request for debugging Include the request context with calls to the backend Big Switch controllers to assist with event correlation and debugging object provenance. The auth token is stripped since this information is sensitive and these requests will appear in debug logs. This also removes mutable objects from default arguments in some of the server manager function definitions that were interferring with the new use of the headers dict. Closes-Bug: #1364696 Change-Id: I5b80b1596cc145742457b3603cbcd67f6e0d9f36 --- neutron/plugins/bigswitch/plugin.py | 4 +++- neutron/plugins/bigswitch/servermanager.py | 22 ++++++++++++++----- .../ml2/drivers/mech_bigswitch/driver.py | 7 ++++++ .../unit/bigswitch/test_servermanager.py | 19 ++++++++++++++++ .../unit/ml2/drivers/test_bigswitch_mech.py | 11 +++++++++- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/neutron/plugins/bigswitch/plugin.py b/neutron/plugins/bigswitch/plugin.py index cb0fb20bf2..5d119113fc 100644 --- a/neutron/plugins/bigswitch/plugin.py +++ b/neutron/plugins/bigswitch/plugin.py @@ -448,7 +448,9 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2, def put_context_in_serverpool(f): @functools.wraps(f) def wrapper(self, context, *args, **kwargs): - self.servers.set_context(context) + # core plugin: context is top level object + # ml2: keeps context in _plugin_context + self.servers.set_context(getattr(context, '_plugin_context', context)) return f(self, context, *args, **kwargs) return wrapper diff --git a/neutron/plugins/bigswitch/servermanager.py b/neutron/plugins/bigswitch/servermanager.py index e36ec28777..1b072848fd 100644 --- a/neutron/plugins/bigswitch/servermanager.py +++ b/neutron/plugins/bigswitch/servermanager.py @@ -72,6 +72,7 @@ FAILURE_CODES = [0, 301, 302, 303, 400, 401, 403, 404, 500, 501, 502, 503, BASE_URI = '/networkService/v1.1' ORCHESTRATION_SERVICE_ID = 'Neutron v2.0' HASH_MATCH_HEADER = 'X-BSN-BVS-HASH-MATCH' +REQ_CONTEXT_HEADER = 'X-REQ-CONTEXT' # error messages NXNETWORK = 'NXVNS' HTTP_SERVICE_UNAVAILABLE_RETRY_COUNT = 3 @@ -125,12 +126,11 @@ class ServerProxy(object): 'cap': self.capabilities}) return self.capabilities - def rest_call(self, action, resource, data='', headers={}, timeout=False, - reconnect=False, hash_handler=None): + def rest_call(self, action, resource, data='', headers=None, + timeout=False, reconnect=False, hash_handler=None): uri = self.base_uri + resource body = jsonutils.dumps(data) - if not headers: - headers = {} + headers = headers or {} headers['Content-type'] = 'application/json' headers['Accept'] = 'application/json' headers['NeutronProxy-Agent'] = self.name @@ -425,7 +425,15 @@ class ServerPool(object): @utils.synchronized('bsn-rest-call') def rest_call(self, action, resource, data, headers, ignore_codes, timeout=False): - hash_handler = cdb.HashHandler(context=self.get_context_ref()) + context = self.get_context_ref() + if context: + # include the requesting context information if available + cdict = context.to_dict() + # remove the auth token so it's not present in debug logs on the + # backend controller + cdict.pop('auth_token', None) + headers[REQ_CONTEXT_HEADER] = jsonutils.dumps(cdict) + hash_handler = cdb.HashHandler(context=context) good_first = sorted(self.servers, key=lambda x: x.failed) first_response = None for active_server in good_first: @@ -479,13 +487,15 @@ class ServerPool(object): return first_response def rest_action(self, action, resource, data='', errstr='%s', - ignore_codes=[], headers={}, timeout=False): + ignore_codes=None, headers=None, timeout=False): """ Wrapper for rest_call that verifies success and raises a RemoteRestError on failure with a provided error string By default, 404 errors on DELETE calls are ignored because they already do not exist on the backend. """ + ignore_codes = ignore_codes or [] + headers = headers or {} if not ignore_codes and action == 'DELETE': ignore_codes = [404] resp = self.rest_call(action, resource, data, headers, ignore_codes, diff --git a/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py b/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py index dc8c12c339..349d8870bd 100644 --- a/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py +++ b/neutron/plugins/ml2/drivers/mech_bigswitch/driver.py @@ -36,6 +36,7 @@ from neutron.plugins.ml2 import driver_api as api EXTERNAL_PORT_OWNER = 'neutron:external_port' LOG = log.getLogger(__name__) +put_context_in_serverpool = plugin.put_context_in_serverpool # time in seconds to maintain existence of vswitch response CACHE_VSWITCH_TIME = 60 @@ -71,18 +72,22 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base, LOG.debug(_("Initialization done")) + @put_context_in_serverpool def create_network_postcommit(self, context): # create network on the network controller self._send_create_network(context.current) + @put_context_in_serverpool def update_network_postcommit(self, context): # update network on the network controller self._send_update_network(context.current) + @put_context_in_serverpool def delete_network_postcommit(self, context): # delete network on the network controller self._send_delete_network(context.current) + @put_context_in_serverpool def create_port_postcommit(self, context): # create port on the network controller port = self._prepare_port_for_controller(context) @@ -90,6 +95,7 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base, self.async_port_create(port["network"]["tenant_id"], port["network"]["id"], port) + @put_context_in_serverpool def update_port_postcommit(self, context): # update port on the network controller port = self._prepare_port_for_controller(context) @@ -113,6 +119,7 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base, triggered_by_tenant=port["network"]["tenant_id"] ) + @put_context_in_serverpool def delete_port_postcommit(self, context): # delete port on the network controller port = context.current diff --git a/neutron/tests/unit/bigswitch/test_servermanager.py b/neutron/tests/unit/bigswitch/test_servermanager.py index fd575abd17..b30ca83479 100644 --- a/neutron/tests/unit/bigswitch/test_servermanager.py +++ b/neutron/tests/unit/bigswitch/test_servermanager.py @@ -22,8 +22,10 @@ import ssl import mock from oslo.config import cfg +from neutron import context from neutron import manager from neutron.openstack.common import importutils +from neutron.openstack.common import jsonutils from neutron.plugins.bigswitch import servermanager from neutron.tests.unit.bigswitch import test_restproxy_plugin as test_rp @@ -211,6 +213,23 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase): self.assertIn('EXTRA-HEADER', callheaders) self.assertEqual(callheaders['EXTRA-HEADER'], 'HI') + def test_req_context_header(self): + sp = manager.NeutronManager.get_plugin().servers + ncontext = context.Context('uid', 'tid') + sp.set_context(ncontext) + with mock.patch(HTTPCON) as conmock: + rv = conmock.return_value + rv.getresponse.return_value.getheader.return_value = 'HASHHEADER' + sp.rest_action('GET', '/') + callheaders = rv.request.mock_calls[0][1][3] + self.assertIn(servermanager.REQ_CONTEXT_HEADER, callheaders) + ctxdct = ncontext.to_dict() + # auth token is not included + ctxdct.pop('auth_token') + self.assertEqual( + ctxdct, jsonutils.loads( + callheaders[servermanager.REQ_CONTEXT_HEADER])) + def test_capabilities_retrieval(self): sp = servermanager.ServerPool() with mock.patch(HTTPCON) as conmock: diff --git a/neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py b/neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py index f1c844734b..127a5a95be 100644 --- a/neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py +++ b/neutron/tests/unit/ml2/drivers/test_bigswitch_mech.py @@ -35,7 +35,8 @@ from neutron.tests.unit import test_db_plugin PHYS_NET = 'physnet1' VLAN_START = 1000 VLAN_END = 1100 -SERVER_POOL = 'neutron.plugins.bigswitch.servermanager.ServerPool' +SERVER_MANAGER = 'neutron.plugins.bigswitch.servermanager' +SERVER_POOL = SERVER_MANAGER + '.ServerPool' DRIVER_MOD = 'neutron.plugins.ml2.drivers.mech_bigswitch.driver' DRIVER = DRIVER_MOD + '.BigSwitchMechanismDriver' @@ -212,3 +213,11 @@ class TestBigSwitchMechDriverPortsV2(test_db_plugin.TestPortsV2, create_body = rmock.mock_calls[-1][1][2] self.assertIsNotNone(create_body['bound_segment']) self.assertEqual(create_body[portbindings.HOST_ID], ext_id) + + def test_req_context_header_present(self): + with contextlib.nested( + mock.patch(SERVER_MANAGER + '.ServerProxy.rest_call'), + self.port(**{'device_id': 'devid', 'binding:host_id': 'host'}) + ) as (mock_rest, p): + headers = mock_rest.mock_calls[0][1][3] + self.assertIn('X-REQ-CONTEXT', headers)