From: Kevin Benton Date: Tue, 2 Sep 2014 18:27:51 +0000 (-0700) Subject: BSN: Add context to backend request for debugging X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=50c65d16bee8e6e46840f232519e92d9ba9989b4;p=openstack-build%2Fneutron-build.git 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 --- diff --git a/neutron/plugins/bigswitch/plugin.py b/neutron/plugins/bigswitch/plugin.py index cb0fb20bf..5d119113f 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 e36ec2877..1b072848f 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 dc8c12c33..349d8870b 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 fd575abd1..b30ca8347 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 f1c844734..127a5a95b 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)