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:
parent
51d769768e
commit
2423d139d3
@ -124,6 +124,10 @@ class RouterBaseDriver(RouterAbstractDriver):
|
|||||||
def get_router_az(self, lrouter):
|
def get_router_az(self, lrouter):
|
||||||
return self.plugin.get_router_az(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):
|
def get_router_az_by_id(self, context, router_id):
|
||||||
lrouter = self.plugin.get_router(context, router_id)
|
lrouter = self.plugin.get_router(context, router_id)
|
||||||
return self.get_router_az(lrouter)
|
return self.get_router_az(lrouter)
|
||||||
|
@ -580,7 +580,8 @@ class RouterSharedDriver(router_driver.RouterBaseDriver):
|
|||||||
conflict_router_ids.extend(new_conflict_router_ids)
|
conflict_router_ids.extend(new_conflict_router_ids)
|
||||||
conflict_router_ids = list(set(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(
|
new = self.edge_manager.bind_router_on_available_edge(
|
||||||
context, router_id, optional_router_ids,
|
context, router_id, optional_router_ids,
|
||||||
conflict_router_ids, conflict_network_ids,
|
conflict_router_ids, conflict_network_ids,
|
||||||
@ -603,6 +604,14 @@ class RouterSharedDriver(router_driver.RouterBaseDriver):
|
|||||||
self.nsx_v, context, router_id, network_id,
|
self.nsx_v, context, router_id, network_id,
|
||||||
address_groups, admin_state)
|
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):
|
def _unbind_router_on_edge(self, context, router_id):
|
||||||
self.edge_manager.reconfigure_shared_edge_metadata_port(
|
self.edge_manager.reconfigure_shared_edge_metadata_port(
|
||||||
context, router_id)
|
context, router_id)
|
||||||
|
@ -2328,6 +2328,12 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
metainfo = {}
|
metainfo = {}
|
||||||
return 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):
|
def _get_router_config_from_flavor(self, context, router):
|
||||||
"""Validate the router flavor and initialize router data
|
"""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')):
|
if not validators.is_attr_set(router.get('flavor_id')):
|
||||||
return
|
return
|
||||||
flavor_id = router['flavor_id']
|
metainfo = self.get_flavor_metainfo(context, router['flavor_id'])
|
||||||
flavor_profile = self._get_router_flavor_profile(context, flavor_id)
|
|
||||||
metainfo = self._get_flavor_metainfo_from_profile(flavor_id,
|
|
||||||
flavor_profile)
|
|
||||||
|
|
||||||
# Go over the attributes of the metainfo
|
# Go over the attributes of the metainfo
|
||||||
allowed_keys = [ROUTER_SIZE, 'router_type', 'distributed',
|
allowed_keys = [ROUTER_SIZE, 'router_type', 'distributed',
|
||||||
az_ext.AZ_HINTS]
|
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():
|
for k, v in metainfo.items():
|
||||||
if k in allowed_keys:
|
if k in allowed_keys:
|
||||||
#special case for availability zones hints which are an array
|
#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)
|
raise n_exc.BadRequest(resource="router", msg=msg)
|
||||||
# Legal value
|
# Legal value
|
||||||
router[k] = v
|
router[k] = v
|
||||||
|
elif k in future_use_keys:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
LOG.warning(_LW("Skipping router flavor metainfo [%(k)s:%(v)s]"
|
LOG.warning(_LW("Skipping router flavor metainfo [%(k)s:%(v)s]"
|
||||||
":unsupported field"),
|
":unsupported field"),
|
||||||
|
@ -795,3 +795,28 @@ class EdgeApplianceDriver(object):
|
|||||||
'featureType': "highavailability_4.0",
|
'featureType': "highavailability_4.0",
|
||||||
'enabled': True}
|
'enabled': True}
|
||||||
self.vcns.enable_ha(edge_id, ha_request)
|
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)
|
||||||
|
@ -34,6 +34,7 @@ from neutron import context as q_context
|
|||||||
from neutron.extensions import l3
|
from neutron.extensions import l3
|
||||||
from neutron.plugins.common import constants as plugin_const
|
from neutron.plugins.common import constants as plugin_const
|
||||||
|
|
||||||
|
from neutron_lib.api import validators
|
||||||
from neutron_lib import exceptions as n_exc
|
from neutron_lib import exceptions as n_exc
|
||||||
|
|
||||||
from vmware_nsx._i18n import _, _LE, _LW
|
from vmware_nsx._i18n import _, _LE, _LW
|
||||||
@ -221,9 +222,8 @@ class EdgeManager(object):
|
|||||||
"""Create an edge for logical router support."""
|
"""Create an edge for logical router support."""
|
||||||
if context is None:
|
if context is None:
|
||||||
context = q_context.get_admin_context()
|
context = q_context.get_admin_context()
|
||||||
|
|
||||||
# deploy edge
|
# deploy edge
|
||||||
self.nsxv_manager.deploy_edge(context, lrouter['id'],
|
return self.nsxv_manager.deploy_edge(context, lrouter['id'],
|
||||||
lrouter['name'], internal_network=None,
|
lrouter['name'], internal_network=None,
|
||||||
appliance_size=appliance_size,
|
appliance_size=appliance_size,
|
||||||
dist=(edge_type == nsxv_constants.VDR_EDGE),
|
dist=(edge_type == nsxv_constants.VDR_EDGE),
|
||||||
@ -618,11 +618,10 @@ class EdgeManager(object):
|
|||||||
appliance_size=appliance_size,
|
appliance_size=appliance_size,
|
||||||
edge_type=edge_type,
|
edge_type=edge_type,
|
||||||
availability_zone=availability_zone.name)
|
availability_zone=availability_zone.name)
|
||||||
self._deploy_edge(context, lrouter,
|
return self._deploy_edge(context, lrouter,
|
||||||
appliance_size=appliance_size,
|
appliance_size=appliance_size,
|
||||||
edge_type=edge_type,
|
edge_type=edge_type,
|
||||||
availability_zone=availability_zone)
|
availability_zone=availability_zone)
|
||||||
return
|
|
||||||
|
|
||||||
with locking.LockManager.get_lock('nsx-edge-request'):
|
with locking.LockManager.get_lock('nsx-edge-request'):
|
||||||
self._clean_all_error_edge_bindings(
|
self._clean_all_error_edge_bindings(
|
||||||
@ -645,7 +644,7 @@ class EdgeManager(object):
|
|||||||
appliance_size=appliance_size,
|
appliance_size=appliance_size,
|
||||||
edge_type=edge_type,
|
edge_type=edge_type,
|
||||||
availability_zone=availability_zone.name)
|
availability_zone=availability_zone.name)
|
||||||
self._deploy_edge(context, lrouter,
|
edge_id = self._deploy_edge(context, lrouter,
|
||||||
appliance_size=appliance_size,
|
appliance_size=appliance_size,
|
||||||
edge_type=edge_type,
|
edge_type=edge_type,
|
||||||
availability_zone=availability_zone)
|
availability_zone=availability_zone)
|
||||||
@ -689,6 +688,8 @@ class EdgeManager(object):
|
|||||||
appliance_size=appliance_size, edge_type=edge_type,
|
appliance_size=appliance_size, edge_type=edge_type,
|
||||||
availability_zone=availability_zone)
|
availability_zone=availability_zone)
|
||||||
|
|
||||||
|
return edge_id
|
||||||
|
|
||||||
def _free_edge_appliance(self, context, router_id):
|
def _free_edge_appliance(self, context, router_id):
|
||||||
"""Try to collect one edge to pool."""
|
"""Try to collect one edge to pool."""
|
||||||
binding = nsxv_db.get_nsxv_router_binding(context.session, router_id)
|
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_name[:nsxv_constants.ROUTER_NAME_LENGTH - len(router_id)] +
|
||||||
'-' + 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(
|
def create_lrouter(
|
||||||
self, context, lrouter, lswitch=None, dist=False,
|
self, context, lrouter, lswitch=None, dist=False,
|
||||||
appliance_size=vcns_const.SERVICE_SIZE_MAPPING['router'],
|
appliance_size=vcns_const.SERVICE_SIZE_MAPPING['router'],
|
||||||
availability_zone=None):
|
availability_zone=None):
|
||||||
"""Create an edge for logical router support."""
|
"""Create an edge for logical router support."""
|
||||||
router_name = self._build_lrouter_name(lrouter['id'], lrouter['name'])
|
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,
|
context, lrouter['id'], router_name,
|
||||||
appliance_size=appliance_size,
|
appliance_size=appliance_size,
|
||||||
dist=dist, availability_zone=availability_zone)
|
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):
|
def delete_lrouter(self, context, router_id, dist=False):
|
||||||
self._free_edge_appliance(context, router_id)
|
self._free_edge_appliance(context, router_id)
|
||||||
|
|
||||||
@ -1882,6 +1897,13 @@ class EdgeManager(object):
|
|||||||
"for net %s not found at the backend"),
|
"for net %s not found at the backend"),
|
||||||
network_id)
|
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,
|
def create_lrouter(nsxv_manager, context, lrouter, lswitch=None, dist=False,
|
||||||
availability_zone=None):
|
availability_zone=None):
|
||||||
|
@ -4795,12 +4795,43 @@ class TestRouterFlavorTestCase(extension.ExtensionTestCase,
|
|||||||
self.plugin._flv_plugin = directory.get_plugin(plugin_const.FLAVORS)
|
self.plugin._flv_plugin = directory.get_plugin(plugin_const.FLAVORS)
|
||||||
self.plugin._process_router_flavor_create = mock.Mock()
|
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
|
# init the availability zones
|
||||||
self.az_name = 'az7'
|
self.az_name = 'az7'
|
||||||
az_config = self.az_name + ':respool-7:datastore-7:True'
|
az_config = self.az_name + ':respool-7:datastore-7:True'
|
||||||
cfg.CONF.set_override('availability_zones', [az_config], group="nsxv")
|
cfg.CONF.set_override('availability_zones', [az_config], group="nsxv")
|
||||||
self.plugin._availability_zones_data = (
|
self.plugin._availability_zones_data = (
|
||||||
nsx_az.ConfiguredAvailabilityZones())
|
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(
|
def _test_router_create_with_flavor(
|
||||||
self, metainfo, expected_data,
|
self, metainfo, expected_data,
|
||||||
@ -4810,7 +4841,7 @@ class TestRouterFlavorTestCase(extension.ExtensionTestCase,
|
|||||||
|
|
||||||
router_data = {'flavor_id': 'dummy',
|
router_data = {'flavor_id': 'dummy',
|
||||||
'tenant_id': 'whatever',
|
'tenant_id': 'whatever',
|
||||||
'name': 'test_router',
|
'name': 'router_with_flavor',
|
||||||
'admin_state_up': True}
|
'admin_state_up': True}
|
||||||
|
|
||||||
if create_type is not None:
|
if create_type is not None:
|
||||||
@ -4832,6 +4863,12 @@ class TestRouterFlavorTestCase(extension.ExtensionTestCase,
|
|||||||
router = self.plugin.create_router(
|
router = self.plugin.create_router(
|
||||||
context.get_admin_context(),
|
context.get_admin_context(),
|
||||||
{'router': router_data})
|
{'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():
|
for key, expected_val in expected_data.items():
|
||||||
self.assertEqual(expected_val, router[key])
|
self.assertEqual(expected_val, router[key])
|
||||||
|
|
||||||
@ -4899,6 +4936,55 @@ class TestRouterFlavorTestCase(extension.ExtensionTestCase,
|
|||||||
self._test_router_create_with_flavor(
|
self._test_router_create_with_flavor(
|
||||||
metainfo, expected_router)
|
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(
|
def _test_router_create_with_flavor_error(
|
||||||
self, metainfo, error_code,
|
self, metainfo, error_code,
|
||||||
create_type=None,
|
create_type=None,
|
||||||
|
@ -1170,16 +1170,20 @@ class FakeVcns(object):
|
|||||||
return (header, response)
|
return (header, response)
|
||||||
|
|
||||||
def get_edge_syslog(self, edge_id):
|
def get_edge_syslog(self, edge_id):
|
||||||
header = {
|
if ('syslog' not in self._edges.get(edge_id)):
|
||||||
'status': 200,
|
header = {
|
||||||
}
|
'status': 400
|
||||||
response = {
|
}
|
||||||
'protocol': 'tcp',
|
response = {}
|
||||||
'serverAddresses': {'ipAddress': ['1.1.1.1']}
|
else:
|
||||||
}
|
header = {
|
||||||
|
'status': 200
|
||||||
|
}
|
||||||
|
response = self._edges.get(edge_id)['syslog']
|
||||||
return (header, response)
|
return (header, response)
|
||||||
|
|
||||||
def update_edge_syslog(self, edge_id, config):
|
def update_edge_syslog(self, edge_id, config):
|
||||||
|
self._edges[edge_id]['syslog'] = config
|
||||||
header = {
|
header = {
|
||||||
'status': 204
|
'status': 204
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user