# Copyright 2014 VMware, Inc. # All Rights Reserved # # 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. import eventlet import netaddr import random from sqlalchemy import exc as db_base_exc import time from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import uuidutils from six import moves from sqlalchemy.orm import exc as sa_exc from neutron.common import exceptions as n_exc from neutron import context as q_context from neutron.extensions import l3 from neutron.i18n import _LE, _LW from neutron.plugins.common import constants as plugin_const from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import locking from vmware_nsx.common import nsxv_constants from vmware_nsx.db import db as nsx_db from vmware_nsx.db import nsxv_db from vmware_nsx.plugins.nsx_v.vshield.common import ( constants as vcns_const) from vmware_nsx.plugins.nsx_v.vshield.tasks import ( constants as task_const) from vmware_nsx.plugins.nsx_v.vshield.tasks import tasks from vmware_nsx.plugins.nsx_v.vshield import vcns WORKER_POOL_SIZE = 8 RP_FILTER_PROPERTY_OFF_TEMPLATE = 'sysctl.net.ipv4.conf.%s.rp_filter=%s' LOG = logging.getLogger(__name__) _uuid = uuidutils.generate_uuid def parse_backup_edge_pool_opt(): """Parse edge pool opts and returns result.""" edge_pool_opts = cfg.CONF.nsxv.backup_edge_pool res = [] for edge_pool_def in edge_pool_opts: split = edge_pool_def.split(':') try: (edge_type, edge_size, minimum_pooled_edges, maximum_pooled_edges) = split[:4] except ValueError: raise n_exc.Invalid(_("Invalid edge pool format")) if edge_type not in vcns_const.ALLOWED_EDGE_TYPES: msg = (_("edge type '%(edge_type)s' is not allowed, " "allowed types: %(allowed)s") % {'edge_type': edge_type, 'allowed': vcns_const.ALLOWED_EDGE_TYPES}) LOG.error(msg) raise n_exc.Invalid(msg) edge_size = edge_size or nsxv_constants.LARGE if edge_size not in vcns_const.ALLOWED_EDGE_SIZES: msg = (_("edge size '%(edge_size)s' is not allowed, " "allowed types: %(allowed)s") % {'edge_type': edge_size, 'allowed': vcns_const.ALLOWED_EDGE_SIZES}) LOG.error(msg) raise n_exc.Invalid(msg) res.append({'edge_type': edge_type, 'edge_size': edge_size, 'minimum_pooled_edges': int(minimum_pooled_edges), 'maximum_pooled_edges': int(maximum_pooled_edges)}) edge_pool_dicts = {} for edge_type in vcns_const.ALLOWED_EDGE_TYPES: edge_pool_dicts[edge_type] = {} for r in res: edge_pool_dict = edge_pool_dicts[r['edge_type']] if r['edge_size'] in edge_pool_dict.keys(): raise n_exc.Invalid(_("Duplicate edge pool configuration")) else: edge_pool_dict[r['edge_size']] = { 'minimum_pooled_edges': r['minimum_pooled_edges'], 'maximum_pooled_edges': r['maximum_pooled_edges']} return edge_pool_dicts class EdgeManager(object): """Edge Appliance Management. EdgeManager provides a pool of edge appliances which we can use to support DHCP&metadata, L3&FIP and LB&FW&VPN services. """ def __init__(self, nsxv_manager, plugin): LOG.debug("Start Edge Manager initialization") self.nsxv_manager = nsxv_manager self.dvs_id = cfg.CONF.nsxv.dvs_id self.edge_pool_dicts = parse_backup_edge_pool_opt() self.nsxv_plugin = nsxv_manager.callbacks.plugin self.plugin = plugin 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() def _get_per_edge_rp_filter_state(self): version = self.nsxv_manager.vcns.get_version() # TODO(kobis): should have better mapping of versions here for this if version[:3] == '6.1': return False return True def _deploy_edge(self, context, lrouter, lswitch=None, appliance_size=nsxv_constants.LARGE, edge_type=nsxv_constants.SERVICE_EDGE, async=True): """Create an edge for logical router support.""" router_id = lrouter['id'] # deploy edge jobdata = { 'router_id': router_id, 'lrouter': lrouter, 'lswitch': lswitch, 'context': context } task = self.nsxv_manager.deploy_edge( 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) return task def _deploy_backup_edges_on_db(self, context, num, appliance_size=nsxv_constants.LARGE, edge_type=nsxv_constants.SERVICE_EDGE): router_ids = [(vcns_const.BACKUP_ROUTER_PREFIX + _uuid())[:vcns_const.EDGE_NAME_LEN] for i in moves.range(num)] for router_id in router_ids: nsxv_db.add_nsxv_router_binding( context.session, router_id, None, None, plugin_const.PENDING_CREATE, appliance_size=appliance_size, edge_type=edge_type) return router_ids def _deploy_backup_edges_at_backend(self, context, router_ids, appliance_size=nsxv_constants.LARGE, edge_type=nsxv_constants.SERVICE_EDGE): eventlet.spawn_n(self._pool_creator, context, router_ids, appliance_size, edge_type) def _pool_creator(self, context, router_ids, appliance_size, edge_type): pool = self.worker_pool for router_id in router_ids: fake_router = { 'id': router_id, 'name': router_id} pool.spawn_n(self._deploy_edge, context, fake_router, appliance_size=appliance_size, edge_type=edge_type, async=False) def _delete_edge(self, context, router_binding): if router_binding['status'] == plugin_const.ERROR: LOG.warning(_LW("Start deleting %(router_id)s corresponding" "edge: %(edge_id)s due to status error"), {'router_id': router_binding['router_id'], 'edge_id': router_binding['edge_id']}) jobdata = {'context': context} nsxv_db.update_nsxv_router_binding( context.session, router_binding['router_id'], status=plugin_const.PENDING_DELETE) self.nsxv_manager.delete_edge( router_binding['router_id'], router_binding['edge_id'], jobdata=jobdata, dist=(router_binding['edge_type'] == nsxv_constants.VDR_EDGE)) def _delete_backup_edges_on_db(self, context, backup_router_bindings): for binding in backup_router_bindings: nsxv_db.update_nsxv_router_binding( context.session, binding['router_id'], status=plugin_const.PENDING_DELETE) def _delete_backup_edges_at_backend(self, context, backup_router_bindings): for binding in backup_router_bindings: # delete edge LOG.debug("Start deleting extra edge: %s in pool", binding['edge_id']) jobdata = { 'context': context } self.nsxv_manager.delete_edge( 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]} 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) self._delete_backup_edges_on_db(context, error_router_bindings) self._delete_backup_edges_at_backend(context, error_router_bindings) def _get_backup_edge_bindings(self, context, appliance_size=nsxv_constants.LARGE, edge_type=nsxv_constants.SERVICE_EDGE, db_update_lock=False): filters = {'appliance_size': [appliance_size], 'edge_type': [edge_type], 'status': [plugin_const.PENDING_CREATE, plugin_const.ACTIVE]} like_filters = {'router_id': vcns_const.BACKUP_ROUTER_PREFIX + "%"} return nsxv_db.get_nsxv_router_bindings( context.session, filters=filters, like_filters=like_filters) 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) def _check_backup_edge_pool(self, minimum_pooled_edges, maximum_pooled_edges, appliance_size=nsxv_constants.LARGE, edge_type=nsxv_constants.SERVICE_EDGE): """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) backup_num = len(backup_router_bindings) if backup_num > maximum_pooled_edges: self._delete_backup_edges_on_db( admin_ctx, backup_router_bindings[:backup_num - maximum_pooled_edges]) elif backup_num < minimum_pooled_edges: new_backup_num = backup_num router_ids = [] while (new_backup_num < minimum_pooled_edges): router_ids.extend( self._deploy_backup_edges_on_db( admin_ctx, 1, appliance_size=appliance_size, edge_type=edge_type)) new_backup_num = len( self._get_backup_edge_bindings( admin_ctx, appliance_size=appliance_size, edge_type=edge_type, db_update_lock=True)) if backup_num > maximum_pooled_edges: self._delete_backup_edges_at_backend( admin_ctx, backup_router_bindings[:backup_num - maximum_pooled_edges]) elif backup_num < minimum_pooled_edges: self._deploy_backup_edges_at_backend(admin_ctx, router_ids, appliance_size=appliance_size, edge_type=edge_type) def check_edge_active_at_backend(self, edge_id): try: status = self.nsxv_manager.get_edge_status(edge_id) return (status == vcns_const.RouterStatus.ROUTER_STATUS_ACTIVE) except Exception: return False def _get_available_router_binding(self, context, appliance_size=nsxv_constants.LARGE, edge_type=nsxv_constants.SERVICE_EDGE): backup_router_bindings = self._get_backup_edge_bindings( context, appliance_size=appliance_size, edge_type=edge_type) while backup_router_bindings: router_binding = random.choice(backup_router_bindings) if (router_binding['status'] == plugin_const.ACTIVE): if not self.check_edge_active_at_backend( router_binding['edge_id']): LOG.debug("Delete unavailable backup resource " "%(router_id)s with edge_id %(edge_id)s", {'router_id': router_binding['router_id'], 'edge_id': router_binding['edge_id']}) self._delete_edge(context, router_binding) else: LOG.debug("Get an available backup resource " "%(router_id)s with edge_id %(edge_id)s", {'router_id': router_binding['router_id'], 'edge_id': router_binding['edge_id']}) return router_binding backup_router_bindings.remove(router_binding) def _get_physical_provider_network(self, context, network_id): phy_net = nsxv_db.get_network_bindings(context.session, network_id) return (phy_net[0]['phy_uuid'] if ( phy_net and phy_net[0]['phy_uuid'] != '') else self.dvs_id) def _create_sub_interface(self, context, network_id, network_name, tunnel_index, address_groups, port_group_id=None): # Get the physical port group /wire id of the network id mappings = nsx_db.get_nsx_switch_ids(context.session, network_id) if mappings: vcns_network_id = mappings[0] else: LOG.error(_LE("Create sub interface failed since network %s not " "found at the backend."), network_id) raise nsx_exc.NsxPluginException( err_msg=_("network %s not found at the backend") % network_id) if port_group_id is None: portgroup = {'vlanId': 0, 'networkName': network_name, 'networkBindingType': 'Static', 'networkType': 'Isolation'} config_spec = {'networkSpec': portgroup} dvs_id = self._get_physical_provider_network(context, network_id) _, port_group_id = self.nsxv_manager.vcns.create_port_group( dvs_id, config_spec) interface = { 'name': _uuid(), 'tunnelId': tunnel_index, 'logicalSwitchId': vcns_network_id, 'isConnected': True } interface['addressGroups'] = {'addressGroups': address_groups} return port_group_id, interface def _getvnic_config(self, edge_id, vnic_index): _, vnic_config = self.nsxv_manager.get_interface(edge_id, vnic_index) return vnic_config def _delete_dhcp_internal_interface(self, context, edge_id, vnic_index, tunnel_index, network_id): """Delete the dhcp internal interface.""" LOG.debug("Query the vnic %s for DHCP Edge %s", vnic_index, edge_id) resource_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36] vnic_config = self._getvnic_config(edge_id, vnic_index) sub_interfaces = (vnic_config['subInterfaces']['subInterfaces'] if 'subInterfaces' in vnic_config else []) port_group_id = (vnic_config['portgroupId'] if 'portgroupId' in vnic_config else None) for sub_interface in sub_interfaces: if tunnel_index == sub_interface['tunnelId']: LOG.debug("Delete the tunnel %d on vnic %d", tunnel_index, vnic_index) (vnic_config['subInterfaces']['subInterfaces']. remove(sub_interface)) break # Clean the vnic if there is no sub-interface attached if len(sub_interfaces) == 0: header, _ = self.nsxv_manager.vcns.delete_interface(edge_id, vnic_index) if port_group_id: objuri = header['location'] job_id = objuri[objuri.rfind("/") + 1:] dvs_id = self._get_physical_provider_network(context, network_id) self.nsxv_manager.delete_portgroup(dvs_id, port_group_id, job_id) else: self.nsxv_manager.vcns.update_interface(edge_id, vnic_config) # Delete the router binding or clean the edge appliance bindings = nsxv_db.get_nsxv_router_bindings(context.session) all_dhcp_edges = {binding['router_id']: binding['edge_id'] for binding in bindings if binding['router_id']. startswith(vcns_const.DHCP_EDGE_PREFIX)} for router_id in all_dhcp_edges: if (router_id != resource_id and all_dhcp_edges[router_id] == edge_id): nsxv_db.delete_nsxv_router_binding(context.session, resource_id) return self._free_dhcp_edge_appliance(context, network_id) def _addr_groups_convert_to_ipset(self, address_groups): cidr_list = [] for addr_group in address_groups: cidr = "/".join([addr_group['primaryAddress'], addr_group['subnetPrefixLength']]) cidr_list.append(cidr) return netaddr.IPSet(cidr_list) def _update_dhcp_internal_interface(self, context, edge_id, vnic_index, tunnel_index, network_id, address_groups): """Update the dhcp internal interface: 1. Add a new vnic tunnel with the address groups 2. Update the address groups to an existing tunnel """ LOG.debug("Query the vnic %s for DHCP Edge %s", vnic_index, edge_id) _, vnic_config = self.nsxv_manager.get_interface(edge_id, vnic_index) sub_iface_dict = vnic_config.get('subInterfaces') port_group_id = vnic_config.get('portgroupId') new_tunnel_creation = True iface_list = [] # Update the sub interface address groups for specific tunnel if sub_iface_dict: sub_interfaces = sub_iface_dict.get('subInterfaces') addr_groups_ipset = self._addr_groups_convert_to_ipset( address_groups) for sb in sub_interfaces: if tunnel_index == sb['tunnelId']: new_tunnel_creation = False sb['addressGroups']['addressGroups'] = address_groups else: sb_ipset = self._addr_groups_convert_to_ipset( sb['addressGroups']['addressGroups']) if addr_groups_ipset & sb_ipset: ls_id = sb['logicalSwitchId'] net_ids = nsx_db.get_net_ids(context.session, ls_id) if net_ids: # Here should never happen, else one bug occurs LOG.error(_LE("net %(id)s on edge %(edge_id)s " "overlaps with new net %(net_id)s"), {'id': net_ids[0], 'edge_id': edge_id, 'net_id': network_id}) raise nsx_exc.NsxPluginException( err_msg=_("update dhcp interface for net %s " "failed") % network_id) else: # Occurs when there are DB inconsistency sb["is_overlapped"] = True LOG.error(_LE("unexpected sub intf %(id)s on edge " "%(edge_id)s overlaps with new net " "%(net_id)s. we would update with " "deleting it for DB consistency"), {'id': net_ids[0], 'edge_id': edge_id, 'net_id': network_id}) iface_list = [sub for sub in sub_interfaces if not sub.get('is_overlapped', False)] # The first DHCP service creation, not update if new_tunnel_creation: network_name_item = [edge_id, str(vnic_index), str(tunnel_index)] network_name = ('-'.join(network_name_item) + _uuid())[:36] port_group_id, iface = self._create_sub_interface( context, network_id, network_name, tunnel_index, address_groups, port_group_id) iface_list.append(iface) LOG.debug("Update the vnic %d for DHCP Edge %s", vnic_index, edge_id) self.nsxv_manager.update_interface('fake_router_id', edge_id, vnic_index, port_group_id, tunnel_index, address_groups=iface_list) @vcns.retry_upon_exception(db_base_exc.OperationalError, max_delay=10) def _allocate_edge_appliance(self, context, resource_id, name, appliance_size=nsxv_constants.LARGE, dist=False): """Try to allocate one avaliable edge from pool.""" edge_type = (nsxv_constants.VDR_EDGE if dist else nsxv_constants.SERVICE_EDGE) lrouter = {'id': resource_id, 'name': name} edge_pool_range = self.edge_pool_dicts[edge_type].get(appliance_size) if edge_pool_range is None: nsxv_db.add_nsxv_router_binding( context.session, resource_id, None, None, plugin_const.PENDING_CREATE, appliance_size=appliance_size, edge_type=edge_type) task = self._deploy_edge(context, lrouter, appliance_size=appliance_size, edge_type=edge_type) task.wait(task_const.TaskState.RESULT) return with locking.LockManager.get_lock( 'nsx-edge-request', lock_file_prefix='get-'): self._clean_all_error_edge_bindings(context) available_router_binding = self._get_available_router_binding( context, appliance_size=appliance_size, edge_type=edge_type) # Synchronously deploy an edge if no avaliable 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) task = self._deploy_edge(context, lrouter, appliance_size=appliance_size, edge_type=edge_type) task.wait(task_const.TaskState.RESULT) else: LOG.debug("Select edge: %(edge_id)s from pool for %(name)s", {'edge_id': available_router_binding['edge_id'], 'name': name}) # select the first avaliable edge in pool. nsxv_db.delete_nsxv_router_binding( context.session, available_router_binding['router_id']) nsxv_db.add_nsxv_router_binding( context.session, lrouter['id'], available_router_binding['edge_id'], None, plugin_const.PENDING_CREATE, appliance_size=appliance_size, edge_type=edge_type) LOG.debug("Select edge: %(edge_id)s from pool for %(name)s", {'edge_id': available_router_binding['edge_id'], 'name': name}) fake_jobdata = { 'context': context, 'router_id': lrouter['id']} fake_userdata = {'jobdata': fake_jobdata, 'router_name': lrouter['name'], 'edge_id': available_router_binding['edge_id'], 'dist': dist} fake_task = tasks.Task(name='fake-deploy-edge-task', resource_id='fake-resource_id', execute_callback=None, userdata=fake_userdata) fake_task.status = task_const.TaskStatus.COMPLETED self.nsxv_manager.callbacks.edge_deploy_result(fake_task) # change edge's name at backend task = self.nsxv_manager.update_edge( resource_id, available_router_binding['edge_id'], name, None, appliance_size=appliance_size, dist=dist) 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)) 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) self._deploy_backup_edges_at_backend( context, router_ids, appliance_size=appliance_size, edge_type=edge_type) def _free_edge_appliance(self, context, router_id): """Try to collect one edge to pool.""" binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) if not binding: LOG.warning(_LW("router binding for router: %s " "not found"), router_id) return dist = (binding['edge_type'] == nsxv_constants.VDR_EDGE) edge_pool_range = self.edge_pool_dicts[binding['edge_type']].get( binding['appliance_size']) if (not self.check_edge_active_at_backend(binding['edge_id']) or not edge_pool_range): nsxv_db.update_nsxv_router_binding( context.session, router_id, status=plugin_const.PENDING_DELETE) # delete edge jobdata = { 'context': context, 'router_id': router_id } self.nsxv_manager.delete_edge( router_id, binding['edge_id'], jobdata=jobdata, dist=dist) return with locking.LockManager.get_lock( 'nsx-edge-request', lock_file_prefix='get-'): self._clean_all_error_edge_bindings(context) backup_router_bindings = self._get_backup_edge_bindings( context, appliance_size=binding['appliance_size'], edge_type=binding['edge_type']) backup_num = len(backup_router_bindings) # collect the edge to pool if pool not full if backup_num < edge_pool_range['maximum_pooled_edges']: LOG.debug("Collect edge: %s to pool", binding['edge_id']) nsxv_db.delete_nsxv_router_binding( context.session, router_id) backup_router_id = (vcns_const.BACKUP_ROUTER_PREFIX + _uuid())[:vcns_const.EDGE_NAME_LEN] nsxv_db.add_nsxv_router_binding( context.session, backup_router_id, binding['edge_id'], None, plugin_const.ACTIVE, appliance_size=binding['appliance_size'], edge_type=binding['edge_type']) # change edge's name at backend task = self.nsxv_manager.update_edge( router_id, binding['edge_id'], backup_router_id, None, appliance_size=binding['appliance_size'], dist=dist) task.wait(task_const.TaskState.RESULT) # Refresh edge_vnic_bindings if not dist and binding['edge_id']: nsxv_db.clean_edge_vnic_binding( context.session, binding['edge_id']) nsxv_db.init_edge_vnic_binding( context.session, binding['edge_id']) else: nsxv_db.update_nsxv_router_binding( context.session, router_id, status=plugin_const.PENDING_DELETE) # delete edge jobdata = { 'context': context, 'router_id': router_id } self.nsxv_manager.delete_edge( router_id, binding['edge_id'], jobdata=jobdata, dist=dist) def _allocate_dhcp_edge_appliance(self, context, resource_id): 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']) def _free_dhcp_edge_appliance(self, context, network_id): router_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36] self._free_edge_appliance(context, router_id) def create_lrouter( self, context, lrouter, lswitch=None, dist=False, appliance_size=vcns_const.SERVICE_SIZE_MAPPING['router']): """Create an edge for logical router support.""" router_name = lrouter['name'] + '-' + lrouter['id'] self._allocate_edge_appliance( context, lrouter['id'], router_name, appliance_size=appliance_size, dist=dist) def delete_lrouter(self, context, router_id, dist=False): self._free_edge_appliance(context, router_id) def update_dhcp_edge_bindings(self, context, network_id): """Reconfigure the DHCP to the edge.""" resource_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36] edge_binding = nsxv_db.get_nsxv_router_binding(context.session, resource_id) if not edge_binding: return with locking.LockManager.get_lock( str(edge_binding['edge_id']), lock_file_prefix='nsxv-dhcp-config-'): self.update_dhcp_service_config(context, edge_binding['edge_id']) def update_dhcp_service_config(self, context, edge_id): """Reconfigure the DHCP to the edge.""" # Get all networks attached to the edge edge_vnic_bindings = nsxv_db.get_edge_vnic_bindings_by_edge( context.session, edge_id) dhcp_networks = [edge_vnic_binding.network_id for edge_vnic_binding in edge_vnic_bindings] ports = self.nsxv_plugin.get_ports( context.elevated(), filters={'network_id': dhcp_networks}) inst_ports = [port for port in ports if port['device_owner'].startswith("compute")] static_bindings = [] for port in inst_ports: static_bindings.extend( self.nsxv_plugin._create_static_binding( context.elevated(), port)) dhcp_request = { 'featureType': "dhcp_4.0", 'enabled': True, 'staticBindings': {'staticBindings': static_bindings}} self.nsxv_manager.vcns.reconfigure_dhcp_service( edge_id, dhcp_request) bindings_get = get_dhcp_binding_mappings(self.nsxv_manager, edge_id) # Refresh edge_dhcp_static_bindings attached to edge nsxv_db.clean_edge_dhcp_static_bindings_by_edge( context.session, edge_id) for mac_address, binding_id in bindings_get.items(): nsxv_db.create_edge_dhcp_static_binding(context.session, edge_id, mac_address, binding_id) def _get_vdr_dhcp_edges(self, context): bindings = nsxv_db.get_vdr_dhcp_bindings(context.session) edges = [binding['dhcp_edge_id'] for binding in bindings] return edges def _get_available_edges(self, context, network_id, conflicting_nets): if conflicting_nets is None: conflicting_nets = [] conflict_edge_ids = [] available_edge_ids = [] router_bindings = nsxv_db.get_nsxv_router_bindings(context.session) 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 binding['status'] == plugin_const.ACTIVE)} vdr_dhcp_edges = self._get_vdr_dhcp_edges(context) if all_dhcp_edges: for dhcp_edge_id in set(all_dhcp_edges.values()): edge_vnic_bindings = nsxv_db.get_edge_vnic_bindings_by_edge( context.session, dhcp_edge_id) free_number = ((vcns_const.MAX_VNIC_NUM - 1) * vcns_const.MAX_TUNNEL_NUM - len(edge_vnic_bindings)) # metadata internal network will use one vnic if free_number <= (vcns_const.MAX_TUNNEL_NUM - 1): conflict_edge_ids.append(dhcp_edge_id) for net_id in conflicting_nets: router_id = (vcns_const.DHCP_EDGE_PREFIX + net_id)[:36] edge_id = all_dhcp_edges.get(router_id) if (edge_id and edge_id not in conflict_edge_ids): conflict_edge_ids.append(edge_id) for x in all_dhcp_edges.values(): if (x not in conflict_edge_ids and x not in available_edge_ids and x not in vdr_dhcp_edges): available_edge_ids.append(x) return (conflict_edge_ids, available_edge_ids) def _get_used_edges(self, context, subnet): """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) def remove_network_from_dhcp_edge(self, context, network_id, edge_id): old_binding = nsxv_db.get_edge_vnic_binding( context.session, edge_id, network_id) if not old_binding: LOG.error(_LE("Remove network %(id)s failed since no binding " "found on edge %(edge_id)s"), {'id': network_id, 'edge_id': edge_id}) return old_vnic_index = old_binding['vnic_index'] old_tunnel_index = old_binding['tunnel_index'] # Cut off the port group/virtual wire connection nsxv_db.free_edge_vnic_by_network(context.session, edge_id, network_id) # update dhcp service config on edge_id self.update_dhcp_service_config(context, edge_id) try: self._delete_dhcp_internal_interface( context, edge_id, old_vnic_index, old_tunnel_index, network_id) except Exception: with excutils.save_and_reraise_exception(): LOG.exception( _('Failed to delete vnic %(vnic_index)d ' 'tunnel %(tunnel_index)d on edge ' '%(edge_id)s'), {'vnic_index': old_vnic_index, 'tunnel_index': old_tunnel_index, 'edge_id': edge_id}) def reuse_existing_dhcp_edge(self, context, edge_id, resource_id, network_id): app_size = vcns_const.SERVICE_SIZE_MAPPING['dhcp'] nsxv_db.add_nsxv_router_binding( context.session, resource_id, edge_id, None, plugin_const.ACTIVE, appliance_size=app_size) 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) with locking.LockManager.get_lock( 'nsx-edge-pool', lock_file_prefix='edge-bind-'): new_edge = nsxv_db.get_nsxv_router_binding(context.session, resource_id) nsxv_db.allocate_edge_vnic_with_tunnel_index( context.session, new_edge['edge_id'], network_id) return new_edge['edge_id'] def create_dhcp_edge_service(self, context, network_id, subnet): """ Create an edge if there is no available edge for dhcp service, Update an edge if there is available edge for dhcp service If new edge was allocated, return resource_id, else return None """ # 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, resource_id) allocate_new_edge = False # case 1: update a subnet to an existing dhcp edge if dhcp_edge_binding: with locking.LockManager.get_lock( 'nsx-edge-pool', lock_file_prefix='edge-bind-'): edge_id = dhcp_edge_binding['edge_id'] (conflict_edge_ids, available_edge_ids) = self._get_used_edges(context, subnet) LOG.debug("The available edges %s, the conflict edges %s " "at present is using edge %s", available_edge_ids, conflict_edge_ids, edge_id) with locking.LockManager.get_lock( str(edge_id), lock_file_prefix='nsxv-dhcp-config-'): # Delete the existing vnic interface if there is # and overlapping subnet if edge_id in conflict_edge_ids: self.remove_network_from_dhcp_edge(context, network_id, edge_id) #Move the network to anther Edge and update vnic: #1. Find an available existing edge or create a new one #2. For the existing one, cut off the old port group # connection #3. Create the new port group connection to an existing # one #4. Update the address groups to the vnic if available_edge_ids: new_id = random.choice(available_edge_ids) LOG.debug("Select edge %s to support dhcp for " "network %s", new_id, network_id) self.reuse_existing_dhcp_edge( context, new_id, resource_id, network_id) else: allocate_new_edge = True # case 2: attach the subnet to a new edge and update vnic else: with locking.LockManager.get_lock( 'nsx-edge-pool', lock_file_prefix='edge-bind-'): (conflict_edge_ids, available_edge_ids) = self._get_used_edges(context, subnet) LOG.debug('The available edges %s, the conflict edges %s', available_edge_ids, conflict_edge_ids) # There is available one if available_edge_ids: new_id = random.choice(available_edge_ids) LOG.debug("Select edge %s to support dhcp for network %s", new_id, network_id) self.reuse_existing_dhcp_edge( context, new_id, resource_id, network_id) else: allocate_new_edge = True if allocate_new_edge: self.allocate_new_dhcp_edge(context, network_id, resource_id) # If a new Edge was allocated, return resource_id return resource_id def update_dhcp_edge_service(self, context, network_id, address_groups=None): """Update the subnet to the dhcp edge vnic.""" if address_groups is None: address_groups = [] resource_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36] edge_binding = nsxv_db.get_nsxv_router_binding(context.session, resource_id) dhcp_binding = nsxv_db.get_edge_vnic_binding(context.session, edge_binding['edge_id'], network_id) if dhcp_binding: edge_id = dhcp_binding['edge_id'] vnic_index = dhcp_binding['vnic_index'] tunnel_index = dhcp_binding['tunnel_index'] LOG.debug('Update the dhcp service for %s on vnic %d tunnel %d', edge_id, vnic_index, tunnel_index) try: with locking.LockManager.get_lock( str(edge_id), lock_file_prefix='nsxv-dhcp-config-'): self._update_dhcp_internal_interface( context, edge_id, vnic_index, tunnel_index, network_id, address_groups) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Failed to update the dhcp service for ' '%(edge_id)s on vnic %(vnic_index)d ' 'tunnel %(tunnel_index)d'), {'edge_id': edge_id, 'vnic_index': vnic_index, 'tunnel_index': tunnel_index}) with locking.LockManager.get_lock( str(edge_id), lock_file_prefix='nsxv-dhcp-config-'): ports = self.nsxv_plugin.get_ports( context, filters={'network_id': [network_id]}) inst_ports = [port for port in ports if port['device_owner'].startswith("compute")] if inst_ports: # update dhcp service config for the new added network self.update_dhcp_service_config(context, edge_id) def delete_dhcp_edge_service(self, context, network_id): """Delete an edge for dhcp service.""" resource_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36] edge_binding = nsxv_db.get_nsxv_router_binding(context.session, resource_id) if edge_binding: dhcp_binding = nsxv_db.get_edge_vnic_binding( context.session, edge_binding['edge_id'], network_id) if dhcp_binding: edge_id = dhcp_binding['edge_id'] vnic_index = dhcp_binding['vnic_index'] tunnel_index = dhcp_binding['tunnel_index'] LOG.debug("Delete the tunnel %d on vnic %d from DHCP Edge %s", tunnel_index, vnic_index, edge_id) nsxv_db.free_edge_vnic_by_network(context.session, edge_id, network_id) try: with locking.LockManager.get_lock( str(edge_id), lock_file_prefix='nsxv-dhcp-config-'): self._delete_dhcp_internal_interface(context, edge_id, vnic_index, tunnel_index, network_id) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Failed to delete the tunnel ' '%(tunnel_index)d on vnic ' '%(vnic_index)d' 'from DHCP Edge %(edge_id)s'), {'tunnel_index': tunnel_index, 'vnic_index': vnic_index, 'edge_id': edge_id}) 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, resource_id) if dhcp_edge_binding: with locking.LockManager.get_lock( 'nsx-edge-pool', lock_file_prefix='edge-bind-'): edge_id = dhcp_edge_binding['edge_id'] with locking.LockManager.get_lock( str(edge_id), lock_file_prefix='nsxv-dhcp-config-'): self.remove_network_from_dhcp_edge(context, network_id, edge_id) # Find DHCP Edge which is associated with this VDR vdr_dhcp_binding = nsxv_db.get_vdr_dhcp_binding_by_vdr( context.session, vdr_router_id) if vdr_dhcp_binding: dhcp_edge_id = vdr_dhcp_binding['dhcp_edge_id'] self.reuse_existing_dhcp_edge( context, dhcp_edge_id, resource_id, network_id) else: # Attach to DHCP Edge dhcp_edge_id = self.allocate_new_dhcp_edge( context, network_id, resource_id) self.plugin.metadata_proxy_handler.configure_router_edge( resource_id, context) self.plugin.setup_dhcp_edge_fw_rules( context, self.plugin, resource_id) if not self.per_interface_rp_filter: with locking.LockManager.get_lock( 'nsx-edge-pool', lock_file_prefix='edge-bind-'): self.nsxv_manager.vcns.set_system_control( dhcp_edge_id, [RP_FILTER_PROPERTY_OFF_TEMPLATE % ('all', '0')]) nsxv_db.add_vdr_dhcp_binding(context.session, vdr_router_id, dhcp_edge_id) address_groups = self.plugin._create_network_dhcp_address_group( context, network_id) self.update_dhcp_edge_service( context, network_id, address_groups=address_groups) self.set_sysctl_rp_filter_for_vdr_dhcp( context, dhcp_edge_id, network_id) def _get_sub_interface_id(self, context, edge_id, network_id): vnic_binding = nsxv_db.get_edge_vnic_binding( context.session, edge_id, network_id) if vnic_binding: _, vnic_config = self.nsxv_manager.get_interface( edge_id, vnic_binding.vnic_index) sub_iface_dict = vnic_config.get('subInterfaces') if sub_iface_dict: sub_interfaces = sub_iface_dict.get('subInterfaces', []) for sub_interface in sub_interfaces: if sub_interface['tunnelId'] == vnic_binding.tunnel_index: return sub_interface['index'] def set_sysctl_rp_filter_for_vdr_dhcp(self, context, edge_id, network_id): if not self.per_interface_rp_filter: return vnic_index = self._get_sub_interface_id(context, edge_id, network_id) if vnic_index: vnic_id = 'vNic_%d' % vnic_index with locking.LockManager.get_lock( str(edge_id), lock_file_prefix='nsxv-dhcp-config-'): sysctl_props = [] h, sysctl = self.nsxv_manager.vcns.get_system_control(edge_id) if sysctl: sysctl_props = sysctl['property'] sysctl_props.append( RP_FILTER_PROPERTY_OFF_TEMPLATE % (vnic_id, '0')) self.nsxv_manager.vcns.set_system_control( edge_id, sysctl_props) def reset_sysctl_rp_filter_for_vdr_dhcp(self, context, edge_id, network_id): if not self.per_interface_rp_filter: return vnic_index = self._get_sub_interface_id(context, edge_id, network_id) if vnic_index: vnic_id = 'vNic_%d' % vnic_index with locking.LockManager.get_lock( str(edge_id), lock_file_prefix='nsxv-dhcp-config-'): h, sysctl = self.nsxv_manager.vcns.get_system_control(edge_id) if sysctl: sysctl_props = sysctl['property'] sysctl_props.remove( RP_FILTER_PROPERTY_OFF_TEMPLATE % (vnic_id, '0')) sysctl_props.append( RP_FILTER_PROPERTY_OFF_TEMPLATE % (vnic_id, '1')) self.nsxv_manager.vcns.set_system_control( edge_id, sysctl_props) def get_plr_by_tlr_id(self, context, router_id): lswitch_id = nsxv_db.get_nsxv_router_binding( context.session, router_id).lswitch_id if lswitch_id: edge_vnic_bindings = nsxv_db.get_edge_vnic_bindings_by_int_lswitch( context.session, lswitch_id) if edge_vnic_bindings: for edge_vnic_binding in edge_vnic_bindings: plr_router_id = nsxv_db.get_nsxv_router_bindings_by_edge( context.session, edge_vnic_binding.edge_id)[0].router_id if plr_router_id != router_id: return plr_router_id def create_plr_with_tlr_id(self, context, router_id, router_name): # Add an internal network preparing for connecting the VDR # to a PLR tlr_edge_id = nsxv_db.get_nsxv_router_binding( context.session, router_id).edge_id # First create an internal lswitch lswitch_name = ('int-' + router_name + router_id)[:36] virtual_wire = {"name": lswitch_name, "tenantId": "virtual wire tenant"} config_spec = {"virtualWireCreateSpec": virtual_wire} vdn_scope_id = cfg.CONF.nsxv.vdn_scope_id h, lswitch_id = self.nsxv_manager.vcns.create_virtual_wire( vdn_scope_id, config_spec) # add vdr's external interface to the lswitch tlr_vnic_index = self.nsxv_manager.add_vdr_internal_interface( tlr_edge_id, lswitch_id, address=vcns_const.INTEGRATION_LR_IPADDRESS.split('/')[0], netmask=vcns_const.INTEGRATION_SUBNET_NETMASK, type="uplink") nsxv_db.create_edge_vnic_binding( context.session, tlr_edge_id, tlr_vnic_index, lswitch_id) # store the lswitch_id into nsxv_router_binding nsxv_db.update_nsxv_router_binding( context.session, router_id, lswitch_id=lswitch_id) # Handle plr relative op plr_router = {'name': router_name, 'id': (vcns_const.PLR_EDGE_PREFIX + _uuid())[:36]} self.create_lrouter(context, plr_router) binding = nsxv_db.get_nsxv_router_binding( context.session, plr_router['id']) plr_edge_id = binding['edge_id'] plr_vnic_index = nsxv_db.allocate_edge_vnic( context.session, plr_edge_id, lswitch_id).vnic_index #TODO(berlin): the internal ip should change based on vnic_index self.nsxv_manager.update_interface( plr_router['id'], plr_edge_id, plr_vnic_index, lswitch_id, address=vcns_const.INTEGRATION_EDGE_IPADDRESS, netmask=vcns_const.INTEGRATION_SUBNET_NETMASK) return plr_router['id'] def delete_plr_by_tlr_id(self, context, plr_id, router_id): # Delete plr's internal interface which connects to internal switch tlr_binding = nsxv_db.get_nsxv_router_binding( context.session, router_id) lswitch_id = tlr_binding.lswitch_id tlr_edge_id = tlr_binding.edge_id plr_edge_id = nsxv_db.get_nsxv_router_binding( context.session, plr_id).edge_id plr_vnic_index = nsxv_db.get_edge_vnic_binding( context.session, plr_edge_id, lswitch_id).vnic_index # Clear static routes before delete internal vnic task = self.nsxv_manager.update_routes( plr_id, plr_edge_id, None, []) task.wait(task_const.TaskState.RESULT) # Delete internal vnic task = self.nsxv_manager.delete_interface( plr_id, plr_edge_id, plr_vnic_index) task.wait(task_const.TaskState.RESULT) nsxv_db.free_edge_vnic_by_network( context.session, plr_edge_id, lswitch_id) # Delete the PLR self.delete_lrouter(context, plr_id) # Clear static routes of vdr task = self.nsxv_manager.update_routes( router_id, tlr_edge_id, None, []) task.wait(task_const.TaskState.RESULT) #First delete the vdr's external interface tlr_vnic_index = nsxv_db.get_edge_vnic_binding( context.session, tlr_edge_id, lswitch_id).vnic_index self.nsxv_manager.delete_vdr_internal_interface( tlr_edge_id, tlr_vnic_index) nsxv_db.delete_edge_vnic_binding_by_network( context.session, tlr_edge_id, lswitch_id) try: # Then delete the internal lswitch self.nsxv_manager.vcns.delete_virtual_wire(lswitch_id) except Exception: LOG.warning(_LW("Failed to delete virtual wire: %s"), lswitch_id) def get_routers_on_same_edge(self, context, router_id): edge_binding = nsxv_db.get_nsxv_router_binding( context.session, router_id) if edge_binding: return [ binding['router_id'] for binding in nsxv_db.get_nsxv_router_bindings_by_edge( context.session, edge_binding['edge_id'])] else: return [] def bind_router_on_available_edge( self, context, target_router_id, optional_router_ids, conflict_router_ids, conflict_network_ids, network_number): """Bind logical router on an available edge. Return True if the logical router is bound to a new edge. """ with locking.LockManager.get_lock( "edge-router", lock_file_prefix="bind-"): optional_edge_ids = [] conflict_edge_ids = [] for router_id in optional_router_ids: binding = nsxv_db.get_nsxv_router_binding( context.session, router_id) if (binding and binding.status == plugin_const.ACTIVE and binding.edge_id not in optional_edge_ids): optional_edge_ids.append(binding.edge_id) for router_id in conflict_router_ids: binding = nsxv_db.get_nsxv_router_binding( context.session, router_id) if binding and binding.edge_id not in conflict_edge_ids: conflict_edge_ids.append(binding.edge_id) optional_edge_ids = list( set(optional_edge_ids) - set(conflict_edge_ids)) max_net_number = 0 available_edge_id = None for edge_id in optional_edge_ids: edge_vnic_bindings = nsxv_db.get_edge_vnic_bindings_by_edge( context.session, edge_id) # one vnic is used to provide external access. net_number = ( vcns_const.MAX_VNIC_NUM - len(edge_vnic_bindings) - 1) if (net_number > max_net_number and net_number >= network_number): net_ids = [vnic_binding.network_id for vnic_binding in edge_vnic_bindings] if not (set(conflict_network_ids) & set(net_ids)): max_net_number = net_number available_edge_id = edge_id else: # TODO(yangyu): Remove conflict_network_ids LOG.exception( _LE("Failed to query conflict_router_ids")) if available_edge_id: edge_binding = nsxv_db.get_nsxv_router_bindings_by_edge( context.session, available_edge_id)[0] nsxv_db.add_nsxv_router_binding( context.session, target_router_id, edge_binding.edge_id, None, edge_binding.status, edge_binding.appliance_size, edge_binding.edge_type) 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']) return True def unbind_router_on_edge(self, context, router_id): """Unbind a logical router from edge. Return True if no logical router bound to the edge. """ with locking.LockManager.get_lock( "edge-router", lock_file_prefix="bind-"): # free edge if no other routers bound to the edge router_ids = self.get_routers_on_same_edge(context, router_id) if router_ids == [router_id]: self._free_edge_appliance(context, router_id) return True else: nsxv_db.delete_nsxv_router_binding(context.session, router_id) def is_router_conflict_on_edge(self, context, router_id, conflict_router_ids, conflict_network_ids, intf_num=0): with locking.LockManager.get_lock( "edge-router", lock_file_prefix="bind-"): router_ids = self.get_routers_on_same_edge(context, router_id) if set(router_ids) & set(conflict_router_ids): return True router_binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) edge_vnic_bindings = nsxv_db.get_edge_vnic_bindings_by_edge( context.session, router_binding.edge_id) if (vcns_const.MAX_VNIC_NUM - len(edge_vnic_bindings ) - 1 < intf_num): LOG.debug("There isn't available edge vnic for the router: %s", router_id) return True for binding in edge_vnic_bindings: if binding.network_id in conflict_network_ids: return True return False def create_lrouter(nsxv_manager, context, lrouter, lswitch=None, dist=False): """Create an edge for logical router support.""" router_id = lrouter['id'] router_name = lrouter['name'] + '-' + router_id appliance_size = vcns_const.SERVICE_SIZE_MAPPING['router'] # store router-edge mapping binding nsxv_db.add_nsxv_router_binding( context.session, router_id, None, None, plugin_const.PENDING_CREATE, appliance_size=appliance_size) # deploy edge jobdata = { 'router_id': router_id, 'lrouter': lrouter, 'lswitch': lswitch, 'context': context } # deploy and wait until the deploy request has been requested # so we will have edge_id ready. The wait here should be fine # 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) task.wait(task_const.TaskState.RESULT) def delete_lrouter(nsxv_manager, context, router_id, dist=False): binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) if binding: nsxv_db.update_nsxv_router_binding( context.session, router_id, status=plugin_const.PENDING_DELETE) edge_id = binding['edge_id'] # delete edge jobdata = { 'context': context } task = nsxv_manager.delete_edge(router_id, edge_id, jobdata=jobdata, dist=dist) task.wait(task_const.TaskState.RESULT) else: LOG.warning(_LW("router binding for router: %s not found"), router_id) def create_dhcp_service(context, nsxv_manager, network): """Create an Edge for dhcp service.""" edge_name = "%s-%s" % (network['name'], network['id']) jobdata = {'network_id': network['id'], 'context': context} # port group id for vlan or virtual wire id for vxlan nsx_network_id = nsx_db.get_nsx_switch_ids(context.session, network['id'])[0] # Deploy an Edge for dhcp service return nsxv_manager.deploy_edge( network['id'], edge_name, nsx_network_id, jobdata=jobdata, appliance_size=vcns_const.SERVICE_SIZE_MAPPING['dhcp']) def delete_dhcp_service(context, nsxv_manager, network_id): """Delete the Edge of dhcp service.""" task = None binding = nsxv_db.get_dhcp_edge_network_binding(context.session, network_id) if binding: dhcp_edge_id = binding['edge_id'] vnic_index = binding['vnic_index'] jobdata = {'context': context, 'network_id': network_id} edge_id = dhcp_edge_id LOG.debug("Delete the vnic %d from DHCP Edge %s", vnic_index, edge_id) nsxv_manager.vcns.delete_interface(edge_id, vnic_index) nsxv_db.free_edge_vnic_by_network( context.session, edge_id, network_id) LOG.debug("Delete the DHCP Edge service %s", edge_id) task = nsxv_manager.delete_edge(network_id, edge_id, jobdata) return task def get_dhcp_edge_id(context, network_id): # Query edge id resource_id = (vcns_const.DHCP_EDGE_PREFIX + network_id)[:36] binding = nsxv_db.get_nsxv_router_binding(context.session, resource_id) if binding: edge_id = binding['edge_id'] return edge_id def create_dhcp_bindings(context, nsxv_manager, network_id, bindings): edge_id = get_dhcp_edge_id(context, network_id) if edge_id: with locking.LockManager.get_lock( str(edge_id), lock_file_prefix='nsxv-dhcp-config-'): for binding in bindings: nsxv_manager.vcns.create_dhcp_binding(edge_id, binding) bindings_get = get_dhcp_binding_mappings(nsxv_manager, edge_id) mac_address_list = [binding['macAddress'] for binding in bindings] for mac_address in mac_address_list: binding_id = bindings_get.get(mac_address) if binding_id: nsxv_db.create_edge_dhcp_static_binding( context.session, edge_id, mac_address, binding_id) else: LOG.warning(_LW("Unable to configure binding for: %s"), mac_address) else: LOG.warning(_LW("Failed to create dhcp bindings since dhcp edge " "for net %s not found at the backend"), network_id) def get_dhcp_binding_mappings(nsxv_manager, edge_id): dhcp_config = query_dhcp_service_config(nsxv_manager, edge_id) bindings_get = {} if dhcp_config: for binding in dhcp_config['staticBindings']['staticBindings']: bindings_get[binding['macAddress'].lower()] = binding['bindingId'] return bindings_get def delete_dhcp_binding(context, nsxv_manager, network_id, mac_address): edge_id = get_dhcp_edge_id(context, network_id) dhcp_binding = nsxv_db.get_edge_dhcp_static_binding( context.session, edge_id, mac_address) if edge_id and dhcp_binding: with locking.LockManager.get_lock( str(edge_id), lock_file_prefix='nsxv-dhcp-config-'): nsxv_manager.vcns.delete_dhcp_binding( edge_id, dhcp_binding.binding_id) nsxv_db.delete_edge_dhcp_static_binding( context.session, edge_id, mac_address) def query_dhcp_service_config(nsxv_manager, edge_id): """Retrieve the current DHCP configuration from the edge.""" _, dhcp_config = nsxv_manager.vcns.query_dhcp_configuration(edge_id) return dhcp_config def update_dhcp_internal_interface(context, nsxv_manager, network_id, address_groups, add=True): # Get the physical port group /wire id of the network id mappings = nsx_db.get_nsx_switch_ids(context.session, network_id) if mappings: vcns_network_id = mappings[0] # Get the DHCP Edge to update the internal interface binding = nsxv_db.get_dhcp_edge_network_binding(context.session, network_id) if binding: dhcp_edge_id = binding['edge_id'] vnic_index = binding['vnic_index'] edge_id = dhcp_edge_id LOG.debug("Query the vnic %s for DHCP Edge %s", vnic_index, edge_id) _, vnic_config = nsxv_manager.get_interface(edge_id, vnic_index) for addr_group in address_groups: vnic_addr_grp = vnic_config['addressGroups']['addressGroups'] if add: vnic_addr_grp.append(addr_group) else: if addr_group in vnic_addr_grp: vnic_addr_grp.remove(addr_group) LOG.debug("Update the vnic %d for DHCP Edge %s", vnic_index, edge_id) nsxv_manager.update_interface( 'fake_router_id', edge_id, vnic_index, vcns_network_id, address_groups=vnic_config['addressGroups']['addressGroups']) def get_router_edge_id(context, router_id): binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) if binding: return binding['edge_id'] def update_gateway(nsxv_manager, context, router_id, nexthop, routes=None): binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) edge_id = binding['edge_id'] if routes is None: routes = [] task = nsxv_manager.update_routes(router_id, edge_id, nexthop, routes) task.wait(task_const.TaskState.RESULT) def get_routes(edge_manager, context, router_id): binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) edge_id = binding['edge_id'] vnic_bindings = nsxv_db.get_edge_vnic_bindings_by_edge(context.session, edge_id) h, routes = edge_manager.vcns.get_routes(edge_id) edge_routes = routes.get('staticRoutes') routes = [] for edge_route in edge_routes.get('staticRoutes'): for vnic_binding in vnic_bindings: if vnic_binding['vnic_index'] == int(edge_route['vnic']): route = {'network_id': vnic_binding['network_id'], 'nexthop': edge_route['nextHop'], 'destination': edge_route['network']} routes.append(route) break return routes def update_routes(edge_manager, context, router_id, routes, nexthop=None, gateway_vnic_index=vcns_const.EXTERNAL_VNIC_INDEX): binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) edge_id = binding['edge_id'] edge_routes = [] for route in routes: if not route.get('network_id'): LOG.warning(_LW("There is no network info for the route %s, so " "the route entry would not be executed!"), route) continue if route.get('external'): edge_routes.append({ 'vnic_index': vcns_const.EXTERNAL_VNIC_INDEX, 'cidr': route['destination'], 'nexthop': route['nexthop']}) else: edge_routes.append({ 'vnic_index': nsxv_db.get_edge_vnic_binding( context.session, edge_id, route['network_id'])['vnic_index'], 'cidr': route['destination'], 'nexthop': route['nexthop']}) task = edge_manager.update_routes(router_id, edge_id, nexthop, edge_routes, gateway_vnic_index=gateway_vnic_index) task.wait(task_const.TaskState.RESULT) def get_internal_lswitch_id_of_plr_tlr(context, router_id): return nsxv_db.get_nsxv_router_binding( context.session, router_id).lswitch_id def get_internal_vnic_index_of_plr_tlr(context, router_id): router_binding = nsxv_db.get_nsxv_router_binding( context.session, router_id) edge_vnic_binding = nsxv_db.get_edge_vnic_binding( context.session, router_binding.edge_id, router_binding.lswitch_id) return edge_vnic_binding.vnic_index def clear_gateway(nsxv_manager, context, router_id): return update_gateway(nsxv_manager, context, router_id, None) def update_external_interface( nsxv_manager, context, router_id, ext_net_id, ipaddr, netmask, secondary=None): secondary = secondary or [] binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) net_bindings = nsxv_db.get_network_bindings(context.session, ext_net_id) if not net_bindings: vcns_network_id = nsxv_manager.external_network else: vcns_network_id = net_bindings[0].phy_uuid nsxv_manager.update_interface(router_id, binding['edge_id'], vcns_const.EXTERNAL_VNIC_INDEX, vcns_network_id, address=ipaddr, netmask=netmask, secondary=secondary) def update_internal_interface(nsxv_manager, context, router_id, int_net_id, address_groups, is_connected=True): # Get the pg/wire id of the network id mappings = nsx_db.get_nsx_switch_ids(context.session, int_net_id) if mappings: vcns_network_id = mappings[0] LOG.debug("Network id %(network_id)s corresponding ref is : " "%(net_moref)s", {'network_id': int_net_id, 'net_moref': vcns_network_id}) # Get edge id binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) edge_id = binding['edge_id'] edge_vnic_binding = nsxv_db.get_edge_vnic_binding( context.session, edge_id, int_net_id) # if edge_vnic_binding is None, then first select one avaliable # internal vnic for connection. if not edge_vnic_binding: edge_vnic_binding = nsxv_db.allocate_edge_vnic( context.session, edge_id, int_net_id) nsxv_manager.update_interface(router_id, edge_id, edge_vnic_binding.vnic_index, vcns_network_id, is_connected=is_connected, address_groups=address_groups) def add_vdr_internal_interface(nsxv_manager, context, router_id, int_net_id, address_groups, is_connected=True): # Get the pg/wire id of the network id mappings = nsx_db.get_nsx_switch_ids(context.session, int_net_id) if mappings: vcns_network_id = mappings[0] LOG.debug("Network id %(network_id)s corresponding ref is : " "%(net_moref)s", {'network_id': int_net_id, 'net_moref': vcns_network_id}) # Get edge id binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) edge_id = binding['edge_id'] edge_vnic_binding = nsxv_db.get_edge_vnic_binding( context.session, edge_id, int_net_id) if not edge_vnic_binding: vnic_index = nsxv_manager.add_vdr_internal_interface( edge_id, vcns_network_id, address_groups=address_groups, is_connected=is_connected) nsxv_db.create_edge_vnic_binding( context.session, edge_id, vnic_index, int_net_id) else: msg = (_("Distributed Router doesn't support multiple subnets " "with same network attached to it.")) raise n_exc.BadRequest(resource='vdr', msg=msg) def update_vdr_internal_interface(nsxv_manager, context, router_id, int_net_id, address_groups, is_connected=True): # Get the pg/wire id of the network id mappings = nsx_db.get_nsx_switch_ids(context.session, int_net_id) if mappings: vcns_network_id = mappings[0] LOG.debug("Network id %(network_id)s corresponding ref is : " "%(net_moref)s", {'network_id': int_net_id, 'net_moref': vcns_network_id}) # Get edge id binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) edge_id = binding['edge_id'] edge_vnic_binding = nsxv_db.get_edge_vnic_binding( context.session, edge_id, int_net_id) nsxv_manager.update_vdr_internal_interface( edge_id, edge_vnic_binding.vnic_index, vcns_network_id, address_groups=address_groups, is_connected=is_connected) def delete_interface(nsxv_manager, context, router_id, network_id, dist=False, is_wait=True): # Get the pg/wire id of the network id mappings = nsx_db.get_nsx_switch_ids(context.session, network_id) if mappings: vcns_network_id = mappings[0] LOG.debug("Network id %(network_id)s corresponding ref is : " "%(net_moref)s", {'network_id': network_id, 'net_moref': vcns_network_id}) # Get edge id binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) edge_id = binding['edge_id'] edge_vnic_binding = nsxv_db.get_edge_vnic_binding( context.session, edge_id, network_id) if not edge_vnic_binding: LOG.warning(_LW("Failed to find the network %(net_id)s " "corresponding vnic index on edge %(edge_id)s"), {'net_id': network_id, 'edge_id': edge_id}) return if not dist: task = nsxv_manager.delete_interface( router_id, edge_id, edge_vnic_binding.vnic_index) if is_wait: task.wait(task_const.TaskState.RESULT) nsxv_db.free_edge_vnic_by_network( context.session, edge_id, network_id) else: nsxv_manager.delete_vdr_internal_interface( edge_id, edge_vnic_binding.vnic_index) nsxv_db.delete_edge_vnic_binding_by_network( context.session, edge_id, network_id) def update_nat_rules(nsxv_manager, context, router_id, snat, dnat): binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) task = nsxv_manager.update_nat_rules( router_id, binding['edge_id'], snat, dnat) task.wait(task_const.TaskState.RESULT) def update_dnat_rules(nsxv_manager, context, router_id, dnat_rules): rtr_binding = nsxv_db.get_nsxv_router_binding(context.session, router_id) edge_id = rtr_binding['edge_id'] for dnat_rule in dnat_rules: vnic_binding = nsxv_db.get_edge_vnic_binding( context.session, edge_id, dnat_rule['network_id']) vnic_index = vnic_binding['vnic_index'] dnat_rule['vnic_index'] = vnic_index nsxv_manager.update_dnat_rules(edge_id, dnat_rules) def clear_nat_rules(nsxv_manager, context, router_id): update_nat_rules(nsxv_manager, context, router_id, [], []) def update_firewall(nsxv_manager, context, router_id, firewall, allow_external=True): jobdata = {'context': context} edge_id = nsxv_db.get_nsxv_router_binding( context.session, router_id)['edge_id'] task = nsxv_manager.asyn_update_firewall(router_id, edge_id, firewall, jobdata=jobdata, allow_external=allow_external) task.wait(task_const.TaskState.RESULT) def check_network_in_use_at_backend(context, network_id): retries = max(cfg.CONF.nsxv.retries, 1) delay = 0.5 for attempt in range(1, retries + 1): if attempt != 1: time.sleep(delay) delay = min(2 * delay, 60) edge_vnic_bindings = nsxv_db.get_edge_vnic_bindings_by_int_lswitch( context.session, network_id) if not edge_vnic_bindings: return LOG.warning(_LW('NSXv: network is still in use at the backend')) LOG.error(_LE('NSXv: network is still in use at the backend')) class NsxVCallbacks(object): """Edge callback implementation Callback functions for asynchronous tasks. """ def __init__(self, plugin): self.plugin = plugin def edge_deploy_started(self, task): """callback when deployment task started.""" jobdata = task.userdata['jobdata'] context = jobdata['context'] router_id = jobdata.get('router_id') edge_id = task.userdata.get('edge_id') name = task.userdata.get('router_name') dist = task.userdata.get('dist') self.edge_deploy_started_sync(context, edge_id, name, router_id, dist) def edge_deploy_started_sync(self, context, edge_id, name, router_id, dist): if edge_id: LOG.debug("Start deploying %(edge_id)s for router %(name)s", {'edge_id': edge_id, 'name': name}) nsxv_db.update_nsxv_router_binding( context.session, router_id, edge_id=edge_id) if not dist: # Init Edge vnic binding nsxv_db.init_edge_vnic_binding( context.session, edge_id) else: LOG.debug("Failed to deploy Edge") if router_id: nsxv_db.update_nsxv_router_binding( context.session, router_id, status=plugin_const.ERROR) def edge_deploy_result(self, task): """callback when deployment task finished.""" jobdata = task.userdata['jobdata'] context = jobdata['context'] name = task.userdata.get('router_name') dist = task.userdata.get('dist') router_id = jobdata['router_id'] edge_id = task.userdata.get('edge_id') self.edge_deploy_result_sync( context, edge_id, name, router_id, dist, task.status == task_const.TaskStatus.COMPLETED) def edge_deploy_result_sync(self, context, edge_id, name, router_id, dist, deploy_successful): router_db = None if uuidutils.is_uuid_like(router_id): try: router_db = self.plugin._get_router( context, router_id) except l3.RouterNotFound: # Router might have been deleted before deploy finished LOG.warning(_LW("Router %s not found"), name) if deploy_successful: LOG.debug("Successfully deployed %(edge_id)s for router %(name)s", {'edge_id': edge_id, 'name': name}) if (router_db and router_db['status'] == plugin_const.PENDING_CREATE): router_db['status'] = plugin_const.ACTIVE nsxv_db.update_nsxv_router_binding( context.session, router_id, status=plugin_const.ACTIVE) else: LOG.debug("Failed to deploy Edge for router %s", name) if router_db: router_db['status'] = plugin_const.ERROR nsxv_db.update_nsxv_router_binding( context.session, router_id, status=plugin_const.ERROR) if not dist and edge_id: nsxv_db.clean_edge_vnic_binding( context.session, edge_id) def edge_update_result(self, task): LOG.debug("edge_update_result %d", task.status) def edge_delete_result(self, task): jobdata = task.userdata['jobdata'] router_id = task.userdata['router_id'] dist = task.userdata.get('dist') edge_id = task.userdata['edge_id'] context = jobdata['context'] try: nsxv_db.delete_nsxv_router_binding(context.session, router_id) if not dist: nsxv_db.clean_edge_vnic_binding(context.session, edge_id) except sa_exc.NoResultFound: LOG.warning(_LW("Router Binding for %s not found"), router_id) def interface_update_result(self, task): LOG.debug("interface_update_result %d", task.status) def interface_delete_result(self, task): LOG.debug("interface_delete_result %d", task.status) def snat_create_result(self, task): LOG.debug("snat_create_result %d", task.status) def snat_delete_result(self, task): LOG.debug("snat_delete_result %d", task.status) def dnat_create_result(self, task): LOG.debug("dnat_create_result %d", task.status) def dnat_delete_result(self, task): LOG.debug("dnat_delete_result %d", task.status) def routes_update_result(self, task): LOG.debug("routes_update_result %d", task.status) def nat_update_result(self, task): LOG.debug("nat_update_result %d", task.status) def _create_rule_id_mapping( self, context, edge_id, firewall, vcns_fw): for rule in vcns_fw['firewallRules']['firewallRules']: if rule.get('ruleTag'): index = rule['ruleTag'] - 1 #TODO(linb):a simple filter of the retrieved rules which may be #created by other operations unintentionally if index < len(firewall['firewall_rule_list']): rule_vseid = rule['ruleId'] rule_id = firewall['firewall_rule_list'][index].get('id') if rule_id: map_info = { 'rule_id': rule_id, 'rule_vseid': rule_vseid, 'edge_id': edge_id } nsxv_db.add_nsxv_edge_firewallrule_binding( context.session, map_info) def firewall_update_result(self, task): LOG.debug("firewall_update_result %d", task.status) context = task.userdata['jobdata']['context'] edge_id = task.userdata['edge_id'] fw_config = task.userdata['fw_config'] vcns_fw_config = task.userdata['vcns_fw_config'] nsxv_db.cleanup_nsxv_edge_firewallrule_binding( context.session, edge_id) self._create_rule_id_mapping( context, edge_id, fw_config, vcns_fw_config)