diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 72ba0173f7..6621556a2e 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -148,6 +148,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, "advanced-service-providers", "subnet_allocation", "availability_zone", + "network_availability_zone", "router_availability_zone"] supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, @@ -497,6 +498,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, pnet.SEGMENTATION_ID: binding.vlan_id} for binding in bindings] + # update availability zones + network[az_ext.AVAILABILITY_ZONES] = ( + self.get_network_availability_zones(context, network)) + def _get_subnet_as_providers(self, context, subnet): net_id = subnet.get('network_id') if net_id is None: @@ -720,10 +725,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # 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 + # Add this availability zone as a router & network resource + for resource in ('router', 'network'): + result[(az, resource)] = True return result def _validate_availability_zones_in_obj(self, context, resource_type, @@ -751,6 +755,16 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, raise az_ext.AvailabilityZoneNotFound( availability_zone=diff.pop()) + def get_network_resource_pool(self, context, network_id): + network = self.get_network(context, network_id) + if az_ext.AZ_HINTS in network: + for hint in network[az_ext.AZ_HINTS]: + # For now we use only the first hint + return self.get_res_pool_id_by_name(hint) + + # return the default + return cfg.CONF.nsxv.resource_pool_id + def create_network(self, context, network): net_data = network['network'] tenant_id = net_data['tenant_id'] @@ -758,6 +772,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Process the provider network extension provider_type = self._convert_to_transport_zones_dict(net_data) self._validate_provider_create(context, net_data) + self._validate_availability_zones_in_obj(context, 'network', net_data) net_data['id'] = str(uuid.uuid4()) external = net_data.get(ext_net_extn.EXTERNAL) @@ -839,6 +854,19 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self._process_network_port_security_create( context, net_data, new_net) + # update the network with the availability zone hints + if az_ext.AZ_HINTS in net_data: + self.validate_availability_zones(context, 'network', + net_data[az_ext.AZ_HINTS]) + az_hints = az_ext.convert_az_list_to_string( + net_data[az_ext.AZ_HINTS]) + super(NsxVPluginV2, self).update_network(context, + new_net['id'], + {'network': {az_ext.AZ_HINTS: az_hints}}) + new_net[az_ext.AZ_HINTS] = az_hints + # still no availability zones until subnets creation + new_net[az_ext.AVAILABILITY_ZONES] = [] + # DB Operations for setting the network as external self._process_l3_create(context, new_net, net_data) if (net_data.get(mpnet.SEGMENTS) and @@ -1960,16 +1988,37 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, super(NsxVPluginV2, self).delete_router(context, id) router_driver.delete_router(context, id) + def _get_availability_zone_by_edge(self, context, 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 + + db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs( + attr.NETWORKS, ['_extend_availability_zone_hints']) + + def _extend_availability_zone_hints(self, net_res, net_db): + net_res[az_ext.AZ_HINTS] = az_ext.convert_az_string_to_list( + net_db[az_ext.AZ_HINTS]) + + def get_network_availability_zones(self, context, net_db): + """Return availability zones which a network belongs to.""" + + resource_id = (vcns_const.DHCP_EDGE_PREFIX + net_db["id"])[:36] + dhcp_edge_binding = nsxv_db.get_nsxv_router_binding( + context.session, resource_id) + if dhcp_edge_binding: + edge_id = dhcp_edge_binding['edge_id'] + return [self._get_availability_zone_by_edge(context, edge_id)] + return [] + 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 [self._get_availability_zone_by_edge(context, edge_id)] return [] def get_router(self, context, id, fields=None): diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py index 04ecbeb4ef..7862e60f7a 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py +++ b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py @@ -740,12 +740,13 @@ class EdgeManager(object): self.nsxv_manager.delete_edge( router_id, edge_id, jobdata=jobdata, dist=dist) - def _allocate_dhcp_edge_appliance(self, context, resource_id): + def _allocate_dhcp_edge_appliance(self, context, resource_id, res_pool): resource_name = (vcns_const.DHCP_EDGE_PREFIX + _uuid())[:vcns_const.EDGE_NAME_LEN] self._allocate_edge_appliance( context, resource_id, resource_name, - appliance_size=vcns_const.SERVICE_SIZE_MAPPING['dhcp']) + appliance_size=vcns_const.SERVICE_SIZE_MAPPING['dhcp'], + res_pool=res_pool) def _free_dhcp_edge_appliance(self, context, network_id): router_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36] @@ -989,12 +990,12 @@ class EdgeManager(object): available_edge_ids.append(x) return (conflict_edge_ids, available_edge_ids) - def _get_used_edges(self, context, subnet): + def _get_used_edges(self, context, subnet, resource_pool): """Returns conflicting and available edges for the subnet.""" conflicting = self.plugin._get_conflicting_networks_for_subnet( context, subnet) return self._get_available_edges(context, subnet['network_id'], - conflicting) + conflicting, resource_pool) def remove_network_from_dhcp_edge(self, context, network_id, edge_id): old_binding = nsxv_db.get_edge_vnic_binding( @@ -1045,8 +1046,11 @@ class EdgeManager(object): nsxv_db.allocate_edge_vnic_with_tunnel_index( context.session, edge_id, network_id) - def allocate_new_dhcp_edge(self, context, network_id, resource_id): - self._allocate_dhcp_edge_appliance(context, resource_id) + def allocate_new_dhcp_edge(self, context, network_id, resource_id, + res_pool): + if not res_pool: + res_pool = cfg.CONF.nsxv.resource_pool_id + self._allocate_dhcp_edge_appliance(context, resource_id, res_pool) with locking.LockManager.get_lock('nsx-edge-pool'): new_edge = nsxv_db.get_nsxv_router_binding(context.session, resource_id) @@ -1062,6 +1066,7 @@ class EdgeManager(object): If new edge was allocated, return resource_id, else return None """ + res_pool = self.plugin.get_network_resource_pool(context, network_id) # Check if the network has one related dhcp edge resource_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36] dhcp_edge_binding = nsxv_db.get_nsxv_router_binding(context.session, @@ -1072,7 +1077,8 @@ class EdgeManager(object): with locking.LockManager.get_lock('nsx-edge-pool'): edge_id = dhcp_edge_binding['edge_id'] (conflict_edge_ids, - available_edge_ids) = self._get_used_edges(context, subnet) + available_edge_ids) = self._get_used_edges(context, subnet, + res_pool) LOG.debug("The available edges %s, the conflict edges %s " "at present is using edge %s", available_edge_ids, conflict_edge_ids, edge_id) @@ -1106,7 +1112,8 @@ class EdgeManager(object): else: with locking.LockManager.get_lock('nsx-edge-pool'): (conflict_edge_ids, - available_edge_ids) = self._get_used_edges(context, subnet) + available_edge_ids) = self._get_used_edges(context, subnet, + res_pool) LOG.debug('The available edges %s, the conflict edges %s', available_edge_ids, conflict_edge_ids) # There is available one @@ -1124,7 +1131,8 @@ class EdgeManager(object): allocate_new_edge = True if allocate_new_edge: - self.allocate_new_dhcp_edge(context, network_id, resource_id) + self.allocate_new_dhcp_edge(context, network_id, resource_id, + res_pool) # If a new Edge was allocated, return resource_id return resource_id @@ -1219,7 +1227,6 @@ class EdgeManager(object): def configure_dhcp_for_vdr_network( self, context, network_id, vdr_router_id): - # If network is already attached to a DHCP Edge, detach from it resource_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36] dhcp_edge_binding = nsxv_db.get_nsxv_router_binding(context.session, @@ -1241,8 +1248,10 @@ class EdgeManager(object): context, dhcp_edge_id, resource_id, network_id) else: # Attach to DHCP Edge + resource_pool = self.plugin.get_network_resource_pool(context, + network_id) dhcp_edge_id = self.allocate_new_dhcp_edge( - context, network_id, resource_id) + context, network_id, resource_id, resource_pool) self.plugin.metadata_proxy_handler.configure_router_edge( resource_id, context) diff --git a/vmware_nsx/tests/unit/nsx_v/test_plugin.py b/vmware_nsx/tests/unit/nsx_v/test_plugin.py index 83f3f8c649..abd78bc6fa 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v/test_plugin.py @@ -603,6 +603,46 @@ class TestNetworksV2(test_plugin.TestNetworksV2, NsxVPluginV2TestCase): self.assertTrue(fake_init_from_policy.called) self.assertTrue(fake_dvs_update.called) + def test_create_network_with_bad_az_hint(self): + p = manager.NeutronManager.get_plugin() + ctx = context.get_admin_context() + data = {'network': { + 'name': 'test-qos', + 'tenant_id': self._tenant_id, + 'port_security_enabled': False, + 'admin_state_up': True, + 'shared': False, + 'availability_zone_hints': ['bad_hint'] + }} + self.assertRaises(n_exc.NeutronException, + p.create_network, + ctx, data) + + def test_create_network_with_az_hint(self): + p = manager.NeutronManager.get_plugin() + ctx = context.get_admin_context() + 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} + + data = {'network': { + 'name': 'test-qos', + 'tenant_id': self._tenant_id, + 'port_security_enabled': False, + 'admin_state_up': True, + 'shared': False, + 'availability_zone_hints': [alter_pool_name] + }} + + # network creation should succeed + net = p.create_network(ctx, data) + self.assertEqual([alter_pool_name], + net['availability_zone_hints']) + # the availability zone is still empty until subnet creation + self.assertEqual([], + net['availability_zones']) + class TestVnicIndex(NsxVPluginV2TestCase, test_vnic_index.VnicIndexDbTestCase): 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 c40fc4194e..5c5407a2dc 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 @@ -84,6 +84,11 @@ class EdgeUtilsTestCaseMixin(testlib_api.SqlTestCase): resource_pool=binding['resource_pool']) +class DummyPlugin(object): + def get_network_resource_pool(self, context, network_id): + return cfg.CONF.nsxv.resource_pool_id + + class EdgeDHCPManagerTestCase(EdgeUtilsTestCaseMixin): def setUp(self): @@ -115,6 +120,7 @@ class EdgeDHCPManagerTestCase(EdgeUtilsTestCaseMixin): self._populate_vcns_router_binding(fake_edge_pool) fake_network = self._create_network() fake_subnet = self._create_subnet(fake_network['id']) + self.edge_manager.plugin = DummyPlugin() with mock.patch.object(self.edge_manager, '_get_used_edges', return_value=([], [])): self.edge_manager.create_dhcp_edge_service(self.ctx, @@ -125,7 +131,8 @@ 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, res_pool=None) + dist=False, set_errors=True, + res_pool=cfg.CONF.nsxv.resource_pool_id) def test_get_random_available_edge(self): available_edge_ids = ['edge-1', 'edge-2']