5da150fcd2
integrate with neutron patch Id3f09b78c8d0a8daa7ec4fa6f5bf79f7d5ab8f8b And also skip new tests added in I99681736d05eefd82bdba72b3866eab9468ef5dd Change-Id: I8b119bc69cc87185ea77646e70135c5984200038
961 lines
48 KiB
Python
961 lines
48 KiB
Python
# Copyright 2014 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.
|
|
|
|
import netaddr
|
|
from oslo_config import cfg
|
|
|
|
from neutron.db import l3_db
|
|
from neutron.db.models import l3 as l3_db_models
|
|
from neutron.db import models_v2
|
|
from neutron_lib.api import validators
|
|
from neutron_lib import constants
|
|
from neutron_lib.db import api as db_api
|
|
from neutron_lib.db import model_query
|
|
from neutron_lib import exceptions as n_exc
|
|
from oslo_log import log as logging
|
|
|
|
from vmware_nsx._i18n import _
|
|
from vmware_nsx.common import exceptions as nsx_exc
|
|
from vmware_nsx.common import locking
|
|
from vmware_nsx.db import nsxv_db
|
|
from vmware_nsx.db import nsxv_models
|
|
from vmware_nsx.extensions import routersize
|
|
from vmware_nsx.plugins.nsx_v.drivers import (
|
|
abstract_router_driver as router_driver)
|
|
from vmware_nsx.plugins.nsx_v import md_proxy as nsx_v_md_proxy
|
|
from vmware_nsx.plugins.nsx_v import plugin as nsx_v
|
|
from vmware_nsx.plugins.nsx_v.vshield.common import (
|
|
constants as vcns_const)
|
|
from vmware_nsx.plugins.nsx_v.vshield import edge_utils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class RouterSharedDriver(router_driver.RouterBaseDriver):
|
|
|
|
def get_type(self):
|
|
return "shared"
|
|
|
|
def create_router(self, context, lrouter,
|
|
appliance_size=None, allow_metadata=True):
|
|
pass
|
|
|
|
def _validate_no_routes(self, router):
|
|
if (validators.is_attr_set(router.get('routes')) and
|
|
len(router['routes']) > 0):
|
|
msg = _("Cannot configure static routes on a shared router")
|
|
raise n_exc.InvalidInput(error_message=msg)
|
|
|
|
def _validate_no_size(self, router):
|
|
if validators.is_attr_set(router.get(routersize.ROUTER_SIZE)):
|
|
msg = _("Cannot specify router-size for shared router")
|
|
raise n_exc.InvalidInput(error_message=msg)
|
|
|
|
def update_router(self, context, router_id, router):
|
|
r = router['router']
|
|
self._validate_no_routes(r)
|
|
self._validate_no_size(r)
|
|
|
|
# If only the name and or description are updated. We do not need to
|
|
# update the backend.
|
|
if set(['name', 'description']) >= set(r.keys()):
|
|
return super(nsx_v.NsxVPluginV2, self.plugin).update_router(
|
|
context, router_id, router)
|
|
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
if not edge_id:
|
|
return super(nsx_v.NsxVPluginV2, self.plugin).update_router(
|
|
context, router_id, router)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
gw_info = self.plugin._extract_external_gw(
|
|
context, router, is_extract=True)
|
|
super(nsx_v.NsxVPluginV2, self.plugin).update_router(
|
|
context, router_id, router)
|
|
|
|
if gw_info != constants.ATTR_NOT_SPECIFIED:
|
|
self.plugin._update_router_gw_info(context, router_id, gw_info)
|
|
if 'admin_state_up' in r:
|
|
# If router was deployed on a different edge then
|
|
# admin-state-up is already updated on the new edge.
|
|
current_edge_id = (
|
|
edge_utils.get_router_edge_id(context, router_id))
|
|
if current_edge_id == edge_id:
|
|
self.plugin._update_router_admin_state(context, router_id,
|
|
self.get_type(),
|
|
r['admin_state_up'])
|
|
return self.plugin.get_router(context, router_id)
|
|
|
|
def detach_router(self, context, router_id, router):
|
|
LOG.debug("Detach shared router id %s", router_id)
|
|
# if it is the last shared router on this edge - add it to the pool
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
if not edge_id:
|
|
return
|
|
|
|
router_db = self.plugin._get_router(context, router_id)
|
|
self._notify_before_router_edge_association(context, router_db)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
self._remove_router_services_on_edge(context, router_id)
|
|
with locking.LockManager.get_lock('nsx-shared-router-pool'):
|
|
self._unbind_router_on_edge(context, router_id)
|
|
|
|
def attach_router(self, context, router_id, router, appliance_size=None):
|
|
# find the right place to add, and create a new one if necessary
|
|
router_db = self.plugin._get_router(context, router_id)
|
|
self._bind_router_on_available_edge(
|
|
context, router_id, router_db.admin_state_up)
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
LOG.debug("Shared router %s attached to edge %s", router_id, edge_id)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
self._add_router_services_on_available_edge(context, router_id)
|
|
self._notify_after_router_edge_association(context, router_db)
|
|
|
|
def delete_router(self, context, router_id):
|
|
# make sure that the router binding is cleaned up
|
|
try:
|
|
nsxv_db.delete_nsxv_router_binding(context.session, router_id)
|
|
except Exception as e:
|
|
LOG.debug('Unable to delete router binding for %s. Error: '
|
|
'%s', router_id, e)
|
|
|
|
def _get_router_routes(self, context, router_id):
|
|
return self.plugin._get_extra_routes_by_router_id(
|
|
context, router_id)
|
|
|
|
def _get_router_next_hop(self, context, router_id):
|
|
router_qry = context.session.query(l3_db_models.Router)
|
|
router_db = router_qry.filter_by(id=router_id).one()
|
|
return self.plugin._get_external_attachment_info(
|
|
context, router_db)[2]
|
|
|
|
def _update_routes_on_routers(self, context, target_router_id, router_ids,
|
|
only_if_target_routes=False):
|
|
if only_if_target_routes:
|
|
# First check if the target router has any routes or next hop
|
|
# If not - it means that nothing changes so we can skip this
|
|
# backend call
|
|
target_routes = self._get_router_routes(context, target_router_id)
|
|
target_next_hop = self._get_router_next_hop(
|
|
context, target_router_id)
|
|
if not target_routes and not target_next_hop:
|
|
LOG.debug("_update_routes_on_routers skipped since router %s "
|
|
"has no routes", target_router_id)
|
|
return
|
|
|
|
nexthop = None
|
|
all_routes = []
|
|
for router_id in router_ids:
|
|
routes = self._get_router_routes(context, router_id)
|
|
filters = {'device_id': [router_id]}
|
|
ports = self.plugin.get_ports(context.elevated(), filters)
|
|
self.plugin._add_network_info_for_routes(context, routes, ports)
|
|
all_routes.extend(routes)
|
|
if not nexthop:
|
|
router_nexthop = self._get_router_next_hop(context,
|
|
router_id)
|
|
if router_nexthop:
|
|
nexthop = router_nexthop
|
|
# TODO(berlin) do rollback op.
|
|
edge_utils.update_routes(self.nsx_v, context, target_router_id,
|
|
all_routes, nexthop)
|
|
|
|
# return a dict of each router -> list of vnics from the other routers
|
|
def _get_all_routers_vnic_indices(self, context, router_ids):
|
|
|
|
all_vnic_indices = {}
|
|
if len(router_ids) < 1:
|
|
# there are no routers
|
|
return all_vnic_indices
|
|
|
|
intf_ports = self.plugin.get_ports(
|
|
context.elevated(),
|
|
filters={'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]})
|
|
|
|
edge_id = edge_utils.get_router_edge_id(context, router_ids[0])
|
|
edge_vnic_bindings = nsxv_db.get_edge_vnic_bindings_by_edge(
|
|
context.session, edge_id)
|
|
|
|
for this_router_id in router_ids:
|
|
# get networks IDs for this router
|
|
router_net_ids = list(
|
|
set([port['network_id']
|
|
for port in intf_ports
|
|
if port['device_id'] == this_router_id]))
|
|
|
|
# get vnic index for each network
|
|
vnic_indices = []
|
|
for net_id in router_net_ids:
|
|
vnic_indices.extend([edge_vnic_binding.vnic_index
|
|
for edge_vnic_binding
|
|
in edge_vnic_bindings
|
|
if edge_vnic_binding.network_id == net_id
|
|
])
|
|
|
|
# make sure the list is unique:
|
|
vnic_indices = list(set(vnic_indices))
|
|
# add to the result dict
|
|
all_vnic_indices[this_router_id] = list(vnic_indices)
|
|
|
|
return all_vnic_indices
|
|
|
|
def update_nat_rules(self, context, router, router_id):
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
router_ids = self.edge_manager.get_routers_on_same_edge(
|
|
context, router_id)
|
|
self._update_nat_rules_on_routers(context, router_id, router_ids)
|
|
|
|
def _update_nat_rules_on_routers(self, context,
|
|
target_router_id, router_ids):
|
|
edge_id, az_name = self.plugin._get_edge_id_and_az_by_rtr_id(
|
|
context, target_router_id)
|
|
az = self._availability_zones.get_availability_zone(az_name)
|
|
snats = []
|
|
dnats = []
|
|
vnics_by_router = self._get_all_routers_vnic_indices(
|
|
context, router_ids)
|
|
for router_id in router_ids:
|
|
router_qry = context.session.query(l3_db_models.Router)
|
|
router = router_qry.filter_by(id=router_id).one()
|
|
if router.gw_port:
|
|
snat, dnat = self.plugin._get_nat_rules(context, router)
|
|
snats.extend(snat)
|
|
dnats.extend(dnat)
|
|
if (not az.bind_floatingip_to_all_interfaces and
|
|
len(dnat) > 0):
|
|
# Copy each DNAT rule to all vnics of the other routers,
|
|
# to allow NAT-ed traffic between routers
|
|
# no need for that if bind_floatingip_to_all_interfaces
|
|
# is on (default)
|
|
other_vnics = []
|
|
for other_router_id in router_ids:
|
|
if other_router_id != router_id:
|
|
other_vnics.extend(
|
|
vnics_by_router[other_router_id])
|
|
for rule in dnat:
|
|
for vnic_index in other_vnics:
|
|
new_rule = rule.copy()
|
|
# use explicit vnic_index
|
|
new_rule['vnic_index'] = vnic_index
|
|
dnats.extend([new_rule])
|
|
|
|
edge_utils.update_nat_rules(
|
|
self.nsx_v, context, target_router_id, snats, dnats, az=az)
|
|
|
|
def _update_external_interface_on_routers(self, context,
|
|
target_router_id, router_ids):
|
|
ext_net_ids = self._get_ext_net_ids(context, router_ids)
|
|
if len(ext_net_ids) > 1:
|
|
LOG.error("Can't configure external interface on multiple "
|
|
"external networks %(networks)s for routers %(routers)s",
|
|
{'networks': ext_net_ids, 'routers': router_ids})
|
|
msg = _("Can't configure external interface on multiple external "
|
|
"networks")
|
|
raise nsx_exc.NsxPluginException(err_msg=msg)
|
|
gateway_primary_addr = None
|
|
gateway_mask = None
|
|
gateway_nexthop = None
|
|
secondary = []
|
|
if not ext_net_ids:
|
|
ext_net_id = None
|
|
else:
|
|
ext_net_id = ext_net_ids[0]
|
|
for router_id in router_ids:
|
|
router_qry = context.session.query(l3_db_models.Router)
|
|
router = router_qry.filter_by(id=router_id).one()
|
|
addr, mask, nexthop = self.plugin._get_external_attachment_info(
|
|
context, router)
|
|
if addr:
|
|
if not gateway_primary_addr:
|
|
gateway_primary_addr = addr
|
|
else:
|
|
secondary.append(addr)
|
|
if mask and not gateway_mask:
|
|
gateway_mask = mask
|
|
if nexthop and not gateway_nexthop:
|
|
gateway_nexthop = nexthop
|
|
secondary.extend(self.plugin._get_floatingips_by_router(
|
|
context, router_id))
|
|
LOG.debug('Configure ext interface as following, ext_net: %s, '
|
|
'primaryAddress: %s, netmask: %s, nexthop: %s, secondary: '
|
|
'%s.', ext_net_id, gateway_primary_addr, gateway_mask,
|
|
gateway_nexthop, secondary)
|
|
self.edge_manager.update_external_interface(
|
|
self.nsx_v, context, target_router_id, ext_net_id,
|
|
gateway_primary_addr, gateway_mask, secondary)
|
|
|
|
def _update_subnets_and_dnat_firewall_on_routers(self, context,
|
|
target_router_id,
|
|
router_ids,
|
|
allow_external=True):
|
|
fw_rules = []
|
|
for router_id in router_ids:
|
|
# Add FW rules per single router
|
|
router_qry = context.session.query(l3_db_models.Router)
|
|
router = router_qry.filter_by(id=router_id).one()
|
|
|
|
# subnet rules to allow east-west traffic
|
|
subnet_rules = self.plugin._get_subnet_fw_rules(context, router)
|
|
if subnet_rules:
|
|
fw_rules.extend(subnet_rules)
|
|
|
|
# DNAT rules
|
|
dnat_rule = self.plugin._get_dnat_fw_rule(context, router)
|
|
if dnat_rule:
|
|
fw_rules.append(dnat_rule)
|
|
|
|
# Add rule for not NAT-ed allocation pools
|
|
alloc_pool_rule = self.plugin._get_allocation_pools_fw_rule(
|
|
context, router)
|
|
if alloc_pool_rule:
|
|
fw_rules.append(alloc_pool_rule)
|
|
|
|
# Add no-snat rules
|
|
nosnat_fw_rules = self.plugin._get_nosnat_subnets_fw_rules(
|
|
context, router)
|
|
fw_rules.extend(nosnat_fw_rules)
|
|
|
|
# If metadata service is enabled, block access to inter-edge network
|
|
if self.plugin.metadata_proxy_handler:
|
|
fw_rules += nsx_v_md_proxy.get_router_fw_rules()
|
|
|
|
# Add ipv6 icmp multicast rule (blocked in Vsphere 7 & up)
|
|
fw_rules.extend(self.plugin._get_firewall_icmpv6_rules())
|
|
|
|
# TODO(asarfaty): Add fwaas rules when fwaas supports shared routers
|
|
fw = {'firewall_rule_list': fw_rules}
|
|
edge_utils.update_firewall(self.nsx_v, context, target_router_id,
|
|
fw, allow_external=allow_external)
|
|
|
|
def update_routes(self, context, router_id, nexthop):
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
if edge_id:
|
|
router_db = self.plugin._get_router(context, router_id)
|
|
available_router_ids, conflict_router_ids = (
|
|
self._get_available_and_conflicting_ids(context, router_id))
|
|
is_conflict = self.edge_manager.is_router_conflict_on_edge(
|
|
context, router_id, conflict_router_ids, [], 0)
|
|
if is_conflict:
|
|
self._notify_before_router_edge_association(context, router_db)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
self._remove_router_services_on_edge(context, router_id)
|
|
with locking.LockManager.get_lock(
|
|
'nsx-shared-router-pool'):
|
|
self._unbind_router_on_edge(context, router_id)
|
|
self._bind_router_on_available_edge(
|
|
context, router_id, router_db.admin_state_up)
|
|
new_edge_id = edge_utils.get_router_edge_id(context,
|
|
router_id)
|
|
with locking.LockManager.get_lock(str(new_edge_id)):
|
|
self._add_router_services_on_available_edge(context,
|
|
router_id)
|
|
self._notify_after_router_edge_association(context, router_db)
|
|
else:
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
router_ids = self.edge_manager.get_routers_on_same_edge(
|
|
context, router_id)
|
|
if router_ids:
|
|
self._update_routes_on_routers(
|
|
context, router_id, router_ids)
|
|
|
|
def _get_ext_net_ids(self, context, router_ids):
|
|
ext_net_ids = []
|
|
for router_id in router_ids:
|
|
router_qry = context.session.query(l3_db_models.Router)
|
|
router_db = router_qry.filter_by(id=router_id).one()
|
|
ext_net_id = router_db.gw_port_id and router_db.gw_port.network_id
|
|
if ext_net_id and ext_net_id not in ext_net_ids:
|
|
ext_net_ids.append(ext_net_id)
|
|
return ext_net_ids
|
|
|
|
def _get_shared_routers(self, context):
|
|
shared_routers = []
|
|
routers_qry = context.session.query(l3_db_models.Router).all()
|
|
for r in routers_qry:
|
|
nsx_attr = (context.session.query(
|
|
nsxv_models.NsxvRouterExtAttributes).filter_by(
|
|
router_id=r['id']).first())
|
|
if nsx_attr and nsx_attr['router_type'] == 'shared':
|
|
shared_routers.append(r)
|
|
return shared_routers
|
|
|
|
def _get_available_and_conflicting_ids(self, context, router_id):
|
|
"""Query all conflicting router ids with existing router id.
|
|
The router with static routes will be conflict with all other routers.
|
|
The routers with different gateway will be conflict.
|
|
The routers with overlapping interface will be conflict.
|
|
In not share_edges_between_tenants: The routers of different tenants
|
|
will be in conflict with the router
|
|
"""
|
|
# 1. Check gateway
|
|
# 2. Check subnet interface
|
|
# 3. Check static routes
|
|
router_list = []
|
|
src_router_dict = {}
|
|
ports_qry = context.session.query(models_v2.Port)
|
|
intf_ports = ports_qry.filter_by(
|
|
device_owner=l3_db.DEVICE_OWNER_ROUTER_INTF).all()
|
|
gw_ports = ports_qry.filter_by(
|
|
device_owner=l3_db.DEVICE_OWNER_ROUTER_GW).all()
|
|
shared_routers = self._get_shared_routers(context)
|
|
for r in shared_routers:
|
|
router_dict = {}
|
|
router_dict['id'] = r['id']
|
|
router_dict['gateway'] = None
|
|
router_dict['tenant_id'] = r['tenant_id']
|
|
for gwp in gw_ports:
|
|
if gwp['id'] == r['gw_port_id']:
|
|
try:
|
|
router_dict['gateway'] = (
|
|
gwp['fixed_ips'][0]['subnet_id'])
|
|
except IndexError:
|
|
LOG.error("Skipping GW port %s with no fixed IP",
|
|
gwp['id'])
|
|
subnet_ids = [p['fixed_ips'][0]['subnet_id'] for p in
|
|
intf_ports if p['device_id'] == r['id']]
|
|
router_dict['subnet_ids'] = subnet_ids
|
|
extra_routes = self.plugin._get_extra_routes_by_router_id(
|
|
context, r['id'])
|
|
destinations = [routes['destination'] for routes in extra_routes]
|
|
router_dict['destinations'] = destinations
|
|
|
|
LOG.debug('The router configuration is %s for router %s',
|
|
router_dict, router_dict['id'])
|
|
if router_id != r['id']:
|
|
router_list.append(router_dict)
|
|
else:
|
|
src_router_dict = router_dict
|
|
|
|
# Router with static routes is conflict with other routers
|
|
available_routers = []
|
|
conflict_routers = []
|
|
if src_router_dict['destinations'] != []:
|
|
conflict_routers = [r['id'] for r in router_list]
|
|
return (available_routers, conflict_routers)
|
|
|
|
subnets_qry = context.session.query(models_v2.Subnet).all()
|
|
conflict_cidr_set = []
|
|
for subnet in subnets_qry:
|
|
if subnet['id'] in src_router_dict['subnet_ids']:
|
|
conflict_cidr_set.append(subnet['cidr'])
|
|
if (src_router_dict['gateway'] is not None and
|
|
subnet['id'] == src_router_dict['gateway']):
|
|
conflict_cidr_set.append(subnet['cidr'])
|
|
conflict_ip_set = netaddr.IPSet(conflict_cidr_set)
|
|
# Check conflict router ids with gateway and interface
|
|
for r in router_list:
|
|
if r['destinations'] != []:
|
|
conflict_routers.append(r['id'])
|
|
else:
|
|
cidr_set = []
|
|
for subnet in subnets_qry:
|
|
if subnet['id'] in r['subnet_ids']:
|
|
cidr_set.append(subnet['cidr'])
|
|
ip_set = netaddr.IPSet(cidr_set)
|
|
if (src_router_dict['gateway'] is None or
|
|
r['gateway'] is None or
|
|
src_router_dict['gateway'] == r['gateway']):
|
|
if (conflict_ip_set & ip_set):
|
|
conflict_routers.append(r['id'])
|
|
else:
|
|
if (not cfg.CONF.nsxv.share_edges_between_tenants and
|
|
src_router_dict['tenant_id'] != r['tenant_id']):
|
|
# routers of other tenants are conflicting
|
|
conflict_routers.append(r['id'])
|
|
else:
|
|
available_routers.append(r['id'])
|
|
else:
|
|
conflict_routers.append(r['id'])
|
|
|
|
return (available_routers, conflict_routers)
|
|
|
|
def _get_conflict_network_and_router_ids_by_intf(self, context, router_id):
|
|
"""Collect conflicting networks and routers based on interface ports.
|
|
Collect conflicting networks which has overlapping subnet attached
|
|
to another router.
|
|
Collect conflict routers which has overlap network attached to it.
|
|
Returns:
|
|
conflict_network_ids: networks which has overlapping ips
|
|
conflict_router_ids: routers which has overlapping interfaces
|
|
intf_num: interfaces number attached on the router
|
|
"""
|
|
conflict_network_ids = []
|
|
conflict_router_ids = []
|
|
ports_qry = context.session.query(models_v2.Port)
|
|
intf_ports = ports_qry.filter_by(
|
|
device_owner=l3_db.DEVICE_OWNER_ROUTER_INTF).all()
|
|
|
|
router_net_ids = list(
|
|
set([port['network_id'] for port in intf_ports
|
|
if port['device_id'] == router_id]))
|
|
if cfg.CONF.allow_overlapping_ips:
|
|
router_intf_ports = [port for port in intf_ports
|
|
if port['device_id'] == router_id]
|
|
subnet_ids = []
|
|
for port in router_intf_ports:
|
|
subnet_ids.append(port['fixed_ips'][0]['subnet_id'])
|
|
subnets_qry = context.session.query(models_v2.Subnet).all()
|
|
subnets = [subnet for subnet in subnets_qry
|
|
if subnet['id'] in subnet_ids]
|
|
conflict_network_ids.extend(
|
|
self.plugin._get_conflict_network_ids_by_overlapping(
|
|
context, subnets))
|
|
|
|
other_router_ports = [port for port in intf_ports
|
|
if port['device_id'] != router_id]
|
|
|
|
for port in other_router_ports:
|
|
if port['network_id'] in router_net_ids:
|
|
conflict_router_ids.append(port['device_id'])
|
|
|
|
conflict_router_ids = list(set(conflict_router_ids))
|
|
conflict_network_ids = list(set(conflict_network_ids))
|
|
intf_num = len(router_net_ids)
|
|
return (conflict_network_ids, conflict_router_ids, intf_num)
|
|
|
|
def _get_conflict_network_ids_by_ext_net(self, context, router_id):
|
|
"""Collect conflicting networks based on external network.
|
|
Collect conflicting networks which has overlapping subnet with the
|
|
router's external network
|
|
"""
|
|
conflict_network_ids = []
|
|
ext_net_id = self._get_external_network_id_by_router(context,
|
|
router_id)
|
|
if ext_net_id:
|
|
ext_net = self.plugin._get_network(context, ext_net_id)
|
|
if ext_net.subnets:
|
|
ext_subnet = ext_net.subnets[0]
|
|
if ext_subnet:
|
|
conflict_network_ids.extend(
|
|
self.plugin._get_conflict_network_ids_by_overlapping(
|
|
context, [ext_subnet]))
|
|
return conflict_network_ids
|
|
|
|
def _get_conflict_router_ids_by_ext_net(self, context,
|
|
conflict_network_ids):
|
|
"""Collect conflict routers based on its external network.
|
|
Collect conflict router if it has external network and the external
|
|
network is in conflict_network_ids
|
|
"""
|
|
ext_net_filters = {'router:external': [True]}
|
|
ext_nets = self.plugin.get_networks(
|
|
context.elevated(), filters=ext_net_filters)
|
|
ext_net_ids = [ext_net.get('id') for ext_net in ext_nets]
|
|
conflict_ext_net_ids = list(set(ext_net_ids) &
|
|
set(conflict_network_ids))
|
|
gw_ports_filter = {'network_id': conflict_ext_net_ids,
|
|
'device_owner': [l3_db.DEVICE_OWNER_ROUTER_GW]}
|
|
ports_qry = context.session.query(models_v2.Port)
|
|
gw_ports = model_query.apply_filters(
|
|
ports_qry, models_v2.Port, gw_ports_filter).all()
|
|
return list(set([gw_port['device_id'] for gw_port in gw_ports]))
|
|
|
|
def _get_optional_and_conflict_router_ids_by_gw(self, context, router_id):
|
|
"""Collect conflict routers and optional routers based on GW port.
|
|
Collect conflict router if it has different external network,
|
|
else, collect optional router if it is not distributed and exclusive
|
|
Returns:
|
|
optional_router_ids: routers we can use its edge for the shared router.
|
|
conflict_router_ids: conflict routers which has different gateway
|
|
"""
|
|
ext_net_id = self._get_external_network_id_by_router(context,
|
|
router_id)
|
|
routers = context.session.query(l3_db_models.Router).all()
|
|
optional_router_ids = []
|
|
conflict_router_ids = []
|
|
|
|
if ext_net_id:
|
|
ports_qry = context.session.query(models_v2.Port)
|
|
all_gw_ports = ports_qry.filter_by(
|
|
device_owner=l3_db.DEVICE_OWNER_ROUTER_GW).all()
|
|
metadata_nets = nsxv_db.get_nsxv_internal_networks(
|
|
context.session,
|
|
vcns_const.InternalEdgePurposes.INTER_EDGE_PURPOSE)
|
|
metadata_net_ids = [metadata_net['network_id']
|
|
for metadata_net in metadata_nets]
|
|
# filter out metadata gw_ports
|
|
all_gw_ports = [gw_port for gw_port in all_gw_ports
|
|
if gw_port['network_id'] not in metadata_net_ids]
|
|
for gw_port in all_gw_ports:
|
|
if gw_port and gw_port['network_id'] != ext_net_id:
|
|
conflict_router_ids.append(gw_port['device_id'])
|
|
|
|
for router in routers:
|
|
router_res = {}
|
|
self.plugin._extend_nsx_router_dict(router_res, router)
|
|
if (router['id'] not in conflict_router_ids and
|
|
router_res.get('router_type') == 'shared'):
|
|
optional_router_ids.append(router['id'])
|
|
return optional_router_ids, conflict_router_ids
|
|
|
|
def _bind_router_on_available_edge(self, context, router_id, admin_state):
|
|
with locking.LockManager.get_lock('nsx-shared-router-pool'):
|
|
conflict_network_ids, conflict_router_ids, intf_num = (
|
|
self._get_conflict_network_and_router_ids_by_intf(context,
|
|
router_id))
|
|
conflict_network_ids_by_ext_net = (
|
|
self._get_conflict_network_ids_by_ext_net(context, router_id))
|
|
conflict_network_ids.extend(conflict_network_ids_by_ext_net)
|
|
optional_router_ids, new_conflict_router_ids = (
|
|
self._get_available_and_conflicting_ids(context, router_id))
|
|
conflict_router_ids.extend(new_conflict_router_ids)
|
|
conflict_router_ids = list(set(conflict_router_ids))
|
|
|
|
az, flavor_id = self.get_router_az_and_flavor_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, az)
|
|
# configure metadata service on the router.
|
|
if self.plugin.metadata_proxy_handler and new:
|
|
md_proxy_handler = self.plugin.get_metadata_proxy_handler(
|
|
az.name)
|
|
if md_proxy_handler:
|
|
md_proxy_handler.configure_router_edge(context, router_id)
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
# add all internal interfaces of the router on edge
|
|
intf_net_ids = (
|
|
self.plugin._get_internal_network_ids_by_router(context,
|
|
router_id))
|
|
for network_id in intf_net_ids:
|
|
address_groups = self.plugin._get_address_groups(
|
|
context, router_id, network_id)
|
|
edge_utils.update_internal_interface(
|
|
self.nsx_v, context, router_id, network_id,
|
|
address_groups, admin_state)
|
|
|
|
if flavor_id:
|
|
# if several routers share same edge, they might have
|
|
# different flavors with conflicting syslog settings.
|
|
# in this case, each new router association will override
|
|
# previous syslog settings on the edge
|
|
self.edge_manager.update_syslog_by_flavor(context, router_id,
|
|
flavor_id, edge_id)
|
|
LOG.info("Binding shared router %(rtr)s: edge %(edge)s",
|
|
{'rtr': router_id, 'edge': edge_id})
|
|
|
|
def _unbind_router_on_edge(self, context, router_id):
|
|
az = self.get_router_az_by_id(context, router_id)
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
self.edge_manager.reconfigure_shared_edge_metadata_port(
|
|
context, router_id)
|
|
self.edge_manager.unbind_router_on_edge(context, router_id)
|
|
if self.plugin.metadata_proxy_handler:
|
|
metadata_proxy_handler = self.plugin.get_metadata_proxy_handler(
|
|
az.name)
|
|
if metadata_proxy_handler:
|
|
metadata_proxy_handler.cleanup_router_edge(context, router_id)
|
|
LOG.info("Unbinding shared router %(rtr)s: edge %(edge)s",
|
|
{'rtr': router_id, 'edge': edge_id})
|
|
|
|
def _add_router_services_on_available_edge(self, context, router_id):
|
|
router_ids = self.edge_manager.get_routers_on_same_edge(
|
|
context, router_id)
|
|
self._update_external_interface_on_routers(
|
|
context, router_id, router_ids)
|
|
self._update_routes_on_routers(context, router_id, router_ids,
|
|
only_if_target_routes=True)
|
|
self._update_nat_rules_on_routers(context, router_id, router_ids)
|
|
self._update_subnets_and_dnat_firewall_on_routers(
|
|
context, router_id, router_ids, allow_external=True)
|
|
|
|
def _remove_router_services_on_edge(self, context, router_id,
|
|
intf_net_id=None):
|
|
router_ids = self.edge_manager.get_routers_on_same_edge(
|
|
context, router_id)
|
|
router_ids.remove(router_id)
|
|
# Refresh firewall, nats, ext_vnic as well as static routes
|
|
self._update_routes_on_routers(context, router_id, router_ids,
|
|
only_if_target_routes=True)
|
|
self._update_subnets_and_dnat_firewall_on_routers(
|
|
context, router_id, router_ids, allow_external=True)
|
|
self._update_nat_rules_on_routers(context, router_id, router_ids)
|
|
self._update_external_interface_on_routers(
|
|
context, router_id, router_ids)
|
|
intf_net_ids = (
|
|
self.plugin._get_internal_network_ids_by_router(context,
|
|
router_id))
|
|
if intf_net_id:
|
|
intf_net_ids.remove(intf_net_id)
|
|
for net_id in intf_net_ids:
|
|
edge_utils.delete_interface(self.nsx_v, context, router_id, net_id)
|
|
|
|
@db_api.retry_db_errors
|
|
def _update_router_gw_info(self, context, router_id, info,
|
|
is_routes_update=False,
|
|
force_update=False):
|
|
router = self.plugin._get_router(context, router_id)
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
if not edge_id:
|
|
super(nsx_v.NsxVPluginV2, self.plugin)._update_router_gw_info(
|
|
context, router_id, info, router=router)
|
|
# UPDATE gw info only if the router has been attached to an edge
|
|
else:
|
|
is_migrated = False
|
|
router_ids = self.edge_manager.get_routers_on_same_edge(
|
|
context, router_id)
|
|
org_ext_net_id = (router.gw_port_id and
|
|
router.gw_port.network_id)
|
|
org_enable_snat = router.enable_snat
|
|
orgaddr, orgmask, orgnexthop = (
|
|
self.plugin._get_external_attachment_info(
|
|
context, router))
|
|
super(nsx_v.NsxVPluginV2, self.plugin)._update_router_gw_info(
|
|
context, router_id, info, router=router)
|
|
router = self.plugin._get_router(context, router_id)
|
|
new_ext_net_id = (router.gw_port_id and
|
|
router.gw_port.network_id)
|
|
new_enable_snat = router.enable_snat
|
|
newaddr, newmask, newnexthop = (
|
|
self.plugin._get_external_attachment_info(context, router))
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
if new_ext_net_id and new_ext_net_id != org_ext_net_id:
|
|
# Check whether the gw address has overlapping
|
|
# with networks attached to the same edge
|
|
conflict_network_ids = (
|
|
self._get_conflict_network_ids_by_ext_net(
|
|
context, router_id))
|
|
is_migrated = self.edge_manager.is_router_conflict_on_edge(
|
|
context, router_id, [], conflict_network_ids)
|
|
if is_migrated:
|
|
self._remove_router_services_on_edge(context,
|
|
router_id)
|
|
with locking.LockManager.get_lock(
|
|
'nsx-shared-router-pool'):
|
|
self._unbind_router_on_edge(context, router_id)
|
|
|
|
if not is_migrated:
|
|
ext_net_ids = self._get_ext_net_ids(context, router_ids)
|
|
if len(ext_net_ids) > 1:
|
|
# move all routing service of the router from existing
|
|
# edge to a new available edge if new_ext_net_id is
|
|
# changed.
|
|
self._remove_router_services_on_edge(context,
|
|
router_id)
|
|
with locking.LockManager.get_lock(
|
|
'nsx-shared-router-pool'):
|
|
self._unbind_router_on_edge(context, router_id)
|
|
is_migrated = True
|
|
else:
|
|
updated_routes = False
|
|
# Update external vnic if addr or mask is changed
|
|
if orgaddr != newaddr or orgmask != newmask:
|
|
# If external gateway is removed, the default
|
|
# gateway should be cleared before updating the
|
|
# interface, or else the backend will fail.
|
|
if (new_ext_net_id != org_ext_net_id and
|
|
new_ext_net_id is None):
|
|
self._update_routes_on_routers(
|
|
context, router_id, router_ids)
|
|
updated_routes = True
|
|
|
|
self._update_external_interface_on_routers(
|
|
context, router_id, router_ids)
|
|
|
|
# Update SNAT rules if ext net changed
|
|
# or ext net not changed but snat is changed.
|
|
if ((new_ext_net_id != org_ext_net_id) or
|
|
(new_ext_net_id == org_ext_net_id and
|
|
new_enable_snat != org_enable_snat)):
|
|
self._update_nat_rules_on_routers(context,
|
|
router_id,
|
|
router_ids)
|
|
|
|
if (new_ext_net_id != org_ext_net_id or
|
|
new_enable_snat != org_enable_snat):
|
|
self._update_subnets_and_dnat_firewall_on_routers(
|
|
context, router_id, router_ids,
|
|
allow_external=True)
|
|
|
|
# Update static routes in all (if not updated yet).
|
|
if not updated_routes:
|
|
self._update_routes_on_routers(
|
|
context, router_id, router_ids)
|
|
if is_migrated:
|
|
self._notify_before_router_edge_association(context,
|
|
router, edge_id)
|
|
self._bind_router_on_available_edge(
|
|
context, router_id, router.admin_state_up)
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
self._add_router_services_on_available_edge(context,
|
|
router_id)
|
|
self._notify_after_router_edge_association(context, router)
|
|
|
|
def _base_add_router_interface(self, context, router_id, interface_info):
|
|
with locking.LockManager.get_lock('nsx-shared-router-pool'):
|
|
return super(nsx_v.NsxVPluginV2, self.plugin).add_router_interface(
|
|
context, router_id, interface_info)
|
|
|
|
def add_router_interface(self, context, router_id, interface_info):
|
|
# Lock the shared router before any action that can cause the router
|
|
# to be deployed on a new edge.
|
|
with locking.LockManager.get_lock('router-%s' % router_id):
|
|
return self._safe_add_router_interface(context, router_id,
|
|
interface_info)
|
|
|
|
def _safe_add_router_interface(self, context, router_id, interface_info):
|
|
self.plugin._check_intf_number_of_router(context, router_id)
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
router_db = self.plugin._get_router(context, router_id)
|
|
if edge_id:
|
|
is_migrated = False
|
|
with locking.LockManager.get_lock('nsx-shared-router-pool'):
|
|
info = super(nsx_v.NsxVPluginV2,
|
|
self.plugin).add_router_interface(
|
|
context, router_id, interface_info)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
router_ids = self.edge_manager.get_routers_on_same_edge(
|
|
context, router_id)
|
|
subnet = self.plugin.get_subnet(context, info['subnet_id'])
|
|
network_id = subnet['network_id']
|
|
# Collect all conflict networks whose cidr are overlapped
|
|
# with networks attached to the router and conflict routers
|
|
# which has same network with the router's.
|
|
conflict_network_ids, conflict_router_ids, _ = (
|
|
self._get_conflict_network_and_router_ids_by_intf(
|
|
context, router_id))
|
|
|
|
_, new_conflict_router_ids = (
|
|
self._get_available_and_conflicting_ids(context,
|
|
router_id))
|
|
conflict_router_ids.extend(new_conflict_router_ids)
|
|
conflict_router_ids = list(set(conflict_router_ids))
|
|
|
|
interface_ports = (
|
|
self.plugin._get_router_interface_ports_by_network(
|
|
context, router_id, network_id))
|
|
# Consider whether another subnet of the same network
|
|
# has been attached to the router.
|
|
if len(interface_ports) > 1:
|
|
is_conflict = (
|
|
self.edge_manager.is_router_conflict_on_edge(
|
|
context, router_id, conflict_router_ids,
|
|
conflict_network_ids, 0))
|
|
else:
|
|
is_conflict = (
|
|
self.edge_manager.is_router_conflict_on_edge(
|
|
context, router_id, conflict_router_ids,
|
|
conflict_network_ids, 1))
|
|
if not is_conflict:
|
|
|
|
address_groups = self.plugin._get_address_groups(
|
|
context, router_id, network_id)
|
|
edge_utils.update_internal_interface(
|
|
self.nsx_v, context, router_id,
|
|
network_id, address_groups,
|
|
router_db.admin_state_up)
|
|
if router_db.gw_port and router_db.enable_snat:
|
|
self._update_nat_rules_on_routers(
|
|
context, router_id, router_ids)
|
|
self._update_subnets_and_dnat_firewall_on_routers(
|
|
context, router_id, router_ids,
|
|
allow_external=True)
|
|
if is_conflict:
|
|
self._notify_before_router_edge_association(
|
|
context, router_db, edge_id)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
if len(interface_ports) > 1:
|
|
self._remove_router_services_on_edge(
|
|
context, router_id)
|
|
else:
|
|
self._remove_router_services_on_edge(
|
|
context, router_id, network_id)
|
|
self._unbind_router_on_edge(context, router_id)
|
|
is_migrated = True
|
|
if is_migrated:
|
|
self._bind_router_on_available_edge(
|
|
context, router_id, router_db.admin_state_up)
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
self._add_router_services_on_available_edge(context,
|
|
router_id)
|
|
self._notify_after_router_edge_association(context, router_db)
|
|
else:
|
|
info = self._base_add_router_interface(context, router_id,
|
|
interface_info)
|
|
# bind and configure routing service on an available edge
|
|
self._bind_router_on_available_edge(
|
|
context, router_id, router_db.admin_state_up)
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
self._add_router_services_on_available_edge(context,
|
|
router_id)
|
|
self._notify_after_router_edge_association(context, router_db)
|
|
return info
|
|
|
|
def remove_router_interface(self, context, router_id, interface_info):
|
|
# Lock the shared router before any action that can cause the router
|
|
# to be deployed on a new edge
|
|
with locking.LockManager.get_lock('router-%s' % router_id):
|
|
return self._safe_remove_router_interface(context, router_id,
|
|
interface_info)
|
|
|
|
def _safe_remove_router_interface(self, context, router_id,
|
|
interface_info):
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
with locking.LockManager.get_lock('nsx-shared-router-pool'):
|
|
info = super(
|
|
nsx_v.NsxVPluginV2, self.plugin).remove_router_interface(
|
|
context, router_id, interface_info)
|
|
subnet = self.plugin.get_subnet(context, info['subnet_id'])
|
|
network_id = subnet['network_id']
|
|
ports = self.plugin._get_router_interface_ports_by_network(
|
|
context, router_id, network_id)
|
|
connected_networks = (
|
|
self.plugin._get_internal_network_ids_by_router(context,
|
|
router_id))
|
|
if not ports and not connected_networks:
|
|
router = self.plugin._get_router(context, router_id)
|
|
self._notify_before_router_edge_association(context, router)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
router_ids = self.edge_manager.get_routers_on_same_edge(
|
|
context, router_id)
|
|
self._update_nat_rules_on_routers(context, router_id,
|
|
router_ids)
|
|
self._update_subnets_and_dnat_firewall_on_routers(
|
|
context, router_id, router_ids, allow_external=True)
|
|
if not ports:
|
|
edge_utils.delete_interface(self.nsx_v, context,
|
|
router_id, network_id)
|
|
# unbind all services if no interfaces attached to the
|
|
# router
|
|
if not connected_networks:
|
|
self._remove_router_services_on_edge(context,
|
|
router_id)
|
|
self._unbind_router_on_edge(context, router_id)
|
|
else:
|
|
address_groups = self.plugin._get_address_groups(
|
|
context, router_id, network_id)
|
|
edge_utils.update_internal_interface(self.nsx_v, context,
|
|
router_id,
|
|
network_id,
|
|
address_groups)
|
|
return info
|
|
|
|
def _update_edge_router(self, context, router_id):
|
|
edge_id = edge_utils.get_router_edge_id(context, router_id)
|
|
with locking.LockManager.get_lock(str(edge_id)):
|
|
router_ids = self.edge_manager.get_routers_on_same_edge(
|
|
context, router_id)
|
|
if router_ids:
|
|
self._update_external_interface_on_routers(
|
|
context, router_id, router_ids)
|
|
self._update_nat_rules_on_routers(
|
|
context, router_id, router_ids)
|
|
self._update_subnets_and_dnat_firewall_on_routers(
|
|
context, router_id, router_ids, allow_external=True)
|