From b2858f8719f077282acced718b1535afbea79c5b Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Mon, 16 May 2016 11:26:00 +0300 Subject: [PATCH] NSX|V router create with availability zones hints Add support for availability zones hints on routers creation - The router will be created on an edge that belongs to the requested resource pool - The nsxv_router_binding db table has a new column for the edge resource pool - New nsxv configuration: availability_zones which should contain a list of resource pools ids, that can be used as hints DocImpact: New configuration parameter availability_zones under nsxv Change-Id: Ib34689d554dafe25f62a045feebe9eed68d2174d --- devstack/README.rst | 1 + devstack/lib/vmware_nsx_v | 1 + vmware_nsx/common/config.py | 4 + .../alembic_migrations/versions/EXPAND_HEAD | 2 +- ...sxv_add_resource_pool_to_router_mapping.py | 36 +++ vmware_nsx/db/nsxv_db.py | 12 +- vmware_nsx/db/nsxv_models.py | 2 + .../nsx_v/drivers/abstract_router_driver.py | 16 ++ .../drivers/distributed_router_driver.py | 8 +- .../nsx_v/drivers/exclusive_router_driver.py | 4 +- .../nsx_v/drivers/shared_router_driver.py | 6 +- vmware_nsx/plugins/nsx_v/plugin.py | 99 ++++++++- .../nsx_v/vshield/edge_appliance_driver.py | 11 +- .../plugins/nsx_v/vshield/edge_utils.py | 210 +++++++++++------- vmware_nsx/plugins/nsx_v/vshield/vcns.py | 5 + vmware_nsx/tests/unit/nsx_v/test_plugin.py | 126 ++++++++++- .../unit/nsx_v/vshield/test_edge_utils.py | 117 ++++++---- 17 files changed, 531 insertions(+), 129 deletions(-) create mode 100644 vmware_nsx/db/migration/alembic_migrations/versions/newton/expand/c288bb6a7252_nsxv_add_resource_pool_to_router_mapping.py diff --git a/devstack/README.rst b/devstack/README.rst index 78c9202d17..896a3c25f6 100644 --- a/devstack/README.rst +++ b/devstack/README.rst @@ -27,6 +27,7 @@ NSXV_PASSWORD # NSXv password. NSXV_CLUSTER_MOID # clusters ids containing OpenStack hosts. NSXV_DATACENTER_MOID # datacenter id for edge deployment. NSXV_RESOURCE_POOL_ID # resource-pool id for edge deployment. +NSXV_AVAILABILITY_ZONES # alternative resource-pools ids for edge deployment NSXV_DATASTORE_ID # datastore id for edge deployment. NSXV_EXTERNAL_NETWORK # id of logic switch for physical network connectivity. NSXV_VDN_SCOPE_ID # network scope id for VXLAN virtual-wires. diff --git a/devstack/lib/vmware_nsx_v b/devstack/lib/vmware_nsx_v index c147321bb1..7538ef6781 100644 --- a/devstack/lib/vmware_nsx_v +++ b/devstack/lib/vmware_nsx_v @@ -98,6 +98,7 @@ function neutron_plugin_configure_service { _nsxv_ini_set datacenter_moid "$NSXV_DATACENTER_MOID" _nsxv_ini_set datastore_id "$NSXV_DATASTORE_ID" _nsxv_ini_set resource_pool_id "$NSXV_RESOURCE_POOL_ID" + _nsxv_ini_set availability_zones "$NSXV_AVAILABILITY_ZONES" _nsxv_ini_set external_network "$NSXV_EXTERNAL_NETWORK" _nsxv_ini_set cluster_moid "$NSXV_CLUSTER_MOID" _nsxv_ini_set backup_edge_pool "$NSXV_BACKUP_POOL" diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index 507f73743b..84ef400436 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -394,6 +394,10 @@ nsxv_opts = [ deprecated_group="vcns", help=_('Optional parameter identifying the ID of resource to ' 'deploy NSX Edges')), + cfg.ListOpt('availability_zones', + default=[], + help=_('Optional parameter identifying the IDs of alternative ' + 'resources to deploy NSX Edges')), cfg.StrOpt('datastore_id', deprecated_group="vcns", help=_('Optional parameter identifying the ID of datastore to ' diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD index 2c1878b6f1..b8fcfdd2e6 100644 --- a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -b7f41687cbad +c288bb6a7252 diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/newton/expand/c288bb6a7252_nsxv_add_resource_pool_to_router_mapping.py b/vmware_nsx/db/migration/alembic_migrations/versions/newton/expand/c288bb6a7252_nsxv_add_resource_pool_to_router_mapping.py new file mode 100644 index 0000000000..6f2e16c6a8 --- /dev/null +++ b/vmware_nsx/db/migration/alembic_migrations/versions/newton/expand/c288bb6a7252_nsxv_add_resource_pool_to_router_mapping.py @@ -0,0 +1,36 @@ +# Copyright 2016 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""NSXv add resource pool to the router bindings table + +Revision ID: c288bb6a7252 +Revises: b7f41687cbad +Create Date: 2016-05-15 06:12:09.450116 +""" + +# revision identifiers, used by Alembic. +revision = 'c288bb6a7252' +down_revision = 'b7f41687cbad' + +from alembic import op +from oslo_config import cfg +import sqlalchemy as sa + +from vmware_nsx.common import config # noqa + + +def upgrade(): + op.add_column('nsxv_router_bindings', + sa.Column('resource_pool', sa.String(36), nullable=True, + server_default=cfg.CONF.nsxv.resource_pool_id)) diff --git a/vmware_nsx/db/nsxv_db.py b/vmware_nsx/db/nsxv_db.py index f8f66d1674..83b6b44c3d 100644 --- a/vmware_nsx/db/nsxv_db.py +++ b/vmware_nsx/db/nsxv_db.py @@ -53,7 +53,8 @@ def _apply_filters_to_query(query, model, filters, like_filters=None): def add_nsxv_router_binding(session, router_id, vse_id, lswitch_id, status, appliance_size=nsxv_constants.LARGE, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + resource_pool=None): with session.begin(subtransactions=True): binding = nsxv_models.NsxvRouterBinding( router_id=router_id, @@ -61,7 +62,8 @@ def add_nsxv_router_binding(session, router_id, vse_id, lswitch_id, status, lswitch_id=lswitch_id, status=status, appliance_size=appliance_size, - edge_type=edge_type) + edge_type=edge_type, + resource_pool=resource_pool) session.add(binding) return binding @@ -135,6 +137,12 @@ def delete_nsxv_router_binding(session, router_id): session.delete(binding) +def get_edge_resource_pool(session, edge_id): + binding = get_nsxv_router_binding_by_edge(session, edge_id) + if binding: + return binding['resource_pool'] + + def get_edge_vnic_binding(session, edge_id, network_id): return session.query(nsxv_models.NsxvEdgeVnicBinding).filter_by( edge_id=edge_id, network_id=network_id).first() diff --git a/vmware_nsx/db/nsxv_models.py b/vmware_nsx/db/nsxv_models.py index 2d4b432d86..a4233bf34c 100644 --- a/vmware_nsx/db/nsxv_models.py +++ b/vmware_nsx/db/nsxv_models.py @@ -48,6 +48,8 @@ class NsxvRouterBinding(model_base.BASEV2, models_v2.HasStatusDescription): edge_type = sa.Column(sa.Enum(nsxv_constants.SERVICE_EDGE, nsxv_constants.VDR_EDGE, name='nsxv_router_bindings_edge_type')) + resource_pool = sa.Column(sa.String(36), + nullable=True) class NsxvEdgeVnicBinding(model_base.BASEV2): diff --git a/vmware_nsx/plugins/nsx_v/drivers/abstract_router_driver.py b/vmware_nsx/plugins/nsx_v/drivers/abstract_router_driver.py index a17a746528..8820ab0950 100644 --- a/vmware_nsx/plugins/nsx_v/drivers/abstract_router_driver.py +++ b/vmware_nsx/plugins/nsx_v/drivers/abstract_router_driver.py @@ -18,6 +18,7 @@ import six from neutron.db import l3_db from neutron.db import models_v2 +from neutron.extensions import availability_zone as az_ext from vmware_nsx._i18n import _ from vmware_nsx.common import exceptions as nsxv_exc from vmware_nsx.plugins.nsx_v.vshield import edge_utils @@ -120,3 +121,18 @@ class RouterBaseDriver(RouterAbstractDriver): # Also update the nat rules if is_uplink: self.update_nat_rules(context, router, router_id) + + def _get_resource_pool_from_hints_by_id(self, context, router_id): + lrouter = self.plugin.get_router(context, router_id) + return self._get_resource_pool_from_hints(lrouter) + + def _get_resource_pools_from_hints(self, lrouter): + pools = [] + if az_ext.AZ_HINTS in lrouter: + for hint in lrouter[az_ext.AZ_HINTS]: + pools.append(self.plugin.get_res_pool_id_by_name(hint)) + return pools + + def _get_resource_pool_from_hints(self, lrouter): + pools = self._get_resource_pools_from_hints(lrouter) + return pools[0] if len(pools) > 0 else None diff --git a/vmware_nsx/plugins/nsx_v/drivers/distributed_router_driver.py b/vmware_nsx/plugins/nsx_v/drivers/distributed_router_driver.py index a79959f8ec..d5f372f421 100644 --- a/vmware_nsx/plugins/nsx_v/drivers/distributed_router_driver.py +++ b/vmware_nsx/plugins/nsx_v/drivers/distributed_router_driver.py @@ -94,7 +94,9 @@ class RouterDistributedDriver(router_driver.RouterBaseDriver): def create_router(self, context, lrouter, appliance_size=None, allow_metadata=True): - self.edge_manager.create_lrouter(context, lrouter, dist=True) + res_pool = self._get_resource_pool_from_hints(lrouter) + self.edge_manager.create_lrouter(context, lrouter, dist=True, + res_pool=res_pool) def update_router(self, context, router_id, router): r = router['router'] @@ -176,8 +178,10 @@ class RouterDistributedDriver(router_driver.RouterBaseDriver): else: # Connecting plr to the tlr if new_ext_net_id is not None. if not plr_id: + res_pool = self._get_resource_pool_from_hints_by_id( + context, router_id) plr_id = self.edge_manager.create_plr_with_tlr_id( - context, router_id, router.get('name')) + context, router_id, router.get('name'), res_pool=res_pool) if new_ext_net_id != org_ext_net_id and orgnexthop: # network changed, so need to remove default gateway # and all static routes before vnic can be configured diff --git a/vmware_nsx/plugins/nsx_v/drivers/exclusive_router_driver.py b/vmware_nsx/plugins/nsx_v/drivers/exclusive_router_driver.py index 2411688148..40a834d10e 100644 --- a/vmware_nsx/plugins/nsx_v/drivers/exclusive_router_driver.py +++ b/vmware_nsx/plugins/nsx_v/drivers/exclusive_router_driver.py @@ -36,8 +36,10 @@ class RouterExclusiveDriver(router_driver.RouterBaseDriver): def create_router(self, context, lrouter, appliance_size=None, allow_metadata=True): + res_pool = self._get_resource_pool_from_hints(lrouter) self.edge_manager.create_lrouter( - context, lrouter, dist=False, appliance_size=appliance_size) + context, lrouter, dist=False, appliance_size=appliance_size, + res_pool=res_pool) if allow_metadata: self.plugin.metadata_proxy_handler.configure_router_edge( lrouter['id']) diff --git a/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py b/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py index ed4857d968..7be0865268 100644 --- a/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py +++ b/vmware_nsx/plugins/nsx_v/drivers/shared_router_driver.py @@ -568,9 +568,13 @@ class RouterSharedDriver(router_driver.RouterBaseDriver): conflict_router_ids.extend(new_conflict_router_ids) conflict_router_ids = list(set(conflict_router_ids)) + res_pool = self._get_resource_pool_from_hints_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, intf_num) + conflict_router_ids, conflict_network_ids, intf_num, + res_pool) # configure metadata service on the router. metadata_proxy_handler = self.plugin.metadata_proxy_handler if metadata_proxy_handler and new: diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 265aa7ecda..72ba0173f7 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -40,6 +40,7 @@ from neutron.common import rpc as n_rpc from neutron import context as n_context from neutron.db import agents_db from neutron.db import allowedaddresspairs_db as addr_pair_db +from neutron.db.availability_zone import router as router_az_db from neutron.db import db_base_plugin_v2 from neutron.db import dns_db from neutron.db import external_net_db @@ -52,6 +53,7 @@ from neutron.db import portsecurity_db from neutron.db import quota_db # noqa 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 l3 from neutron.extensions import multiprovidernet as mpnet @@ -114,6 +116,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, rt_rtr.RouterType_mixin, external_net_db.External_net_db_mixin, extraroute_db.ExtraRoute_db_mixin, + router_az_db.RouterAvailabilityZoneMixin, l3_gwmode_db.L3_NAT_db_mixin, portbindings_db.PortBindingMixin, portsecurity_db.PortSecurityDbMixin, @@ -143,7 +146,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, "nsxv-router-size", "vnic-index", "advanced-service-providers", - "subnet_allocation"] + "subnet_allocation", + "availability_zone", + "router_availability_zone"] supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, qos_consts.RULE_TYPE_DSCP_MARK] @@ -188,6 +193,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self.nsx_sg_utils = securitygroup_utils.NsxSecurityGroupUtils( self.nsx_v) self._validate_config() + self._build_availability_zones_data() self.sg_container_id = self._create_security_group_container() self.default_section = self._create_cluster_default_fw_section() self._process_security_groups_rules_logging() @@ -709,6 +715,42 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # The vnic-id format which is expected by NSXv return '%s.%03d' % (device_id, port_index) + def _list_availability_zones(self, context, filters=None): + #TODO(asarfaty): We may need to use the filters arg, but now it + # is here only for overriding the original api + result = {} + for az in self._availability_zones_data.keys(): + # Add this availability zone as a router resource + resource = 'router' + key = (az, resource) + result[key] = True + return result + + def _validate_availability_zones_in_obj(self, context, resource_type, + obj_data): + if az_ext.AZ_HINTS in obj_data: + self.validate_availability_zones(context, resource_type, + obj_data[az_ext.AZ_HINTS]) + + def validate_availability_zones(self, context, resource_type, + availability_zones): + """Verify that the availability zones exist, and only 1 hint + was set. + """ + # For now we support only 1 hint per network/router + # TODO(asarfaty): support multiple hints + if len(availability_zones) > 1: + err_msg = _("Can't use multiple availability zone hints") + raise n_exc.InvalidInput(error_message=err_msg) + + # check that all hints appear in the predefined list of availability + # zones + diff = (set(availability_zones) - + set(self._availability_zones_data.keys())) + if diff: + raise az_ext.AvailabilityZoneNotFound( + availability_zone=diff.pop()) + def create_network(self, context, network): net_data = network['network'] tenant_id = net_data['tenant_id'] @@ -1793,14 +1835,20 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, r = router['router'] self._decide_router_type(context, r) self._validate_router_size(router) + self._validate_availability_zones_in_obj(context, 'router', r) + # First extract the gateway info in case of updating # gateway before edge is deployed. # TODO(berlin): admin_state_up and routes update gw_info = self._extract_external_gw(context, router) lrouter = super(NsxVPluginV2, self).create_router(context, router) + with context.session.begin(subtransactions=True): 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) + lrouter = super(NsxVPluginV2, self).get_router(context, + lrouter['id']) try: router_driver = self._get_router_driver(context, router_db) if router_driver.get_type() == nsxv_constants.EXCLUSIVE: @@ -1912,6 +1960,18 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, super(NsxVPluginV2, self).delete_router(context, id) router_driver.delete_router(context, id) + def get_router_availability_zones(self, router): + """Return availability zones which a router belongs to.""" + context = n_context.get_admin_context() + edge_id = self._get_edge_id_by_rtr_id(context, router["id"]) + if edge_id: + resource_pool = nsxv_db.get_edge_resource_pool( + context.session, edge_id) + if resource_pool: + av_zone = self.get_res_pool_name_by_id(resource_pool) + return [av_zone] + return [] + 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: @@ -2722,6 +2782,13 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, ver = self.nsx_v.vcns.get_version() if version.LooseVersion(ver) < version.LooseVersion('6.2.0'): + # Do not support availability zones hints below 6.2 + if (cfg.CONF.nsxv.availability_zones and + len(cfg.CONF.nsxv.availability_zones) > 0): + error = (_("Availability zones are not supported for version " + "%s") % ver) + raise nsx_exc.NsxPluginException(err_msg=error) + LOG.warning(_LW("Skipping validations. Not supported by version.")) return # Validations below only supported by 6.2.0 and above @@ -2733,6 +2800,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, for cluster in cfg.CONF.nsxv.cluster_moid: inventory.append((cluster, 'cluster_moid')) + # Add the availability zones resource pools + if cfg.CONF.nsxv.availability_zones: + for az in cfg.CONF.nsxv.availability_zones: + inventory.append((az, 'availability_zones')) + for moref, field in inventory: if moref and not self.nsx_v.vcns.validate_inventory(moref): error = _("Configured %s not found") % field @@ -2741,6 +2813,31 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, def _handle_qos_notification(self, qos_policy, event_type): qos_utils.handle_qos_notification(qos_policy, event_type, self._dvs) + def _build_availability_zones_data(self): + self._availability_zones_data = {} + if not len(cfg.CONF.nsxv.availability_zones): + return + + # Add the availability zones resource pools + if cfg.CONF.nsxv.availability_zones: + for az in cfg.CONF.nsxv.availability_zones: + name = self.nsx_v.vcns.get_inventory_name(az) + self._availability_zones_data[name] = az + # Add the default resource_pool_id too + az = cfg.CONF.nsxv.resource_pool_id + name = self.nsx_v.vcns.get_inventory_name(az) + self._availability_zones_data[name] = az + + def get_res_pool_id_by_name(self, name): + if name in self._availability_zones_data.keys(): + return self._availability_zones_data[name] + raise az_ext.AvailabilityZoneNotFound(availability_zone=name) + + def get_res_pool_name_by_id(self, res_pool_id): + for name in self._availability_zones_data.keys(): + if res_pool_id == self._availability_zones_data[name]: + return name + # Register the callback def _validate_network_has_subnet(resource, event, trigger, **kwargs): diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py b/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py index 19923e3a81..a3040243f6 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py +++ b/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py @@ -502,14 +502,16 @@ class EdgeApplianceDriver(object): def deploy_edge(self, resource_id, name, internal_network, jobdata=None, dist=False, wait_for_exec=False, loadbalancer_enable=True, - appliance_size=nsxv_constants.LARGE, async=True): + appliance_size=nsxv_constants.LARGE, async=True, + res_pool=None): task_name = 'deploying-%s' % name edge_name = name edge = self._assemble_edge( edge_name, datacenter_moid=self.datacenter_moid, deployment_container_id=self.deployment_container_id, appliance_size=appliance_size, remote_access=False, dist=dist) - appliance = self._assemble_edge_appliance(self.resource_pool_id, + res_pool = res_pool or self.resource_pool_id + appliance = self._assemble_edge_appliance(res_pool, self.datastore_id) if appliance: edge['appliances']['appliances'] = [appliance] @@ -590,7 +592,7 @@ class EdgeApplianceDriver(object): def update_edge(self, router_id, edge_id, name, internal_network, jobdata=None, dist=False, loadbalancer_enable=True, appliance_size=nsxv_constants.LARGE, - set_errors=False): + set_errors=False, res_pool=None): """Update edge name.""" task_name = 'update-%s' % name edge_name = name @@ -599,7 +601,8 @@ class EdgeApplianceDriver(object): deployment_container_id=self.deployment_container_id, appliance_size=appliance_size, remote_access=False, dist=dist) edge['id'] = edge_id - appliance = self._assemble_edge_appliance(self.resource_pool_id, + res_pool = res_pool or self.resource_pool_id + appliance = self._assemble_edge_appliance(res_pool, self.datastore_id) if appliance: edge['appliances']['appliances'] = [appliance] diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py index 7f33ac562f..04ecbeb4ef 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py +++ b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py @@ -101,6 +101,15 @@ def parse_backup_edge_pool_opt(): return edge_pool_dicts +def get_configured_res_pools(): + pools = [] + if cfg.CONF.nsxv.resource_pool_id: + pools.append(cfg.CONF.nsxv.resource_pool_id) + if cfg.CONF.nsxv.availability_zones: + pools.extend(cfg.CONF.nsxv.availability_zones) + return pools + + class EdgeManager(object): """Edge Appliance Management. EdgeManager provides a pool of edge appliances which we can use @@ -114,6 +123,7 @@ class EdgeManager(object): self.edge_pool_dicts = parse_backup_edge_pool_opt() self.nsxv_plugin = nsxv_manager.callbacks.plugin self.plugin = plugin + self._resource_pools = get_configured_res_pools() self.per_interface_rp_filter = self._get_per_edge_rp_filter_state() self.worker_pool = eventlet.GreenPool(WORKER_POOL_SIZE) self._check_backup_edge_pools() @@ -155,7 +165,8 @@ class EdgeManager(object): def _deploy_edge(self, context, lrouter, lswitch=None, appliance_size=nsxv_constants.COMPACT, - edge_type=nsxv_constants.SERVICE_EDGE, async=True): + edge_type=nsxv_constants.SERVICE_EDGE, async=True, + res_pool=None): """Create an edge for logical router support.""" router_id = lrouter['id'] # deploy edge @@ -170,12 +181,14 @@ class EdgeManager(object): lrouter['id'], lrouter['name'], internal_network=None, jobdata=jobdata, wait_for_exec=True, appliance_size=appliance_size, - dist=(edge_type == nsxv_constants.VDR_EDGE), async=async) + dist=(edge_type == nsxv_constants.VDR_EDGE), async=async, + res_pool=res_pool) return task def _deploy_backup_edges_on_db(self, context, num, appliance_size=nsxv_constants.COMPACT, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + res_pool=cfg.CONF.nsxv.resource_pool_id): router_ids = [(vcns_const.BACKUP_ROUTER_PREFIX + _uuid())[:vcns_const.EDGE_NAME_LEN] for i in moves.range(num)] @@ -184,17 +197,20 @@ class EdgeManager(object): nsxv_db.add_nsxv_router_binding( context.session, router_id, None, None, plugin_const.PENDING_CREATE, - appliance_size=appliance_size, edge_type=edge_type) + appliance_size=appliance_size, edge_type=edge_type, + resource_pool=res_pool) return router_ids - def _deploy_backup_edges_at_backend(self, context, router_ids, - appliance_size=nsxv_constants.COMPACT, - edge_type=nsxv_constants.SERVICE_EDGE): - + def _deploy_backup_edges_at_backend( + self, context, router_ids, + appliance_size=nsxv_constants.COMPACT, + edge_type=nsxv_constants.SERVICE_EDGE, + res_pool=cfg.CONF.nsxv.resource_pool_id): eventlet.spawn_n(self._pool_creator, context, router_ids, - appliance_size, edge_type) + appliance_size, edge_type, res_pool) - def _pool_creator(self, context, router_ids, appliance_size, edge_type): + def _pool_creator(self, context, router_ids, appliance_size, + edge_type, res_pool): pool = self.worker_pool for router_id in router_ids: fake_router = { @@ -202,7 +218,8 @@ class EdgeManager(object): 'name': router_id} pool.spawn_n(self._deploy_edge, context, fake_router, appliance_size=appliance_size, - edge_type=edge_type, async=False) + edge_type=edge_type, async=False, + res_pool=res_pool) def _delete_edge(self, context, router_binding): if router_binding['status'] == plugin_const.ERROR: @@ -237,8 +254,10 @@ class EdgeManager(object): binding['router_id'], binding['edge_id'], jobdata=jobdata, dist=(binding['edge_type'] == nsxv_constants.VDR_EDGE)) - def _clean_all_error_edge_bindings(self, context): - filters = {'status': [plugin_const.ERROR]} + def _clean_all_error_edge_bindings( + self, context, + res_pool=cfg.CONF.nsxv.resource_pool_id): + filters = {'status': [plugin_const.ERROR], 'resource_pool': [res_pool]} like_filters = {'router_id': vcns_const.BACKUP_ROUTER_PREFIX + "%"} error_router_bindings = nsxv_db.get_nsxv_router_bindings( context.session, filters=filters, like_filters=like_filters) @@ -250,9 +269,11 @@ class EdgeManager(object): def _get_backup_edge_bindings(self, context, appliance_size=nsxv_constants.COMPACT, edge_type=nsxv_constants.SERVICE_EDGE, - db_update_lock=False): + db_update_lock=False, + res_pool=cfg.CONF.nsxv.resource_pool_id): filters = {'appliance_size': [appliance_size], 'edge_type': [edge_type], + 'resource_pool': [res_pool], 'status': [plugin_const.PENDING_CREATE, plugin_const.ACTIVE]} like_filters = {'router_id': vcns_const.BACKUP_ROUTER_PREFIX + "%"} @@ -261,30 +282,34 @@ class EdgeManager(object): def _check_backup_edge_pools(self): admin_ctx = q_context.get_admin_context() - self._clean_all_error_edge_bindings(admin_ctx) - for edge_type, v in self.edge_pool_dicts.items(): - for edge_size in vcns_const.ALLOWED_EDGE_SIZES: - if edge_size in v.keys(): - edge_pool_range = v[edge_size] - self._check_backup_edge_pool( - edge_pool_range['minimum_pooled_edges'], - edge_pool_range['maximum_pooled_edges'], - appliance_size=edge_size, edge_type=edge_type) - else: - self._check_backup_edge_pool( - 0, 0, - appliance_size=edge_size, edge_type=edge_type) + for res_pool in self._resource_pools: + self._clean_all_error_edge_bindings(admin_ctx, res_pool=res_pool) + for edge_type, v in self.edge_pool_dicts.items(): + for edge_size in vcns_const.ALLOWED_EDGE_SIZES: + if edge_size in v.keys(): + edge_pool_range = v[edge_size] + self._check_backup_edge_pool( + edge_pool_range['minimum_pooled_edges'], + edge_pool_range['maximum_pooled_edges'], + appliance_size=edge_size, edge_type=edge_type, + res_pool=res_pool) + else: + self._check_backup_edge_pool( + 0, 0, + appliance_size=edge_size, edge_type=edge_type, + res_pool=res_pool) def _check_backup_edge_pool(self, minimum_pooled_edges, maximum_pooled_edges, appliance_size=nsxv_constants.COMPACT, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + res_pool=cfg.CONF.nsxv.resource_pool_id): """Check edge pool's status and return one available edge for use.""" admin_ctx = q_context.get_admin_context() backup_router_bindings = self._get_backup_edge_bindings( admin_ctx, appliance_size=appliance_size, edge_type=edge_type, - db_update_lock=True) + db_update_lock=True, res_pool=res_pool) backup_num = len(backup_router_bindings) if backup_num > maximum_pooled_edges: self._delete_backup_edges_on_db( @@ -297,12 +322,12 @@ class EdgeManager(object): router_ids.extend( self._deploy_backup_edges_on_db( admin_ctx, 1, appliance_size=appliance_size, - edge_type=edge_type)) + edge_type=edge_type, res_pool=res_pool)) new_backup_num = len( self._get_backup_edge_bindings( admin_ctx, appliance_size=appliance_size, - edge_type=edge_type, db_update_lock=True)) - + edge_type=edge_type, db_update_lock=True, + res_pool=res_pool)) if backup_num > maximum_pooled_edges: self._delete_backup_edges_at_backend( admin_ctx, @@ -311,7 +336,8 @@ class EdgeManager(object): self._deploy_backup_edges_at_backend(admin_ctx, router_ids, appliance_size=appliance_size, - edge_type=edge_type) + edge_type=edge_type, + res_pool=res_pool) def check_edge_active_at_backend(self, edge_id): try: @@ -322,9 +348,13 @@ class EdgeManager(object): def _get_available_router_binding(self, context, appliance_size=nsxv_constants.COMPACT, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + res_pool=None): + if not res_pool: + res_pool = cfg.CONF.nsxv.resource_pool_id backup_router_bindings = self._get_backup_edge_bindings( - context, appliance_size=appliance_size, edge_type=edge_type) + context, appliance_size=appliance_size, edge_type=edge_type, + res_pool=res_pool) while backup_router_bindings: router_binding = random.choice(backup_router_bindings) if (router_binding['status'] == plugin_const.ACTIVE): @@ -534,9 +564,9 @@ class EdgeManager(object): @vcns.retry_upon_exception(db_base_exc.OperationalError, max_delay=10) def _allocate_edge_appliance(self, context, resource_id, name, appliance_size=nsxv_constants.COMPACT, - dist=False): + dist=False, + res_pool=cfg.CONF.nsxv.resource_pool_id): """Try to allocate one available edge from pool.""" - edge_type = (nsxv_constants.VDR_EDGE if dist else nsxv_constants.SERVICE_EDGE) lrouter = {'id': resource_id, @@ -547,33 +577,38 @@ class EdgeManager(object): context.session, resource_id, None, None, plugin_const.PENDING_CREATE, appliance_size=appliance_size, - edge_type=edge_type) + edge_type=edge_type, + resource_pool=res_pool) self._deploy_edge(context, lrouter, appliance_size=appliance_size, - edge_type=edge_type, async=False) + edge_type=edge_type, async=False, + res_pool=res_pool) return with locking.LockManager.get_lock('nsx-edge-request'): - self._clean_all_error_edge_bindings(context) + self._clean_all_error_edge_bindings(context, res_pool=res_pool) available_router_binding = self._get_available_router_binding( - context, appliance_size=appliance_size, edge_type=edge_type) + context, appliance_size=appliance_size, edge_type=edge_type, + res_pool=res_pool) if available_router_binding: # Update the status from ACTIVE to PENDING_UPDATE # in case of other threads select the same router binding nsxv_db.update_nsxv_router_binding( context.session, available_router_binding['router_id'], status=plugin_const.PENDING_UPDATE) - # Synchronously deploy an edge if no avaliable edge in pool. + # Synchronously deploy an edge if no available edge in pool. if not available_router_binding: # store router-edge mapping binding nsxv_db.add_nsxv_router_binding( context.session, resource_id, None, None, plugin_const.PENDING_CREATE, appliance_size=appliance_size, - edge_type=edge_type) + edge_type=edge_type, + resource_pool=res_pool) self._deploy_edge(context, lrouter, appliance_size=appliance_size, - edge_type=edge_type, async=False) + edge_type=edge_type, async=False, + res_pool=res_pool) else: LOG.debug("Select edge: %(edge_id)s from pool for %(name)s", {'edge_id': available_router_binding['edge_id'], @@ -588,7 +623,8 @@ class EdgeManager(object): None, plugin_const.PENDING_CREATE, appliance_size=appliance_size, - edge_type=edge_type) + edge_type=edge_type, + resource_pool=res_pool) LOG.debug("Select edge: %(edge_id)s from pool for %(name)s", {'edge_id': available_router_binding['edge_id'], 'name': name}) @@ -611,18 +647,20 @@ class EdgeManager(object): task = self.nsxv_manager.update_edge( resource_id, available_router_binding['edge_id'], name, None, appliance_size=appliance_size, dist=dist, - jobdata=jobdata, set_errors=True) + jobdata=jobdata, set_errors=True, res_pool=res_pool) task.wait(task_const.TaskState.RESULT) backup_num = len(self._get_backup_edge_bindings( context, appliance_size=appliance_size, edge_type=edge_type, - db_update_lock=True)) + db_update_lock=True, res_pool=res_pool)) router_ids = self._deploy_backup_edges_on_db( context, edge_pool_range['minimum_pooled_edges'] - backup_num, - appliance_size=appliance_size, edge_type=edge_type) + appliance_size=appliance_size, edge_type=edge_type, + res_pool=res_pool) self._deploy_backup_edges_at_backend( context, router_ids, - appliance_size=appliance_size, edge_type=edge_type) + appliance_size=appliance_size, edge_type=edge_type, + res_pool=res_pool) def _free_edge_appliance(self, context, router_id): """Try to collect one edge to pool.""" @@ -632,10 +670,12 @@ class EdgeManager(object): "not found"), router_id) return dist = (binding['edge_type'] == nsxv_constants.VDR_EDGE) + edge_id = binding['edge_id'] + res_pool = nsxv_db.get_edge_resource_pool(context.session, edge_id) edge_pool_range = self.edge_pool_dicts[binding['edge_type']].get( binding['appliance_size']) if (binding['status'] == plugin_const.ERROR or - not self.check_edge_active_at_backend(binding['edge_id']) or + not self.check_edge_active_at_backend(edge_id) or not edge_pool_range): nsxv_db.update_nsxv_router_binding( context.session, router_id, @@ -646,14 +686,14 @@ class EdgeManager(object): 'router_id': router_id } self.nsxv_manager.delete_edge( - router_id, binding['edge_id'], jobdata=jobdata, dist=dist) + router_id, edge_id, jobdata=jobdata, dist=dist) return with locking.LockManager.get_lock('nsx-edge-request'): - self._clean_all_error_edge_bindings(context) + self._clean_all_error_edge_bindings(context, res_pool=res_pool) backup_router_bindings = self._get_backup_edge_bindings( context, appliance_size=binding['appliance_size'], - edge_type=binding['edge_type']) + edge_type=binding['edge_type'], res_pool=res_pool) backup_num = len(backup_router_bindings) # collect the edge to pool if pool not full if backup_num < edge_pool_range['maximum_pooled_edges']: @@ -664,30 +704,30 @@ class EdgeManager(object): nsxv_db.add_nsxv_router_binding( context.session, backup_router_id, - binding['edge_id'], + edge_id, None, plugin_const.PENDING_UPDATE, appliance_size=binding['appliance_size'], - edge_type=binding['edge_type']) + edge_type=binding['edge_type'], + resource_pool=res_pool) # change edge's name at backend task = self.nsxv_manager.update_edge( - backup_router_id, binding['edge_id'], backup_router_id, None, - appliance_size=binding['appliance_size'], dist=dist) + backup_router_id, edge_id, backup_router_id, None, + appliance_size=binding['appliance_size'], dist=dist, + res_pool=res_pool) task.wait(task_const.TaskState.RESULT) # Clean all edge vnic bindings - nsxv_db.clean_edge_vnic_binding( - context.session, binding['edge_id']) + nsxv_db.clean_edge_vnic_binding(context.session, edge_id) # Refresh edge_vnic_bindings for centralized router - if not dist and binding['edge_id']: - nsxv_db.init_edge_vnic_binding( - context.session, binding['edge_id']) + if not dist and edge_id: + nsxv_db.init_edge_vnic_binding(context.session, edge_id) if task.status == task_const.TaskStatus.COMPLETED: nsxv_db.update_nsxv_router_binding( context.session, backup_router_id, status=plugin_const.ACTIVE) - LOG.debug("Collect edge: %s to pool", binding['edge_id']) + LOG.debug("Collect edge: %s to pool", edge_id) else: nsxv_db.update_nsxv_router_binding( context.session, router_id, @@ -698,7 +738,7 @@ class EdgeManager(object): 'router_id': router_id } self.nsxv_manager.delete_edge( - router_id, binding['edge_id'], jobdata=jobdata, dist=dist) + router_id, edge_id, jobdata=jobdata, dist=dist) def _allocate_dhcp_edge_appliance(self, context, resource_id): resource_name = (vcns_const.DHCP_EDGE_PREFIX + @@ -718,13 +758,16 @@ class EdgeManager(object): def create_lrouter( self, context, lrouter, lswitch=None, dist=False, - appliance_size=vcns_const.SERVICE_SIZE_MAPPING['router']): + appliance_size=vcns_const.SERVICE_SIZE_MAPPING['router'], + res_pool=None): """Create an edge for logical router support.""" + if not res_pool: + res_pool = cfg.CONF.nsxv.resource_pool_id router_name = self._build_lrouter_name(lrouter['id'], lrouter['name']) self._allocate_edge_appliance( context, lrouter['id'], router_name, appliance_size=appliance_size, - dist=dist) + dist=dist, res_pool=res_pool) def delete_lrouter(self, context, router_id, dist=False): self._free_edge_appliance(context, router_id) @@ -907,12 +950,15 @@ class EdgeManager(object): else: return new_id - def _get_available_edges(self, context, network_id, conflicting_nets): + def _get_available_edges(self, context, network_id, conflicting_nets, + res_pool=cfg.CONF.nsxv.resource_pool_id): if conflicting_nets is None: conflicting_nets = [] conflict_edge_ids = [] available_edge_ids = [] - router_bindings = nsxv_db.get_nsxv_router_bindings(context.session) + filters = {'resource_pool': [res_pool]} + router_bindings = nsxv_db.get_nsxv_router_bindings(context.session, + filters=filters) all_dhcp_edges = {binding['router_id']: binding['edge_id'] for binding in router_bindings if (binding['router_id']. startswith(vcns_const.DHCP_EDGE_PREFIX) and @@ -994,7 +1040,8 @@ class EdgeManager(object): nsxv_db.add_nsxv_router_binding( context.session, resource_id, edge_id, None, plugin_const.ACTIVE, - appliance_size=app_size) + appliance_size=app_size, + resource_pool=cfg.CONF.nsxv.resource_pool_id) nsxv_db.allocate_edge_vnic_with_tunnel_index( context.session, edge_id, network_id) @@ -1394,7 +1441,8 @@ class EdgeManager(object): if plr_router_id != router_id: return plr_router_id - def create_plr_with_tlr_id(self, context, router_id, router_name): + def create_plr_with_tlr_id(self, context, router_id, router_name, + res_pool=None): # Add an internal network preparing for connecting the VDR # to a PLR tlr_edge_id = nsxv_db.get_nsxv_router_binding( @@ -1424,7 +1472,7 @@ class EdgeManager(object): # Handle plr relative op plr_router = {'name': router_name, 'id': (vcns_const.PLR_EDGE_PREFIX + _uuid())[:36]} - self.create_lrouter(context, plr_router) + self.create_lrouter(context, plr_router, res_pool=res_pool) binding = nsxv_db.get_nsxv_router_binding( context.session, plr_router['id']) plr_edge_id = binding['edge_id'] @@ -1503,10 +1551,12 @@ class EdgeManager(object): def bind_router_on_available_edge( self, context, target_router_id, optional_router_ids, conflict_router_ids, - conflict_network_ids, network_number): + conflict_network_ids, network_number, resource_pool): """Bind logical router on an available edge. Return True if the logical router is bound to a new edge. """ + if not resource_pool: + resource_pool = cfg.CONF.nsxv.resource_pool_id with locking.LockManager.get_lock('nsx-edge-router'): optional_edge_ids = [] conflict_edge_ids = [] @@ -1514,6 +1564,7 @@ class EdgeManager(object): binding = nsxv_db.get_nsxv_router_binding( context.session, router_id) if (binding and binding.status == plugin_const.ACTIVE and + binding.resource_pool == resource_pool and binding.edge_id not in optional_edge_ids): optional_edge_ids.append(binding.edge_id) @@ -1552,13 +1603,15 @@ class EdgeManager(object): edge_binding.edge_id, None, edge_binding.status, edge_binding.appliance_size, - edge_binding.edge_type) + edge_binding.edge_type, + resource_pool=resource_pool) else: router_name = ('shared' + '-' + _uuid())[ :vcns_const.EDGE_NAME_LEN] self._allocate_edge_appliance( context, target_router_id, router_name, - appliance_size=vcns_const.SERVICE_SIZE_MAPPING['router']) + appliance_size=vcns_const.SERVICE_SIZE_MAPPING['router'], + res_pool=resource_pool) return True def unbind_router_on_edge(self, context, router_id): @@ -1659,7 +1712,8 @@ class EdgeManager(object): network_id) -def create_lrouter(nsxv_manager, context, lrouter, lswitch=None, dist=False): +def create_lrouter(nsxv_manager, context, lrouter, lswitch=None, dist=False, + res_pool=None): """Create an edge for logical router support.""" router_id = lrouter['id'] router_name = lrouter['name'] + '-' + router_id @@ -1668,7 +1722,8 @@ def create_lrouter(nsxv_manager, context, lrouter, lswitch=None, dist=False): nsxv_db.add_nsxv_router_binding( context.session, router_id, None, None, plugin_const.PENDING_CREATE, - appliance_size=appliance_size) + appliance_size=appliance_size, + resource_pool=res_pool) # deploy edge jobdata = { @@ -1683,7 +1738,8 @@ def create_lrouter(nsxv_manager, context, lrouter, lswitch=None, dist=False): # as we're not in a database transaction now task = nsxv_manager.deploy_edge( router_id, router_name, internal_network=None, - dist=dist, jobdata=jobdata, appliance_size=appliance_size) + dist=dist, jobdata=jobdata, appliance_size=appliance_size, + res_pool=res_pool) task.wait(task_const.TaskState.RESULT) diff --git a/vmware_nsx/plugins/nsx_v/vshield/vcns.py b/vmware_nsx/plugins/nsx_v/vshield/vcns.py index 9455c54a62..2e9fc476f7 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/vcns.py +++ b/vmware_nsx/plugins/nsx_v/vshield/vcns.py @@ -827,6 +827,11 @@ class Vcns(object): return False return True + def get_inventory_name(self, object_id): + uri = '%s/inventory/%s/basicinfo' % (SERVICES_PREFIX, object_id) + h, c = self.do_request(HTTP_GET, uri, decode=True) + return c['name'] + def _get_version(self): uri = '/api/2.0/services/vsmconfig' h, c = self.do_request(HTTP_GET, uri, decode=True) diff --git a/vmware_nsx/tests/unit/nsx_v/test_plugin.py b/vmware_nsx/tests/unit/nsx_v/test_plugin.py index 154f55ad25..83f3f8c649 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v/test_plugin.py @@ -132,10 +132,14 @@ class NsxVPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase): mock_delete_dhcp_service = mock.patch("%s.%s" % ( vmware.EDGE_MANAGE_NAME, 'delete_dhcp_edge_service')) mock_delete_dhcp_service.start() + 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) 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 plugin_instance._get_edge_id_by_rtr_id = mock.Mock() plugin_instance._get_edge_id_by_rtr_id.return_value = False plugin_instance.edge_manager.is_dhcp_opt_enabled = True @@ -1890,7 +1894,8 @@ class L3NatTest(test_l3_plugin.L3BaseForIntTests, NsxVPluginV2TestCase): data['router']['name'] = name if admin_state_up: data['router']['admin_state_up'] = admin_state_up - for arg in (('admin_state_up', 'tenant_id') + (arg_list or ())): + for arg in (('admin_state_up', 'tenant_id') + + (arg_list or ())): # Arg must be present and not empty if kwargs.get(arg): data['router'][arg] = kwargs[arg] @@ -3003,6 +3008,40 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase, returned_router['id']) self.assertEqual(plugin_const.ERROR, new_router['status']) + def test_create_router_with_bad_az_hint(self): + p = manager.NeutronManager.get_plugin() + router = {'router': {'admin_state_up': True, + 'name': 'e161be1d-0d0d-4046-9823-5a593d94f72c', + 'tenant_id': context.get_admin_context().tenant_id, + 'router_type': 'exclusive', + 'availability_zone_hints': ['bad_hint']}} + self.assertRaises(n_exc.NeutronException, + p.create_router, + context.get_admin_context(), + router) + + def test_create_router_with_az_hint(self): + p = manager.NeutronManager.get_plugin() + alter_pool_id = 'respool-7' + alter_pool_name = 'rs-7' + p._availability_zones_data = {'default': self.default_res_pool, + alter_pool_name: alter_pool_id} + p._get_edge_id_by_rtr_id = p.real_get_edge + + router = {'router': {'admin_state_up': True, + 'name': 'e161be1d-0d0d-4046-9823-5a593d94f72c', + 'tenant_id': context.get_admin_context().tenant_id, + 'router_type': 'exclusive', + 'availability_zone_hints': [alter_pool_name]}} + + # router creation should succeed + returned_router = p.create_router(context.get_admin_context(), + router) + self.assertEqual([alter_pool_name], + returned_router['availability_zone_hints']) + self.assertEqual([alter_pool_name], + returned_router['availability_zones']) + class ExtGwModeTestCase(NsxVPluginV2TestCase, test_ext_gw_mode.ExtGwModeIntTestCase): @@ -3263,6 +3302,44 @@ class TestVdrTestCase(L3NatTest, L3NatTestCaseBase, def test_router_create_distributed_unspecified(self): self._test_router_create_with_distributed(None, False) + def _test_create_rotuer_with_az_hint(self, with_hint): + # init the availability zones in the plugin + p = manager.NeutronManager.get_plugin() + pool_id = 'respool-7' + pool_name = 'rs-7' + p._availability_zones_data = {'default': self.default_res_pool, + pool_name: pool_id} + + # create a router with/without hints + router = {'router': {'admin_state_up': True, + 'name': 'e161be1d-0d0d-4046-9823-5a593d94f72c', + 'tenant_id': context.get_admin_context().tenant_id, + 'distributed': True}} + if with_hint: + router['router']['availability_zone_hints'] = [pool_name] + returned_router = p.create_router(context.get_admin_context(), + router) + # availability zones is still empty because the router is not attached + if with_hint: + self.assertEqual([pool_name], + returned_router['availability_zone_hints']) + else: + self.assertEqual([], + returned_router['availability_zone_hints']) + + edge_id = edge_utils.get_router_edge_id( + context.get_admin_context(), returned_router['id']) + res_pool = nsxv_db.get_edge_resource_pool( + context.get_admin_context().session, edge_id) + expected_pool = pool_id if with_hint else self.default_res_pool + self.assertEqual(expected_pool, res_pool) + + def test_create_rotuer_with_az_hint(self): + self._test_create_rotuer_with_az_hint(True) + + def test_create_rotuer_without_az_hint(self): + self._test_create_rotuer_with_az_hint(False) + def test_floatingip_with_assoc_fails(self): self._test_floatingip_with_assoc_fails( self._plugin_name + '._check_and_get_fip_assoc') @@ -4109,3 +4186,50 @@ class TestSharedRouterTestCase(L3NatTest, L3NatTestCaseBase, # get the updated router and check it's type body = self._show('routers', router_id) self.assertEqual('exclusive', body['router']['router_type']) + + def _test_create_rotuer_with_az_hint(self, with_hint): + # init the availability zones in the plugin + p = manager.NeutronManager.get_plugin() + pool_id = 'respool-7' + pool_name = 'rs-7' + p._availability_zones_data = {'default': self.default_res_pool, + pool_name: pool_id} + + # create a router with/without hints + router = {'router': {'admin_state_up': True, + 'name': 'e161be1d-0d0d-4046-9823-5a593d94f72c', + 'tenant_id': context.get_admin_context().tenant_id, + 'router_type': 'shared'}} + if with_hint: + router['router']['availability_zone_hints'] = [pool_name] + returned_router = p.create_router(context.get_admin_context(), + router) + # availability zones is still empty because the router is not attached + if with_hint: + self.assertEqual([pool_name], + returned_router['availability_zone_hints']) + else: + self.assertEqual([], + returned_router['availability_zone_hints']) + self.assertEqual([], + returned_router['availability_zones']) + + # Add interface so the router will be attached to an edge + with self.subnet() as s1: + router_id = returned_router['id'] + self._router_interface_action('add', + router_id, + s1['subnet']['id'], + None) + edge_id = edge_utils.get_router_edge_id( + context.get_admin_context(), router_id) + res_pool = nsxv_db.get_edge_resource_pool( + context.get_admin_context().session, edge_id) + expected_pool = pool_id if with_hint else self.default_res_pool + self.assertEqual(expected_pool, res_pool) + + def test_create_rotuer_with_az_hint(self): + self._test_create_rotuer_with_az_hint(True) + + def test_create_rotuer_without_az_hint(self): + self._test_create_rotuer_with_az_hint(False) diff --git a/vmware_nsx/tests/unit/nsx_v/vshield/test_edge_utils.py b/vmware_nsx/tests/unit/nsx_v/vshield/test_edge_utils.py index afe886ff78..c40fc4194e 100644 --- a/vmware_nsx/tests/unit/nsx_v/vshield/test_edge_utils.py +++ b/vmware_nsx/tests/unit/nsx_v/vshield/test_edge_utils.py @@ -38,6 +38,7 @@ EDGE_CREATING = 'creating-' EDGE_ERROR1 = 'error1-' EDGE_ERROR2 = 'error2-' EDGE_DELETING = 'deleting-' +DEFAULT_RES_POOL = 'respool-28' class EdgeUtilsTestCaseMixin(testlib_api.SqlTestCase): @@ -56,6 +57,8 @@ class EdgeUtilsTestCaseMixin(testlib_api.SqlTestCase): self.ctx = context.get_admin_context() self.addCleanup(nsxv_manager_p.stop) self.fake_jobdata = {'router_id': 'fake_id', 'context': self.ctx} + cfg.CONF.set_override("resource_pool_id", DEFAULT_RES_POOL, + group="nsxv") def _create_router(self, name='router1'): return {'name': name, @@ -77,7 +80,8 @@ class EdgeUtilsTestCaseMixin(testlib_api.SqlTestCase): self.ctx.session, binding['router_id'], binding['edge_id'], None, binding['status'], appliance_size=binding['appliance_size'], - edge_type=binding['edge_type']) + edge_type=binding['edge_type'], + resource_pool=binding['resource_pool']) class EdgeDHCPManagerTestCase(EdgeUtilsTestCaseMixin): @@ -94,17 +98,20 @@ class EdgeDHCPManagerTestCase(EdgeUtilsTestCaseMixin): 'edge_id': 'edge-1', 'router_id': 'backup-11111111-1111', 'appliance_size': 'compact', - 'edge_type': 'service'}, + 'edge_type': 'service', + 'resource_pool': DEFAULT_RES_POOL}, {'status': plugin_const.PENDING_DELETE, 'edge_id': 'edge-2', 'router_id': 'dhcp-22222222-2222', 'appliance_size': 'compact', - 'edge_type': 'service'}, + 'edge_type': 'service', + 'resource_pool': DEFAULT_RES_POOL}, {'status': plugin_const.PENDING_DELETE, 'edge_id': 'edge-3', 'router_id': 'backup-33333333-3333', 'appliance_size': 'compact', - 'edge_type': 'service'}] + 'edge_type': 'service', + 'resource_pool': DEFAULT_RES_POOL}] self._populate_vcns_router_binding(fake_edge_pool) fake_network = self._create_network() fake_subnet = self._create_subnet(fake_network['id']) @@ -118,7 +125,7 @@ class EdgeDHCPManagerTestCase(EdgeUtilsTestCaseMixin): self.nsxv_manager.update_edge.assert_called_once_with( resource_id, 'edge-1', mock.ANY, None, jobdata=jobdata, appliance_size=vcns_const.SERVICE_SIZE_MAPPING['dhcp'], - dist=False, set_errors=True) + dist=False, set_errors=True, res_pool=None) def test_get_random_available_edge(self): available_edge_ids = ['edge-1', 'edge-2'] @@ -185,10 +192,11 @@ class EdgeUtilsTestCase(EdgeUtilsTestCaseMixin): lrouter = self._create_router() self.nsxv_manager.deploy_edge.reset_mock() edge_utils.create_lrouter(self.nsxv_manager, self.ctx, lrouter, - lswitch=None, dist=False) + lswitch=None, dist=False, + res_pool=DEFAULT_RES_POOL) self.nsxv_manager.deploy_edge.assert_called_once_with( lrouter['id'], (lrouter['name'] + '-' + lrouter['id']), - internal_network=None, dist=False, + internal_network=None, dist=False, res_pool=DEFAULT_RES_POOL, jobdata={'router_id': lrouter['id'], 'lrouter': lrouter, 'lswitch': None, @@ -365,70 +373,81 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): def _create_available_router_bindings( self, num, size=nsxv_constants.LARGE, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + resource_pool=DEFAULT_RES_POOL): id_prefix = EDGE_AVAIL + size + '-' + edge_type return [{'status': plugin_const.ACTIVE, 'edge_id': id_prefix + '-edge-' + str(i), 'router_id': (vcns_const.BACKUP_ROUTER_PREFIX + id_prefix + str(i)), 'appliance_size': size, - 'edge_type': edge_type} + 'edge_type': edge_type, + 'resource_pool': resource_pool} for i in moves.range(num)] def _create_creating_router_bindings( self, num, size=nsxv_constants.LARGE, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + resource_pool=DEFAULT_RES_POOL): id_prefix = EDGE_CREATING + size + '-' + edge_type return [{'status': plugin_const.PENDING_CREATE, 'edge_id': id_prefix + '-edge-' + str(i), 'router_id': (vcns_const.BACKUP_ROUTER_PREFIX + id_prefix + str(i)), 'appliance_size': size, - 'edge_type': edge_type} + 'edge_type': edge_type, + 'resource_pool': resource_pool} for i in moves.range(num)] def _create_error_router_bindings( self, num, status=plugin_const.ERROR, size=nsxv_constants.LARGE, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + resource_pool=DEFAULT_RES_POOL): id_prefix = EDGE_ERROR1 + size + '-' + edge_type return [{'status': status, 'edge_id': id_prefix + '-edge-' + str(i), 'router_id': (vcns_const.BACKUP_ROUTER_PREFIX + id_prefix + str(i)), 'appliance_size': size, - 'edge_type': edge_type} + 'edge_type': edge_type, + 'resource_pool': resource_pool} for i in moves.range(num)] def _create_error_router_bindings_at_backend( self, num, status=plugin_const.ACTIVE, size=nsxv_constants.LARGE, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + resource_pool=DEFAULT_RES_POOL): id_prefix = EDGE_ERROR2 + size + '-' + edge_type return [{'status': status, 'edge_id': id_prefix + '-edge-' + str(i), 'router_id': (vcns_const.BACKUP_ROUTER_PREFIX + id_prefix + str(i)), 'appliance_size': size, - 'edge_type': edge_type} + 'edge_type': edge_type, + 'resource_pool': resource_pool} for i in moves.range(num)] def _create_deleting_router_bindings( self, num, size=nsxv_constants.LARGE, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + resource_pool=DEFAULT_RES_POOL): id_prefix = EDGE_DELETING + size + '-' + edge_type return [{'status': plugin_const.PENDING_DELETE, 'edge_id': id_prefix + '-edge-' + str(i), 'router_id': (vcns_const.BACKUP_ROUTER_PREFIX + id_prefix + str(i)), 'appliance_size': size, - 'edge_type': edge_type} + 'edge_type': edge_type, + 'resource_pool': resource_pool} for i in moves.range(num)] def _create_edge_pools(self, avail, creating, error, error_at_backend, deleting, size=nsxv_constants.LARGE, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + resource_pool=DEFAULT_RES_POOL): """Create a backup edge pool with different status of edges. Backup edges would be edges with avail, creating and error_at_backend, @@ -436,34 +455,45 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): """ return ( self._create_error_router_bindings( - error, size=size, edge_type=edge_type) + + error, size=size, edge_type=edge_type, + resource_pool=resource_pool) + self._create_deleting_router_bindings( - deleting, size=size, edge_type=edge_type) + + deleting, size=size, edge_type=edge_type, + resource_pool=resource_pool) + self._create_error_router_bindings_at_backend( - error_at_backend, size=size, edge_type=edge_type) + + error_at_backend, size=size, edge_type=edge_type, + resource_pool=resource_pool) + self._create_creating_router_bindings( - creating, size=size, edge_type=edge_type) + + creating, size=size, edge_type=edge_type, + resource_pool=resource_pool) + self._create_available_router_bindings( - avail, size=size, edge_type=edge_type)) + avail, size=size, edge_type=edge_type, + resource_pool=resource_pool)) def _create_backup_router_bindings( self, avail, creating, error, error_at_backend, deleting, error_status=plugin_const.PENDING_DELETE, error_at_backend_status=plugin_const.PENDING_DELETE, size=nsxv_constants.LARGE, - edge_type=nsxv_constants.SERVICE_EDGE): + edge_type=nsxv_constants.SERVICE_EDGE, + resource_pool=DEFAULT_RES_POOL): return ( self._create_error_router_bindings( - error, status=error_status, size=size, edge_type=edge_type) + + error, status=error_status, size=size, edge_type=edge_type, + resource_pool=resource_pool) + self._create_error_router_bindings_at_backend( error_at_backend, status=error_at_backend_status, - size=size, edge_type=edge_type) + + size=size, edge_type=edge_type, + resource_pool=resource_pool) + self._create_creating_router_bindings( - creating, size=size, edge_type=edge_type) + + creating, size=size, edge_type=edge_type, + resource_pool=resource_pool) + self._create_available_router_bindings( - avail, size=size, edge_type=edge_type) + + avail, size=size, edge_type=edge_type, + resource_pool=resource_pool) + self._create_deleting_router_bindings( - deleting, size=size, edge_type=edge_type)) + deleting, size=size, edge_type=edge_type, + resource_pool=resource_pool)) def _verify_router_bindings(self, exp_bindings, act_db_bindings): exp_dict = dict(zip([binding['router_id'] @@ -472,7 +502,8 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): 'edge_id': binding['edge_id'], 'status': binding['status'], 'appliance_size': binding['appliance_size'], - 'edge_type': binding['edge_type']} + 'edge_type': binding['edge_type'], + 'resource_pool': binding['resource_pool']} for binding in act_db_bindings] act_dict = dict(zip([binding['router_id'] for binding in act_bindings], act_bindings)) @@ -489,7 +520,7 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): error_at_backend_status=plugin_const.ACTIVE, size=nsxv_constants.LARGE) backup_bindings = self.edge_manager._get_backup_edge_bindings(self.ctx, - appliance_size=nsxv_constants.LARGE) + appliance_size=nsxv_constants.LARGE, res_pool=DEFAULT_RES_POOL) self._verify_router_bindings(expect_backup_bindings, backup_bindings) def test_get_available_router_bindings(self): @@ -508,7 +539,8 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): for binding_db in nsxv_db.get_nsxv_router_bindings( self.ctx.session) if (binding_db['appliance_size'] == appliance_size and - binding_db['edge_type'] == edge_type)] + binding_db['edge_type'] == edge_type and + binding_db['resource_pool'] == DEFAULT_RES_POOL)] self._verify_router_bindings(expect_backup_bindings, router_bindings) edge_id = (EDGE_AVAIL + appliance_size + '-' + edge_type + '-edge-' + str(0)) @@ -527,7 +559,8 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): error_at_backend_status=plugin_const.PENDING_DELETE) self.edge_manager._check_backup_edge_pool( 0, 3, - appliance_size=appliance_size, edge_type=edge_type) + appliance_size=appliance_size, edge_type=edge_type, + res_pool=DEFAULT_RES_POOL) router_bindings = [ binding for binding in nsxv_db.get_nsxv_router_bindings(self.ctx.session) @@ -547,7 +580,8 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): edge_utils.eventlet.spawn_n.return_value = None self.edge_manager._check_backup_edge_pool( - 5, 10, appliance_size=appliance_size, edge_type=edge_type) + 5, 10, appliance_size=appliance_size, edge_type=edge_type, + res_pool=DEFAULT_RES_POOL) router_bindings = [ binding for binding in nsxv_db.get_nsxv_router_bindings(self.ctx.session) @@ -557,7 +591,8 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): binding_ids = [bind.router_id for bind in router_bindings] self.assertEqual(2, len(router_bindings)) edge_utils.eventlet.spawn_n.assert_called_with( - mock.ANY, mock.ANY, binding_ids, appliance_size, edge_type) + mock.ANY, mock.ANY, binding_ids, appliance_size, + edge_type, DEFAULT_RES_POOL) def test_check_backup_edge_pools_with_empty_conf(self): pool_edges = (self._create_edge_pools(1, 2, 3, 4, 5) + @@ -663,7 +698,8 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): self.nsxv_manager.update_edge.assert_has_calls( [mock.call('fake_id', edge_id, 'fake_name', None, jobdata=self.fake_jobdata, set_errors=True, - appliance_size=nsxv_constants.LARGE, dist=False)]) + appliance_size=nsxv_constants.LARGE, dist=False, + res_pool=None)]) def test_allocate_compact_edge_appliance_with_default(self): self.edge_manager.edge_pool_dicts = self.default_edge_pool_dicts @@ -681,7 +717,8 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): self.nsxv_manager.update_edge.assert_has_calls( [mock.call('fake_id', edge_id, 'fake_name', None, jobdata=self.fake_jobdata, set_errors=True, - appliance_size=nsxv_constants.COMPACT, dist=False)]) + appliance_size=nsxv_constants.COMPACT, dist=False, + res_pool=None)]) def test_allocate_large_edge_appliance_with_vdr(self): self.edge_manager.edge_pool_dicts = self.vdr_edge_pool_dicts @@ -699,7 +736,8 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): self.nsxv_manager.update_edge.assert_has_calls( [mock.call('fake_id', edge_id, 'fake_name', None, jobdata=self.fake_jobdata, set_errors=True, - appliance_size=nsxv_constants.LARGE, dist=True)]) + appliance_size=nsxv_constants.LARGE, dist=True, + res_pool=None)]) def test_free_edge_appliance_with_empty(self): self.edge_manager._clean_all_error_edge_bindings = mock.Mock() @@ -718,7 +756,8 @@ class EdgeManagerTestCase(EdgeUtilsTestCaseMixin): assert not self.nsxv_manager.delete_edge.called self.nsxv_manager.update_edge.assert_has_calls( [mock.call(mock.ANY, mock.ANY, mock.ANY, None, - appliance_size=nsxv_constants.COMPACT, dist=False)]) + appliance_size=nsxv_constants.COMPACT, dist=False, + res_pool=None)]) def test_free_edge_appliance_with_default_with_full(self): self.edge_pool_dicts = {