Merge "NSX|V router flavor support"
This commit is contained in:
commit
1b0ede9f2d
@ -23,6 +23,7 @@ from neutron_lib import constants
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import uuidutils
|
||||
from sqlalchemy.orm import exc as sa_exc
|
||||
@ -56,15 +57,18 @@ from neutron.db import securitygroups_db
|
||||
from neutron.extensions import allowedaddresspairs as addr_pair
|
||||
from neutron.extensions import availability_zone as az_ext
|
||||
from neutron.extensions import external_net as ext_net_extn
|
||||
from neutron.extensions import flavors
|
||||
from neutron.extensions import l3
|
||||
from neutron.extensions import multiprovidernet as mpnet
|
||||
from neutron.extensions import portbindings as pbin
|
||||
from neutron.extensions import portsecurity as psec
|
||||
from neutron.extensions import providernet as pnet
|
||||
from neutron.extensions import securitygroup as ext_sg
|
||||
from neutron import manager
|
||||
from neutron.plugins.common import constants as plugin_const
|
||||
from neutron.plugins.common import utils
|
||||
from neutron.quota import resource_registry
|
||||
from neutron.services.flavors import flavors_plugin
|
||||
from neutron.services.qos import qos_consts
|
||||
from vmware_nsx.dvs import dvs
|
||||
from vmware_nsx.services.qos.common import utils as qos_com_utils
|
||||
@ -154,7 +158,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
"subnet_allocation",
|
||||
"availability_zone",
|
||||
"network_availability_zone",
|
||||
"router_availability_zone"]
|
||||
"router_availability_zone",
|
||||
"l3-flavors", "flavors"]
|
||||
|
||||
supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
|
||||
qos_consts.RULE_TYPE_DSCP_MARKING]
|
||||
@ -2218,8 +2223,98 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
if r.get('router_type') == nsxv_constants.EXCLUSIVE:
|
||||
r[ROUTER_SIZE] = cfg.CONF.nsxv.exclusive_router_appliance_size
|
||||
|
||||
def _get_router_flavor_profile(self, context, flavor_id):
|
||||
flv_plugin = manager.NeutronManager.get_service_plugins().get(
|
||||
plugin_const.FLAVORS)
|
||||
if not flv_plugin:
|
||||
msg = _("Flavors plugin not found")
|
||||
raise n_exc.BadRequest(resource="router", msg=msg)
|
||||
|
||||
# Will raise FlavorNotFound if doesn't exist
|
||||
fl_db = flavors_plugin.FlavorsPlugin.get_flavor(
|
||||
flv_plugin, context, flavor_id)
|
||||
|
||||
if fl_db['service_type'] != plugin_const.L3_ROUTER_NAT:
|
||||
raise flavors.InvalidFlavorServiceType(
|
||||
service_type=fl_db['service_type'])
|
||||
|
||||
if not fl_db['enabled']:
|
||||
raise flavors.FlavorDisabled()
|
||||
|
||||
# get the profile (Currently only 1 is supported, so take the first)
|
||||
if not fl_db['service_profiles']:
|
||||
return
|
||||
profile_id = fl_db['service_profiles'][0]
|
||||
|
||||
return flavors_plugin.FlavorsPlugin.get_service_profile(
|
||||
flv_plugin,
|
||||
context,
|
||||
profile_id)
|
||||
|
||||
def _get_flavor_metainfo_from_profile(self, flavor_id, flavor_profile):
|
||||
if not flavor_profile:
|
||||
return {}
|
||||
metainfo_string = flavor_profile.get('metainfo').replace("'", "\"")
|
||||
try:
|
||||
metainfo = jsonutils.loads(metainfo_string)
|
||||
if not isinstance(metainfo, dict):
|
||||
LOG.warning(_LW("Skipping router flavor %(flavor)s metainfo "
|
||||
"[%(metainfo)s]: expected a dictionary"),
|
||||
{'flavor': flavor_id,
|
||||
'metainfo': metainfo_string})
|
||||
metainfo = {}
|
||||
except ValueError as e:
|
||||
LOG.warning(_LW("Error reading router flavor %(flavor)s metainfo "
|
||||
"[%(metainfo)s]: %(error)s"),
|
||||
{'flavor': flavor_id,
|
||||
'metainfo': metainfo_string,
|
||||
'error': e})
|
||||
metainfo = {}
|
||||
return metainfo
|
||||
|
||||
def _get_router_config_from_flavor(self, context, router):
|
||||
"""Validate the router flavor and initialize router data
|
||||
|
||||
Validate that the flavor is legit, and that contradicting configuration
|
||||
does not exist.
|
||||
Also update the router data to reflect the selected flavor.
|
||||
"""
|
||||
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)
|
||||
|
||||
# Go over the attributes of the metainfo
|
||||
allowed_keys = [ROUTER_SIZE, 'router_type', 'distributed',
|
||||
az_ext.AZ_HINTS]
|
||||
for k, v in metainfo.items():
|
||||
if k in allowed_keys:
|
||||
#special case for availability zones hints which are an array
|
||||
if k == az_ext.AZ_HINTS:
|
||||
if not isinstance(v, list):
|
||||
v = [v]
|
||||
# The default az hists is an empty array
|
||||
if (validators.is_attr_set(router.get(k)) and
|
||||
len(router[k]) > 0):
|
||||
msg = (_("Cannot specify %s if the flavor profile "
|
||||
"defines it") % k)
|
||||
raise n_exc.BadRequest(resource="router", msg=msg)
|
||||
|
||||
elif validators.is_attr_set(router.get(k)) and router[k] != v:
|
||||
msg = _("Cannot specify %s if the flavor defines it") % k
|
||||
raise n_exc.BadRequest(resource="router", msg=msg)
|
||||
# Legal value
|
||||
router[k] = v
|
||||
else:
|
||||
LOG.warning(_LW("Skipping router flavor metainfo [%(k)s:%(v)s]"
|
||||
":unsupported field"),
|
||||
{'k': k, 'v': v})
|
||||
|
||||
def create_router(self, context, router, allow_metadata=True):
|
||||
r = router['router']
|
||||
self._get_router_config_from_flavor(context, r)
|
||||
self._decide_router_type(context, r)
|
||||
self._validate_router_size(router)
|
||||
self._validate_availability_zones_in_obj(context, 'router', r)
|
||||
@ -2234,6 +2329,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
router_db = self._get_router(context, lrouter['id'])
|
||||
self._process_extra_attr_router_create(context, router_db, r)
|
||||
self._process_nsx_router_create(context, router_db, r)
|
||||
self._process_router_flavor_create(context, router_db, r)
|
||||
|
||||
lrouter = super(NsxVPluginV2, self).get_router(context,
|
||||
lrouter['id'])
|
||||
@ -2388,6 +2484,18 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
context, edge_id)]
|
||||
return []
|
||||
|
||||
def _process_router_flavor_create(self, context, router_db, r):
|
||||
"""Update the router DB structure with the flavor ID upon creation
|
||||
"""
|
||||
if validators.is_attr_set(r.get('flavor_id')):
|
||||
router_db.flavor_id = r['flavor_id']
|
||||
|
||||
def add_flavor_id(plugin, router_res, router_db):
|
||||
router_res['flavor_id'] = router_db['flavor_id']
|
||||
|
||||
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
|
||||
l3.ROUTERS, [add_flavor_id])
|
||||
|
||||
def get_router(self, context, id, fields=None):
|
||||
router = super(NsxVPluginV2, self).get_router(context, id, fields)
|
||||
if router.get("distributed") and 'router_type' in router:
|
||||
|
@ -26,6 +26,7 @@ from neutron.extensions import dvr as dist_router
|
||||
from neutron.extensions import external_net
|
||||
from neutron.extensions import l3
|
||||
from neutron.extensions import l3_ext_gw_mode
|
||||
from neutron.extensions import l3_flavors
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.extensions import providernet as pnet
|
||||
from neutron.extensions import router_availability_zone
|
||||
@ -37,6 +38,7 @@ from neutron.services.qos import qos_consts
|
||||
from neutron.tests.unit import _test_extension_portbindings as test_bindings
|
||||
import neutron.tests.unit.db.test_allowedaddresspairs_db as test_addr_pair
|
||||
import neutron.tests.unit.db.test_db_base_plugin_v2 as test_plugin
|
||||
from neutron.tests.unit.extensions import base as extension
|
||||
import neutron.tests.unit.extensions.test_l3 as test_l3_plugin
|
||||
import neutron.tests.unit.extensions.test_l3_ext_gw_mode as test_ext_gw_mode
|
||||
import neutron.tests.unit.extensions.test_portsecurity as test_psec
|
||||
@ -138,8 +140,16 @@ class NsxVPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
|
||||
self.default_res_pool = 'respool-28'
|
||||
cfg.CONF.set_override("resource_pool_id", self.default_res_pool,
|
||||
group="nsxv")
|
||||
super(NsxVPluginV2TestCase, self).setUp(plugin=plugin,
|
||||
ext_mgr=ext_mgr)
|
||||
if service_plugins is not None:
|
||||
# override the service plugins only if specified directly
|
||||
super(NsxVPluginV2TestCase, self).setUp(
|
||||
plugin=plugin,
|
||||
service_plugins=service_plugins,
|
||||
ext_mgr=ext_mgr)
|
||||
else:
|
||||
super(NsxVPluginV2TestCase, self).setUp(
|
||||
plugin=plugin,
|
||||
ext_mgr=ext_mgr)
|
||||
self.addCleanup(self.fc2.reset_all)
|
||||
plugin_instance = manager.NeutronManager.get_plugin()
|
||||
plugin_instance.real_get_edge = plugin_instance._get_edge_id_by_rtr_id
|
||||
@ -1770,6 +1780,8 @@ class TestL3ExtensionManager(object):
|
||||
router_size.EXTENDED_ATTRIBUTES_2_0.get(key, {}))
|
||||
l3.RESOURCE_ATTRIBUTE_MAP[key].update(
|
||||
router_availability_zone.EXTENDED_ATTRIBUTES_2_0.get(key, {}))
|
||||
l3.RESOURCE_ATTRIBUTE_MAP[key].update(
|
||||
l3_flavors.EXTENDED_ATTRIBUTES_2_0.get(key, {}))
|
||||
# Finally add l3 resources to the global attribute map
|
||||
attributes.RESOURCE_ATTRIBUTE_MAP.update(
|
||||
l3.RESOURCE_ATTRIBUTE_MAP)
|
||||
@ -4736,3 +4748,177 @@ class TestSharedRouterTestCase(L3NatTest, L3NatTestCaseBase,
|
||||
|
||||
def test_create_rotuer_without_az_hint(self):
|
||||
self._test_create_rotuer_with_az_hint(False)
|
||||
|
||||
|
||||
class TestRouterFlavorTestCase(extension.ExtensionTestCase,
|
||||
test_l3_plugin.L3NatTestCaseMixin,
|
||||
L3NatTest
|
||||
):
|
||||
|
||||
FLAVOR_PLUGIN = 'neutron.services.flavors.flavors_plugin.FlavorsPlugin'
|
||||
|
||||
def setUp(self, plugin=PLUGIN_NAME):
|
||||
# init the core plugin and flavors plugin
|
||||
service_plugins = {plugin_const.FLAVORS: self.FLAVOR_PLUGIN}
|
||||
super(TestRouterFlavorTestCase, self).setUp(
|
||||
plugin=plugin, service_plugins=service_plugins)
|
||||
self.plugin = manager.NeutronManager.get_plugin()
|
||||
self.plugin._flv_plugin = (
|
||||
manager.NeutronManager.get_service_plugins().
|
||||
get(plugin_const.FLAVORS))
|
||||
self.plugin._process_router_flavor_create = mock.Mock()
|
||||
|
||||
# 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())
|
||||
|
||||
def _test_router_create_with_flavor(
|
||||
self, metainfo, expected_data,
|
||||
create_type=None,
|
||||
create_size=None,
|
||||
create_az=None):
|
||||
|
||||
router_data = {'flavor_id': 'dummy',
|
||||
'tenant_id': 'whatever',
|
||||
'name': 'test_router',
|
||||
'admin_state_up': True}
|
||||
|
||||
if create_type is not None:
|
||||
router_data['router_type'] = create_type
|
||||
if create_size is not None:
|
||||
router_data['router_size'] = create_size
|
||||
if create_az is not None:
|
||||
router_data['availability_zone_hints'] = [create_az]
|
||||
|
||||
flavor_data = {'service_type': plugin_const.L3_ROUTER_NAT,
|
||||
'enabled': True,
|
||||
'service_profiles': ['profile_id']}
|
||||
|
||||
# Mock the flavors plugin
|
||||
with mock.patch(self.FLAVOR_PLUGIN + '.get_flavor',
|
||||
return_value=flavor_data):
|
||||
with mock.patch(self.FLAVOR_PLUGIN + '.get_service_profile',
|
||||
return_value={'metainfo': metainfo}):
|
||||
router = self.plugin.create_router(
|
||||
context.get_admin_context(),
|
||||
{'router': router_data})
|
||||
for key, expected_val in expected_data.items():
|
||||
self.assertEqual(expected_val, router[key])
|
||||
|
||||
def test_router_create_with_flavor_different_sizes(self):
|
||||
"""Create exclusive router with size in flavor
|
||||
"""
|
||||
for size in ['compact', 'large', 'xlarge', 'quadlarge']:
|
||||
metainfo = "{'router_size':'%s'}" % size
|
||||
expected_router = {'router_type': 'exclusive',
|
||||
'router_size': size}
|
||||
self._test_router_create_with_flavor(
|
||||
metainfo, expected_router,
|
||||
create_type='exclusive')
|
||||
|
||||
def test_router_create_with_flavor_ex_different_sizes(self):
|
||||
"""Create exclusive router with size and type in flavor
|
||||
"""
|
||||
for size in ['compact', 'large', 'xlarge', 'quadlarge']:
|
||||
metainfo = "{'router_size':'%s','router_type':'exclusive'}" % size
|
||||
expected_router = {'router_type': 'exclusive',
|
||||
'router_size': size}
|
||||
self._test_router_create_with_flavor(
|
||||
metainfo, expected_router)
|
||||
|
||||
def test_router_create_with_flavor_az(self):
|
||||
"""Create exclusive router with availability zone in flavor
|
||||
"""
|
||||
metainfo = "{'availability_zone_hints':'%s'}" % self.az_name
|
||||
expected_router = {'router_type': 'exclusive',
|
||||
'availability_zone_hints': [self.az_name],
|
||||
'distributed': False}
|
||||
self._test_router_create_with_flavor(
|
||||
metainfo, expected_router,
|
||||
create_type='exclusive')
|
||||
|
||||
def test_router_create_with_flavor_shared(self):
|
||||
"""Create shared router with availability zone and type in flavor
|
||||
"""
|
||||
metainfo = ("{'availability_zone_hints':'%s',"
|
||||
"'router_type':'shared'}" % self.az_name)
|
||||
expected_router = {'router_type': 'shared',
|
||||
'availability_zone_hints': [self.az_name],
|
||||
'distributed': False}
|
||||
self._test_router_create_with_flavor(
|
||||
metainfo, expected_router)
|
||||
|
||||
def test_router_create_with_flavor_distributed(self):
|
||||
"""Create distributed router with availability zone and type in flavor
|
||||
"""
|
||||
metainfo = ("{'availability_zone_hints':'%s',"
|
||||
"'distributed':true}" % self.az_name)
|
||||
expected_router = {'distributed': True,
|
||||
'availability_zone_hints': [self.az_name]}
|
||||
self._test_router_create_with_flavor(
|
||||
metainfo, expected_router)
|
||||
|
||||
def test_router_flavor_error_parsing(self):
|
||||
"""Use the wrong format for the flavor metainfo
|
||||
|
||||
It should be ignored, and default values are used
|
||||
"""
|
||||
metainfo = "xxx"
|
||||
expected_router = {'distributed': False,
|
||||
'router_type': 'shared'}
|
||||
self._test_router_create_with_flavor(
|
||||
metainfo, expected_router)
|
||||
|
||||
def _test_router_create_with_flavor_error(
|
||||
self, metainfo, error_code,
|
||||
create_type=None,
|
||||
create_size=None,
|
||||
create_az=None):
|
||||
|
||||
router_data = {'flavor_id': 'dummy',
|
||||
'tenant_id': 'whatever',
|
||||
'name': 'test_router',
|
||||
'admin_state_up': True}
|
||||
|
||||
if create_type is not None:
|
||||
router_data['router_type'] = create_type
|
||||
if create_size is not None:
|
||||
router_data['router_size'] = create_size
|
||||
if create_az is not None:
|
||||
router_data['availability_zone_hints'] = [create_az]
|
||||
|
||||
flavor_data = {'service_type': plugin_const.L3_ROUTER_NAT,
|
||||
'enabled': True,
|
||||
'service_profiles': ['profile_id']}
|
||||
|
||||
# Mock the flavors plugin
|
||||
with mock.patch(self.FLAVOR_PLUGIN + '.get_flavor',
|
||||
return_value=flavor_data):
|
||||
with mock.patch(self.FLAVOR_PLUGIN + '.get_service_profile',
|
||||
return_value={'metainfo': metainfo}):
|
||||
self.assertRaises(error_code,
|
||||
self.plugin.create_router,
|
||||
context.get_admin_context(),
|
||||
{'router': router_data})
|
||||
|
||||
def test_router_flavor_size_conflict(self):
|
||||
metainfo = "{'router_size':'large','router_type':'exclusive'}"
|
||||
self._test_router_create_with_flavor_error(
|
||||
metainfo, n_exc.BadRequest,
|
||||
create_size='compact')
|
||||
|
||||
def test_router_flavor_type_conflict(self):
|
||||
metainfo = "{'router_size':'large','router_type':'exclusive'}"
|
||||
self._test_router_create_with_flavor_error(
|
||||
metainfo, n_exc.BadRequest,
|
||||
create_type='shared')
|
||||
|
||||
def test_router_flavor_az_conflict(self):
|
||||
metainfo = ("{'availability_zone_hints':'%s',"
|
||||
"'distributed':true}" % self.az_name)
|
||||
self._test_router_create_with_flavor_error(
|
||||
metainfo, n_exc.BadRequest,
|
||||
create_az=['az2'])
|
||||
|
Loading…
x
Reference in New Issue
Block a user