Merge "NSX|V router flavor support"

This commit is contained in:
Jenkins 2016-09-19 05:54:34 +00:00 committed by Gerrit Code Review
commit 1b0ede9f2d
2 changed files with 297 additions and 3 deletions

View File

@ -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:

View File

@ -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'])