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
This commit is contained in:
committed by
Kevin Benton
parent
09fbeb7e2d
commit
50c65d16be
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user