# API parameter name and Database column names may differ.
# Useful to keep the filtering between API and Database.
API_TO_DB_COLUMN_MAP = {'port_id': 'fixed_port_id'}
+CORE_ROUTER_ATTRS = ('id', 'name', 'tenant_id', 'admin_state_up', 'status')
class Router(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
"""Mixin class to add L3/NAT router methods to db_base_plugin_v2."""
l3_rpc_notifier = l3_rpc_agent_api.L3AgentNotify
+ router_device_owners = (
+ DEVICE_OWNER_ROUTER_INTF,
+ DEVICE_OWNER_ROUTER_GW,
+ DEVICE_OWNER_FLOATINGIP
+ )
@property
def _core_plugin(self):
return manager.NeutronManager.get_plugin()
- def _get_router(self, context, id):
+ def _get_router(self, context, router_id):
try:
- router = self._get_by_id(context, Router, id)
+ router = self._get_by_id(context, Router, router_id)
except exc.NoResultFound:
- raise l3.RouterNotFound(router_id=id)
+ raise l3.RouterNotFound(router_id=router_id)
return router
- def _make_router_dict(self, router, fields=None,
- process_extensions=True):
- res = {'id': router['id'],
- 'name': router['name'],
- 'tenant_id': router['tenant_id'],
- 'admin_state_up': router['admin_state_up'],
- 'status': router['status'],
- EXTERNAL_GW_INFO: None,
- 'gw_port_id': router['gw_port_id']}
+ def _make_router_dict(self, router, fields=None, process_extensions=True):
+ res = dict((key, router[key]) for key in CORE_ROUTER_ATTRS)
if router['gw_port_id']:
- nw_id = router.gw_port['network_id']
- res[EXTERNAL_GW_INFO] = {'network_id': nw_id}
+ ext_gw_info = {'network_id': router.gw_port['network_id']}
+ else:
+ ext_gw_info = None
+ res.update({
+ EXTERNAL_GW_INFO: ext_gw_info,
+ 'gw_port_id': router['gw_port_id'],
+ })
# NOTE(salv-orlando): The following assumes this mixin is used in a
# class inheriting from CommonDbMixin, which is true for all existing
# plugins.
if process_extensions:
- self._apply_dict_extend_functions(
- l3.ROUTERS, res, router)
+ self._apply_dict_extend_functions(l3.ROUTERS, res, router)
return self._fields(res, fields)
- def create_router(self, context, router):
- r = router['router']
- has_gw_info = False
- if EXTERNAL_GW_INFO in r:
- has_gw_info = True
- gw_info = r[EXTERNAL_GW_INFO]
- del r[EXTERNAL_GW_INFO]
- tenant_id = self._get_tenant_id_for_create(context, r)
+ def _create_router_db(self, context, router, tenant_id, gw_info):
+ """Create the DB object and update gw info, if available."""
with context.session.begin(subtransactions=True):
# pre-generate id so it will be available when
# configuring external gw port
router_db = Router(id=uuidutils.generate_uuid(),
tenant_id=tenant_id,
- name=r['name'],
- admin_state_up=r['admin_state_up'],
+ name=router['name'],
+ admin_state_up=router['admin_state_up'],
status="ACTIVE")
context.session.add(router_db)
- if has_gw_info:
+ return router_db
+
+ def create_router(self, context, router):
+ r = router['router']
+ gw_info = r.pop(EXTERNAL_GW_INFO, None)
+ tenant_id = self._get_tenant_id_for_create(context, r)
+ with context.session.begin(subtransactions=True):
+ router_db = self._create_router_db(context, r, tenant_id, gw_info)
+ if gw_info:
self._update_router_gw_info(context, router_db['id'], gw_info)
return self._make_router_dict(router_db, process_extensions=False)
+ def _update_router_db(self, context, router_id, data, gw_info):
+ """Update the DB object and related gw info, if available."""
+ with context.session.begin(subtransactions=True):
+ if gw_info != attributes.ATTR_NOT_SPECIFIED:
+ self._update_router_gw_info(context, router_id, gw_info)
+ router_db = self._get_router(context, router_id)
+ if data:
+ router_db.update(data)
+ return router_db
+
def update_router(self, context, id, router):
r = router['router']
- has_gw_info = False
- gw_info = None
- if EXTERNAL_GW_INFO in r:
- has_gw_info = True
- gw_info = r[EXTERNAL_GW_INFO]
- del r[EXTERNAL_GW_INFO]
+ gw_info = r.pop(EXTERNAL_GW_INFO, attributes.ATTR_NOT_SPECIFIED)
# check whether router needs and can be rescheduled to the proper
# l3 agent (associated with given external network);
# do check before update in DB as an exception will be raised
# in case no proper l3 agent found
candidates = None
- if has_gw_info:
+ if gw_info != attributes.ATTR_NOT_SPECIFIED:
candidates = self._check_router_needs_rescheduling(
context, id, gw_info)
- with context.session.begin(subtransactions=True):
- if has_gw_info:
- self._update_router_gw_info(context, id, gw_info)
- router_db = self._get_router(context, id)
- # Ensure we actually have something to update
- if r.keys():
- router_db.update(r)
+ router_db = self._update_router_db(context, id, r, gw_info)
if candidates:
l3_plugin = manager.NeutronManager.get_service_plugins().get(
constants.L3_ROUTER_NAT)
l3_plugin.reschedule_router(context, id, candidates)
- self.l3_rpc_notifier.routers_updated(
- context, [router_db['id']])
+ self.l3_rpc_notifier.routers_updated(context, [router_db['id']])
return self._make_router_dict(router_db)
def _check_router_needs_rescheduling(self, context, router_id, gw_info):
gw_port['id'])
context.session.add(router)
- def _update_router_gw_info(self, context, router_id, info, router=None):
- # TODO(salvatore-orlando): guarantee atomic behavior also across
- # operations that span beyond the model classes handled by this
- # class (e.g.: delete_port)
- router = router or self._get_router(context, router_id)
- gw_port = router.gw_port
- # network_id attribute is required by API, so it must be present
+ def _validate_gw_info(self, context, gw_port, info):
network_id = info['network_id'] if info else None
if network_id:
network_db = self._core_plugin._get_network(context, network_id)
if not network_db.external:
- msg = _("Network %s is not a valid external "
- "network") % network_id
+ msg = _("Network %s is not an external network") % network_id
raise n_exc.BadRequest(resource='router', msg=msg)
-
- # figure out if we need to delete existing port
- if gw_port and gw_port['network_id'] != network_id:
- fip_count = self.get_floatingips_count(context.elevated(),
- {'router_id': [router_id]})
- if fip_count:
+ return network_id
+
+ def _delete_current_gw_port(self, context, router_id, router, new_network):
+ """Delete gw port, if it is attached to an old network."""
+ is_gw_port_attached_to_existing_network = (
+ router.gw_port and router.gw_port['network_id'] != new_network)
+ admin_ctx = context.elevated()
+ if is_gw_port_attached_to_existing_network:
+ if self.get_floatingips_count(
+ admin_ctx, {'router_id': [router_id]}):
raise l3.RouterExternalGatewayInUseByFloatingIp(
- router_id=router_id, net_id=gw_port['network_id'])
+ router_id=router_id, net_id=router.gw_port['network_id'])
with context.session.begin(subtransactions=True):
+ gw_port_id = router.gw_port['id']
router.gw_port = None
context.session.add(router)
- self._core_plugin.delete_port(context.elevated(),
- gw_port['id'],
- l3_port_check=False)
-
- if network_id is not None and (gw_port is None or
- gw_port['network_id'] != network_id):
+ self._core_plugin.delete_port(
+ admin_ctx, gw_port_id, l3_port_check=False)
+
+ def _create_gw_port(self, context, router_id, router, new_network):
+ new_valid_gw_port_attachment = (
+ new_network and (not router.gw_port or
+ router.gw_port['network_id'] != new_network))
+ if new_valid_gw_port_attachment:
subnets = self._core_plugin._get_subnets_by_network(context,
- network_id)
+ new_network)
for subnet in subnets:
self._check_for_dup_router_subnet(context, router_id,
- network_id, subnet['id'],
+ new_network, subnet['id'],
subnet['cidr'])
- self._create_router_gw_port(context, router, network_id)
+ self._create_router_gw_port(context, router, new_network)
+
+ def _update_router_gw_info(self, context, router_id, info, router=None):
+ # TODO(salvatore-orlando): guarantee atomic behavior also across
+ # operations that span beyond the model classes handled by this
+ # class (e.g.: delete_port)
+ router = router or self._get_router(context, router_id)
+ gw_port = router.gw_port
+ network_id = self._validate_gw_info(context, gw_port, info)
+ self._delete_current_gw_port(context, router_id, router, network_id)
+ self._create_gw_port(context, router_id, router, network_id)
+
+ def _ensure_router_not_in_use(self, context, router_id):
+ admin_ctx = context.elevated()
+ router = self._get_router(context, router_id)
+ if self.get_floatingips_count(
+ admin_ctx, filters={'router_id': [router_id]}):
+ raise l3.RouterInUse(router_id=router_id)
+ device_owner = self._get_device_owner(context, router)
+ device_filter = {'device_id': [router_id],
+ 'device_owner': [device_owner]}
+ port_count = self._core_plugin.get_ports_count(
+ admin_ctx, filters=device_filter)
+ if port_count:
+ raise l3.RouterInUse(router_id=router_id)
+ return router
def delete_router(self, context, id):
with context.session.begin(subtransactions=True):
- router = self._get_router(context, id)
-
- # Ensure that the router is not used
- fips = self.get_floatingips_count(context.elevated(),
- filters={'router_id': [id]})
- if fips:
- raise l3.RouterInUse(router_id=id)
-
- device_filter = {'device_id': [id],
- 'device_owner': [DEVICE_OWNER_ROUTER_INTF]}
- ports = self._core_plugin.get_ports_count(context.elevated(),
- filters=device_filter)
- if ports:
- raise l3.RouterInUse(router_id=id)
+ router = self._ensure_router_not_in_use(context, id)
#TODO(nati) Refactor here when we have router insertion model
vpnservice = manager.NeutronManager.get_service_plugins().get(
except exc.NoResultFound:
pass
- def add_router_interface(self, context, router_id, interface_info):
+ def _get_device_owner(self, context, router=None):
+ """Get device_owner for the specified router."""
+ # NOTE(armando-migliaccio): in the base case this is invariant
+ return DEVICE_OWNER_ROUTER_INTF
+
+ def _validate_interface_info(self, interface_info):
if not interface_info:
msg = _("Either subnet_id or port_id must be specified")
raise n_exc.BadRequest(resource='router', msg=msg)
+ port_id_specified = 'port_id' in interface_info
+ subnet_id_specified = 'subnet_id' in interface_info
+ if port_id_specified and subnet_id_specified:
+ msg = _("Cannot specify both subnet-id and port-id")
+ raise n_exc.BadRequest(resource='router', msg=msg)
+ return port_id_specified, subnet_id_specified
- if 'port_id' in interface_info:
- # make sure port update is committed
- with context.session.begin(subtransactions=True):
- if 'subnet_id' in interface_info:
- msg = _("Cannot specify both subnet-id and port-id")
- raise n_exc.BadRequest(resource='router', msg=msg)
-
- port = self._core_plugin._get_port(context,
- interface_info['port_id'])
- if port['device_id']:
- raise n_exc.PortInUse(net_id=port['network_id'],
- port_id=port['id'],
- device_id=port['device_id'])
- fixed_ips = [ip for ip in port['fixed_ips']]
- if len(fixed_ips) != 1:
- msg = _('Router port must have exactly one fixed IP')
- raise n_exc.BadRequest(resource='router', msg=msg)
- subnet_id = fixed_ips[0]['subnet_id']
- subnet = self._core_plugin._get_subnet(context, subnet_id)
- self._check_for_dup_router_subnet(context, router_id,
- port['network_id'],
- subnet['id'],
- subnet['cidr'])
- port.update({'device_id': router_id,
- 'device_owner': DEVICE_OWNER_ROUTER_INTF})
- elif 'subnet_id' in interface_info:
- subnet_id = interface_info['subnet_id']
- subnet = self._core_plugin._get_subnet(context, subnet_id)
- # Ensure the subnet has a gateway
- if not subnet['gateway_ip']:
- msg = _('Subnet for router interface must have a gateway IP')
+ def _add_interface_by_port(self, context, router_id, port_id, owner):
+ with context.session.begin(subtransactions=True):
+ port = self._core_plugin._get_port(context, port_id)
+ if port['device_id']:
+ raise n_exc.PortInUse(net_id=port['network_id'],
+ port_id=port['id'],
+ device_id=port['device_id'])
+ fixed_ips = [ip for ip in port['fixed_ips']]
+ if len(fixed_ips) != 1:
+ msg = _('Router port must have exactly one fixed IP')
raise n_exc.BadRequest(resource='router', msg=msg)
+ subnet_id = fixed_ips[0]['subnet_id']
+ subnet = self._core_plugin._get_subnet(context, subnet_id)
self._check_for_dup_router_subnet(context, router_id,
- subnet['network_id'],
- subnet_id,
+ port['network_id'],
+ subnet['id'],
subnet['cidr'])
- fixed_ip = {'ip_address': subnet['gateway_ip'],
- 'subnet_id': subnet['id']}
- port = self._core_plugin.create_port(context, {
- 'port':
- {'tenant_id': subnet['tenant_id'],
- 'network_id': subnet['network_id'],
- 'fixed_ips': [fixed_ip],
- 'mac_address': attributes.ATTR_NOT_SPECIFIED,
- 'admin_state_up': True,
- 'device_id': router_id,
- 'device_owner': DEVICE_OWNER_ROUTER_INTF,
- 'name': ''}})
+ port.update({'device_id': router_id, 'device_owner': owner})
+ return port
+
+ def _add_interface_by_subnet(self, context, router_id, subnet_id, owner):
+ subnet = self._core_plugin._get_subnet(context, subnet_id)
+ if not subnet['gateway_ip']:
+ msg = _('Subnet for router interface must have a gateway IP')
+ raise n_exc.BadRequest(resource='router', msg=msg)
+ self._check_for_dup_router_subnet(context, router_id,
+ subnet['network_id'],
+ subnet_id,
+ subnet['cidr'])
+ fixed_ip = {'ip_address': subnet['gateway_ip'],
+ 'subnet_id': subnet['id']}
+ return self._core_plugin.create_port(context, {
+ 'port':
+ {'tenant_id': subnet['tenant_id'],
+ 'network_id': subnet['network_id'],
+ 'fixed_ips': [fixed_ip],
+ 'mac_address': attributes.ATTR_NOT_SPECIFIED,
+ 'admin_state_up': True,
+ 'device_id': router_id,
+ 'device_owner': owner,
+ 'name': ''}})
+
+ def add_router_interface(self, context, router_id, interface_info):
+ add_by_port, add_by_sub = self._validate_interface_info(interface_info)
+ device_owner = self._get_device_owner(context, router_id)
+
+ if add_by_port:
+ port = self._add_interface_by_port(
+ context, router_id, interface_info['port_id'], device_owner)
+ elif add_by_sub:
+ port = self._add_interface_by_subnet(
+ context, router_id, interface_info['subnet_id'], device_owner)
self.l3_rpc_notifier.routers_updated(
context, [router_id], 'add_router_interface')
info = {'id': router_id,
- 'tenant_id': subnet['tenant_id'],
+ 'tenant_id': port['tenant_id'],
'port_id': port['id'],
'subnet_id': port['fixed_ips'][0]['subnet_id']}
notifier_api.notify(context,
raise l3.RouterInterfaceInUseByFloatingIP(
router_id=router_id, subnet_id=subnet_id)
+ def _remove_interface_by_port(self, context, router_id,
+ port_id, subnet_id, owner):
+ port_db = self._core_plugin._get_port(context, port_id)
+ if not (port_db['device_owner'] == owner and
+ port_db['device_id'] == router_id):
+ raise l3.RouterInterfaceNotFound(router_id=router_id,
+ port_id=port_id)
+ port_subnet_id = port_db['fixed_ips'][0]['subnet_id']
+ if subnet_id and port_subnet_id != subnet_id:
+ raise n_exc.SubnetMismatchForPort(
+ port_id=port_id, subnet_id=subnet_id)
+ subnet = self._core_plugin._get_subnet(context, port_subnet_id)
+ self._confirm_router_interface_not_in_use(
+ context, router_id, port_subnet_id)
+ self._core_plugin.delete_port(context, port_db['id'],
+ l3_port_check=False)
+ return (port_db, subnet)
+
+ def _remove_interface_by_subnet(self, context,
+ router_id, subnet_id, owner):
+ self._confirm_router_interface_not_in_use(
+ context, router_id, subnet_id)
+ subnet = self._core_plugin._get_subnet(context, subnet_id)
+
+ try:
+ rport_qry = context.session.query(models_v2.Port)
+ ports = rport_qry.filter_by(
+ device_id=router_id,
+ device_owner=owner,
+ network_id=subnet['network_id'])
+
+ for p in ports:
+ if p['fixed_ips'][0]['subnet_id'] == subnet_id:
+ self._core_plugin.delete_port(context, p['id'],
+ l3_port_check=False)
+ return (p, subnet)
+ except exc.NoResultFound:
+ pass
+ raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id,
+ subnet_id=subnet_id)
+
def remove_router_interface(self, context, router_id, interface_info):
if not interface_info:
msg = _("Either subnet_id or port_id must be specified")
raise n_exc.BadRequest(resource='router', msg=msg)
- if 'port_id' in interface_info:
- port_id = interface_info['port_id']
- port_db = self._core_plugin._get_port(context, port_id)
- if not (port_db['device_owner'] == DEVICE_OWNER_ROUTER_INTF and
- port_db['device_id'] == router_id):
- raise l3.RouterInterfaceNotFound(router_id=router_id,
- port_id=port_id)
- if 'subnet_id' in interface_info:
- port_subnet_id = port_db['fixed_ips'][0]['subnet_id']
- if port_subnet_id != interface_info['subnet_id']:
- raise n_exc.SubnetMismatchForPort(
- port_id=port_id,
- subnet_id=interface_info['subnet_id'])
- subnet_id = port_db['fixed_ips'][0]['subnet_id']
- subnet = self._core_plugin._get_subnet(context, subnet_id)
- self._confirm_router_interface_not_in_use(
- context, router_id, subnet_id)
- self._core_plugin.delete_port(context, port_db['id'],
- l3_port_check=False)
- elif 'subnet_id' in interface_info:
- subnet_id = interface_info['subnet_id']
- self._confirm_router_interface_not_in_use(context, router_id,
- subnet_id)
-
- subnet = self._core_plugin._get_subnet(context, subnet_id)
- found = False
-
- try:
- rport_qry = context.session.query(models_v2.Port)
- ports = rport_qry.filter_by(
- device_id=router_id,
- device_owner=DEVICE_OWNER_ROUTER_INTF,
- network_id=subnet['network_id'])
-
- for p in ports:
- if p['fixed_ips'][0]['subnet_id'] == subnet_id:
- port_id = p['id']
- self._core_plugin.delete_port(context, p['id'],
- l3_port_check=False)
- found = True
- break
- except exc.NoResultFound:
- pass
+ port_id = interface_info.get('port_id')
+ subnet_id = interface_info.get('subnet_id')
+ device_owner = self._get_device_owner(context, router_id)
+ if port_id:
+ port, subnet = self._remove_interface_by_port(context, router_id,
+ port_id, subnet_id,
+ device_owner)
+ elif subnet_id:
+ port, subnet = self._remove_interface_by_subnet(
+ context, router_id, subnet_id, device_owner)
- if not found:
- raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id,
- subnet_id=subnet_id)
self.l3_rpc_notifier.routers_updated(
context, [router_id], 'remove_router_interface')
info = {'id': router_id,
- 'tenant_id': subnet['tenant_id'],
- 'port_id': port_id,
- 'subnet_id': subnet_id}
+ 'tenant_id': port['tenant_id'],
+ 'port_id': port['id'],
+ 'subnet_id': subnet['id']}
notifier_api.notify(context,
notifier_api.publisher_id('network'),
'router.interface.delete',
'status': floatingip['status']}
return self._fields(res, fields)
+ def _get_interface_ports_for_network(self, context, network_id):
+ router_intf_qry = context.session.query(models_v2.Port)
+ return router_intf_qry.filter_by(
+ network_id=network_id,
+ device_owner=DEVICE_OWNER_ROUTER_INTF)
+
def _get_router_for_floatingip(self, context, internal_port,
internal_subnet_id,
external_network_id):
'which has no gateway_ip') % internal_subnet_id)
raise n_exc.BadRequest(resource='floatingip', msg=msg)
- # find router interface ports on this network
- router_intf_qry = context.session.query(models_v2.Port)
- router_intf_ports = router_intf_qry.filter_by(
- network_id=internal_port['network_id'],
- device_owner=DEVICE_OWNER_ROUTER_INTF)
+ router_intf_ports = self._get_interface_ports_for_network(
+ context, internal_port['network_id'])
for intf_p in router_intf_ports:
if intf_p['fixed_ips'][0]['subnet_id'] == internal_subnet_id:
deletion checks.
"""
port_db = self._core_plugin._get_port(context, port_id)
- if port_db['device_owner'] in [DEVICE_OWNER_ROUTER_INTF,
- DEVICE_OWNER_ROUTER_GW,
- DEVICE_OWNER_FLOATINGIP]:
+ if port_db['device_owner'] in self.router_device_owners:
# Raise port in use only if the port has IP addresses
# Otherwise it's a stale port that can be removed
fixed_ips = port_db['fixed_ips']
self._populate_subnet_for_ports(context, gw_ports)
return gw_ports
- def get_sync_interfaces(self, context, router_ids,
- device_owner=DEVICE_OWNER_ROUTER_INTF):
+ def get_sync_interfaces(self, context, router_ids, device_owners=None):
"""Query router interfaces that relate to list of router_ids."""
+ device_owners = device_owners or [DEVICE_OWNER_ROUTER_INTF]
if not router_ids:
return []
filters = {'device_id': router_ids,
- 'device_owner': [device_owner]}
+ 'device_owner': device_owners}
interfaces = self._core_plugin.get_ports(context, filters)
if interfaces:
self._populate_subnet_for_ports(context, interfaces)
router[l3_constants.INTERFACE_KEY] = router_interfaces
return routers_dict.values()
- def get_sync_data(self, context, router_ids=None, active=None):
+ def _get_router_info_list(self, context, router_ids=None, active=None,
+ device_owners=None):
"""Query routers and their related floating_ips, interfaces."""
with context.session.begin(subtransactions=True):
routers = self._get_sync_routers(context,
router_ids=router_ids,
active=active)
router_ids = [router['id'] for router in routers]
+ interfaces = self.get_sync_interfaces(
+ context, router_ids, device_owners)
floating_ips = self._get_sync_floating_ips(context, router_ids)
- interfaces = self.get_sync_interfaces(context, router_ids)
+ return (routers, interfaces, floating_ips)
+
+ def get_sync_data(self, context, router_ids=None, active=None):
+ routers, interfaces, floating_ips = self._get_router_info_list(
+ context, router_ids=router_ids, active=active)
return self._process_sync_data(routers, interfaces, floating_ips)