Add edge syslog configuration support to router flavors

Router flavor metainfo defines syslog settings in following format:
'syslog':{'server_ip':'<ip>','server2_ip':'<ip>','protocol':'[tcp|udp]'
server2_ip and protocol are optional.
This configuration is not stored on router. Instead, after edge allocation,
flavor metainfo is queried and edge is updated accordingly.
Limitations for shared routers: since several shared routers can share same
edge, each new router association will override syslog settings on the edge.
In addition, due to delayed nature of edge allocation, syslog settings will
reflect flavor metadata at point of port creation (metadata might have changed
since router creation)

Change-Id: I5e38c7391812c5747e9e4475e0bb50f107e872b6
This commit is contained in:
Anna Khmelnitsky 2016-12-01 10:13:42 -08:00
parent 51d769768e
commit 2423d139d3
7 changed files with 177 additions and 19 deletions

View File

@ -124,6 +124,10 @@ class RouterBaseDriver(RouterAbstractDriver):
def get_router_az(self, lrouter):
return self.plugin.get_router_az(lrouter)
def get_router_az_and_flavor_by_id(self, context, router_id):
lrouter = self.plugin.get_router(context, router_id)
return (self.get_router_az(lrouter), lrouter.get('flavor_id'))
def get_router_az_by_id(self, context, router_id):
lrouter = self.plugin.get_router(context, router_id)
return self.get_router_az(lrouter)

View File

@ -580,7 +580,8 @@ class RouterSharedDriver(router_driver.RouterBaseDriver):
conflict_router_ids.extend(new_conflict_router_ids)
conflict_router_ids = list(set(conflict_router_ids))
az = self.get_router_az_by_id(context, router_id)
az, flavor_id = self.get_router_az_and_flavor_by_id(context,
router_id)
new = self.edge_manager.bind_router_on_available_edge(
context, router_id, optional_router_ids,
conflict_router_ids, conflict_network_ids,
@ -603,6 +604,14 @@ class RouterSharedDriver(router_driver.RouterBaseDriver):
self.nsx_v, context, router_id, network_id,
address_groups, admin_state)
if flavor_id:
# if several routers share same edge, they might have
# different flavors with conflicting syslog settings.
# in this case, each new router association will override
# previous syslog settings on the edge
self.edge_manager.update_syslog_by_flavor(context, router_id,
flavor_id, edge_id)
def _unbind_router_on_edge(self, context, router_id):
self.edge_manager.reconfigure_shared_edge_metadata_port(
context, router_id)

View File

@ -2328,6 +2328,12 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
metainfo = {}
return metainfo
def get_flavor_metainfo(self, context, flavor_id):
"""Retrieve metainfo from first profile of specified flavor"""
flavor_profile = self._get_router_flavor_profile(context, flavor_id)
return self._get_flavor_metainfo_from_profile(flavor_id,
flavor_profile)
def _get_router_config_from_flavor(self, context, router):
"""Validate the router flavor and initialize router data
@ -2337,14 +2343,14 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
"""
if not validators.is_attr_set(router.get('flavor_id')):
return
flavor_id = router['flavor_id']
flavor_profile = self._get_router_flavor_profile(context, flavor_id)
metainfo = self._get_flavor_metainfo_from_profile(flavor_id,
flavor_profile)
metainfo = self.get_flavor_metainfo(context, router['flavor_id'])
# Go over the attributes of the metainfo
allowed_keys = [ROUTER_SIZE, 'router_type', 'distributed',
az_ext.AZ_HINTS]
# This info will be used later on
# and is not part of standard router config
future_use_keys = ['syslog']
for k, v in metainfo.items():
if k in allowed_keys:
#special case for availability zones hints which are an array
@ -2363,6 +2369,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
raise n_exc.BadRequest(resource="router", msg=msg)
# Legal value
router[k] = v
elif k in future_use_keys:
pass
else:
LOG.warning(_LW("Skipping router flavor metainfo [%(k)s:%(v)s]"
":unsupported field"),

View File

@ -795,3 +795,28 @@ class EdgeApplianceDriver(object):
'featureType': "highavailability_4.0",
'enabled': True}
self.vcns.enable_ha(edge_id, ha_request)
def update_edge_syslog(self, edge_id, syslog_config, router_id):
if 'server_ip' not in syslog_config:
LOG.warning(_LW("Server IP missing in syslog config for %s"),
router_id)
return
protocol = syslog_config.get('protocol', 'tcp')
if protocol not in ['tcp', 'udp']:
LOG.warning(_LW("Invalid protocol in syslog config for %s"),
router_id)
return
server_ip = syslog_config['server_ip']
request = {'featureType': 'syslog',
'protocol': protocol,
'serverAddresses': {'ipAddress': [server_ip],
'type': 'IpAddressesDto'}}
# edge allows up to 2 syslog servers
if 'server2_ip' in syslog_config:
request['serverAddresses']['ipAddress'].append(
syslog_config['server2_ip'])
self.vcns.update_edge_syslog(edge_id, request)

View File

@ -34,6 +34,7 @@ from neutron import context as q_context
from neutron.extensions import l3
from neutron.plugins.common import constants as plugin_const
from neutron_lib.api import validators
from neutron_lib import exceptions as n_exc
from vmware_nsx._i18n import _, _LE, _LW
@ -221,9 +222,8 @@ class EdgeManager(object):
"""Create an edge for logical router support."""
if context is None:
context = q_context.get_admin_context()
# deploy edge
self.nsxv_manager.deploy_edge(context, lrouter['id'],
return self.nsxv_manager.deploy_edge(context, lrouter['id'],
lrouter['name'], internal_network=None,
appliance_size=appliance_size,
dist=(edge_type == nsxv_constants.VDR_EDGE),
@ -618,11 +618,10 @@ class EdgeManager(object):
appliance_size=appliance_size,
edge_type=edge_type,
availability_zone=availability_zone.name)
self._deploy_edge(context, lrouter,
return self._deploy_edge(context, lrouter,
appliance_size=appliance_size,
edge_type=edge_type,
availability_zone=availability_zone)
return
with locking.LockManager.get_lock('nsx-edge-request'):
self._clean_all_error_edge_bindings(
@ -645,7 +644,7 @@ class EdgeManager(object):
appliance_size=appliance_size,
edge_type=edge_type,
availability_zone=availability_zone.name)
self._deploy_edge(context, lrouter,
edge_id = self._deploy_edge(context, lrouter,
appliance_size=appliance_size,
edge_type=edge_type,
availability_zone=availability_zone)
@ -689,6 +688,8 @@ class EdgeManager(object):
appliance_size=appliance_size, edge_type=edge_type,
availability_zone=availability_zone)
return edge_id
def _free_edge_appliance(self, context, router_id):
"""Try to collect one edge to pool."""
binding = nsxv_db.get_nsxv_router_binding(context.session, router_id)
@ -793,17 +794,31 @@ class EdgeManager(object):
router_name[:nsxv_constants.ROUTER_NAME_LENGTH - len(router_id)] +
'-' + router_id)
def update_syslog_by_flavor(self, context, router_id, flavor_id, edge_id):
"""Update syslog config on edge according to router flavor."""
syslog_config = self._get_syslog_config_from_flavor(context,
router_id,
flavor_id)
if syslog_config:
self.nsxv_manager.update_edge_syslog(edge_id, syslog_config,
router_id)
def create_lrouter(
self, context, lrouter, lswitch=None, dist=False,
appliance_size=vcns_const.SERVICE_SIZE_MAPPING['router'],
availability_zone=None):
"""Create an edge for logical router support."""
router_name = self._build_lrouter_name(lrouter['id'], lrouter['name'])
self._allocate_edge_appliance(
edge_id = self._allocate_edge_appliance(
context, lrouter['id'], router_name,
appliance_size=appliance_size,
dist=dist, availability_zone=availability_zone)
if lrouter.get('flavor_id'):
self.update_syslog_by_flavor(context,
lrouter['id'], lrouter['flavor_id'], edge_id)
def delete_lrouter(self, context, router_id, dist=False):
self._free_edge_appliance(context, router_id)
@ -1882,6 +1897,13 @@ class EdgeManager(object):
"for net %s not found at the backend"),
network_id)
def _get_syslog_config_from_flavor(self, context, router_id, flavor_id):
if not validators.is_attr_set(flavor_id):
return
metainfo = self.plugin.get_flavor_metainfo(context, flavor_id)
return metainfo.get('syslog')
def create_lrouter(nsxv_manager, context, lrouter, lswitch=None, dist=False,
availability_zone=None):

View File

@ -4795,12 +4795,43 @@ class TestRouterFlavorTestCase(extension.ExtensionTestCase,
self.plugin._flv_plugin = directory.get_plugin(plugin_const.FLAVORS)
self.plugin._process_router_flavor_create = mock.Mock()
def mock_add_flavor_id(self, router_res, router_db):
# this function is a registered callback so we can't mock it
# in a regular way.
# need to change behavior for this test suite only, since
# there is no "unregister_dict_extend_funcs"
if router_res['name'] == 'router_with_flavor':
router_res['flavor_id'] = 'raspberry'
self.plugin.register_dict_extend_funcs(
l3.ROUTERS, [mock_add_flavor_id])
# init the availability zones
self.az_name = 'az7'
az_config = self.az_name + ':respool-7:datastore-7:True'
cfg.CONF.set_override('availability_zones', [az_config], group="nsxv")
self.plugin._availability_zones_data = (
nsx_az.ConfiguredAvailabilityZones())
self._iteration = 1
def assertSyslogConfig(self, expected):
"""Verify syslog was updated in fake driver
Test assumes edge ids are created sequentally starting from edge-1
"""
edge_id = ('edge-%s' % self._iteration)
actual = self.plugin.nsx_v.vcns.get_edge_syslog(edge_id)[1]
if not expected:
# test expects no syslog to be configured
self.assertNotIn('serverAddresses', actual)
return
self.assertEqual(expected['protocol'], actual['protocol'])
self.assertEqual(expected['server_ip'],
actual['serverAddresses']['ipAddress'][0])
if 'server2_ip' in expected:
self.assertEqual(expected['server2_ip'],
actual['serverAddresses']['ipAddress'][1])
def _test_router_create_with_flavor(
self, metainfo, expected_data,
@ -4810,7 +4841,7 @@ class TestRouterFlavorTestCase(extension.ExtensionTestCase,
router_data = {'flavor_id': 'dummy',
'tenant_id': 'whatever',
'name': 'test_router',
'name': 'router_with_flavor',
'admin_state_up': True}
if create_type is not None:
@ -4832,6 +4863,12 @@ class TestRouterFlavorTestCase(extension.ExtensionTestCase,
router = self.plugin.create_router(
context.get_admin_context(),
{'router': router_data})
# syslog data is not part of router config
# and needs to be validated separately
if 'syslog' in expected_data.keys():
self.assertSyslogConfig(expected_data['syslog'])
del expected_data['syslog']
for key, expected_val in expected_data.items():
self.assertEqual(expected_val, router[key])
@ -4899,6 +4936,55 @@ class TestRouterFlavorTestCase(extension.ExtensionTestCase,
self._test_router_create_with_flavor(
metainfo, expected_router)
def test_router_create_with_syslog_flavor(self):
"""Create exclusive router with syslog config in flavor"""
# Basic config - server IP only
ip = '1.1.1.10'
expected_router = {'router_type': 'exclusive',
'syslog': {'protocol': 'tcp',
'server_ip': ip}}
metainfo = ("{'router_type':'exclusive',"
"'syslog':{'server_ip':'%s'}}" % ip)
self._iteration = 1
self._test_router_create_with_flavor(
metainfo, expected_router)
# Advanced config - secondary server IP and protocol
ip2 = '1.1.1.11'
for protocol in ['tcp', 'udp']:
expected_router = {'router_type': 'exclusive',
'syslog': {'protocol': protocol,
'server_ip': ip, 'server2_ip': ip2}}
metainfo = ("{'router_type':'exclusive',"
"'syslog':{'server_ip':'%s', 'server2_ip':'%s',"
"'protocol':'%s'}}" % (ip, ip2, protocol))
self._iteration += 1
self._test_router_create_with_flavor(
metainfo, expected_router)
def test_router_create_with_syslog_flavor_error(self):
"""Create router based on flavor with badly formed syslog metadata
Syslog metadata should be ignored
"""
expected_router = {'router_type': 'exclusive',
'syslog': None}
self._iteration = 0
bad_defs = ("'server_ip':'1.1.1.1', 'protocol':'http2'",
"'server2_ip':'2.2.2.2'",
"'protocol':'tcp'")
for meta in bad_defs:
metainfo = "{'router_type':'exclusive', 'syslog': {%s}}" % meta
self._iteration += 1
self._test_router_create_with_flavor(
metainfo, expected_router)
def _test_router_create_with_flavor_error(
self, metainfo, error_code,
create_type=None,

View File

@ -1170,16 +1170,20 @@ class FakeVcns(object):
return (header, response)
def get_edge_syslog(self, edge_id):
header = {
'status': 200,
}
response = {
'protocol': 'tcp',
'serverAddresses': {'ipAddress': ['1.1.1.1']}
}
if ('syslog' not in self._edges.get(edge_id)):
header = {
'status': 400
}
response = {}
else:
header = {
'status': 200
}
response = self._edges.get(edge_id)['syslog']
return (header, response)
def update_edge_syslog(self, edge_id, config):
self._edges[edge_id]['syslog'] = config
header = {
'status': 204
}