diff --git a/neutron/db/bgp_db.py b/neutron/db/bgp_db.py index d1ddfe6e73e..a60a97f053c 100644 --- a/neutron/db/bgp_db.py +++ b/neutron/db/bgp_db.py @@ -12,22 +12,33 @@ # License for the specific language governing permissions and limitations # under the License. +import itertools + from oslo_db import exception as oslo_db_exc from oslo_log import log as logging from oslo_utils import uuidutils import sqlalchemy as sa +from sqlalchemy import and_ from sqlalchemy import orm +from sqlalchemy.orm import aliased from sqlalchemy.orm import exc as sa_exc +from neutron_lib import constants as lib_consts + from neutron.api.v2 import attributes as attr from neutron.common import exceptions as n_exc +from neutron.db import address_scope_db from neutron.db import common_db_mixin as common_db +from neutron.db import l3_attrs_db +from neutron.db import l3_db from neutron.db import model_base from neutron.db import models_v2 from neutron.extensions import bgp as bgp_ext LOG = logging.getLogger(__name__) +DEVICE_OWNER_ROUTER_GW = lib_consts.DEVICE_OWNER_ROUTER_GW +DEVICE_OWNER_ROUTER_INTF = lib_consts.DEVICE_OWNER_ROUTER_INTF class BgpSpeakerPeerBinding(model_base.BASEV2): @@ -140,7 +151,9 @@ class BgpDbMixin(common_db.CommonDbMixin): res['peers'] = self.get_bgp_peers_by_bgp_speaker(context, bgp_speaker['id'], fields=bgp_peer_attrs) - res['advertised_routes'] = [] + res['advertised_routes'] = self.get_routes_by_bgp_speaker_id( + context, + bgp_speaker_id) return res def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker): @@ -267,8 +280,30 @@ class BgpDbMixin(common_db.CommonDbMixin): except sa_exc.NoResultFound: raise bgp_ext.BgpSpeakerNotFound(id=bgp_speaker_id) + def _get_bgp_speaker_ids_by_router(self, context, router_id): + with context.session.begin(subtransactions=True): + network_binding = aliased(BgpSpeakerNetworkBinding) + r_port = aliased(l3_db.RouterPort) + query = context.session.query(network_binding.bgp_speaker_id) + query = query.filter( + r_port.router_id == router_id, + r_port.port_type == lib_consts.DEVICE_OWNER_ROUTER_GW, + r_port.port_id == models_v2.Port.id, + models_v2.Port.network_id == network_binding.network_id) + + return [binding.bgp_speaker_id for binding in query.all()] + + def _get_bgp_speaker_ids_by_binding_network(self, context, network_id): + with context.session.begin(subtransactions=True): + query = context.session.query( + BgpSpeakerNetworkBinding.bgp_speaker_id) + query = query.filter( + BgpSpeakerNetworkBinding.network_id == network_id) + return query.all() + def get_advertised_routes(self, context, bgp_speaker_id): - return self._make_advertised_routes_dict([]) + routes = self.get_routes_by_bgp_speaker_id(context, bgp_speaker_id) + return self._make_advertised_routes_dict(routes) def _get_id_for(self, resource, id_name): try: @@ -406,3 +441,473 @@ class BgpDbMixin(common_db.CommonDbMixin): 'auth_type', 'password'] res = dict((k, bgp_peer[k]) for k in attrs) return self._fields(res, fields) + + def _get_address_scope_ids_for_bgp_speaker(self, context, bgp_speaker_id): + with context.session.begin(subtransactions=True): + binding = aliased(BgpSpeakerNetworkBinding) + address_scope = aliased(address_scope_db.AddressScope) + query = context.session.query(address_scope) + query = query.filter( + binding.bgp_speaker_id == bgp_speaker_id, + models_v2.Subnet.ip_version == binding.ip_version, + models_v2.Subnet.network_id == binding.network_id, + models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id, + models_v2.SubnetPool.address_scope_id == address_scope.id) + return [scope.id for scope in query.all()] + + def get_routes_by_bgp_speaker_id(self, context, bgp_speaker_id): + """Get all routes that should be advertised by a BgpSpeaker.""" + with context.session.begin(subtransactions=True): + net_routes = self._get_tenant_network_routes_by_bgp_speaker( + context, + bgp_speaker_id) + fip_routes = self._get_central_fip_host_routes_by_bgp_speaker( + context, + bgp_speaker_id) + return itertools.chain(fip_routes, net_routes) + + def get_routes_by_bgp_speaker_binding(self, context, + bgp_speaker_id, network_id): + """Get all routes for the given bgp_speaker binding.""" + with context.session.begin(subtransactions=True): + fip_routes = self._get_central_fip_host_routes_by_binding( + context, + network_id, + bgp_speaker_id) + net_routes = self._get_tenant_network_routes_by_binding( + context, + network_id, + bgp_speaker_id) + return itertools.chain(fip_routes, net_routes) + + def _get_routes_by_router(self, context, router_id): + bgp_speaker_ids = self._get_bgp_speaker_ids_by_router(context, + router_id) + route_dict = {} + for bgp_speaker_id in bgp_speaker_ids: + fip_routes = self._get_central_fip_host_routes_by_router( + context, + router_id, + bgp_speaker_id) + net_routes = self._get_tenant_network_routes_by_router( + context, + router_id, + bgp_speaker_id) + routes = list(itertools.chain(fip_routes, net_routes)) + route_dict[bgp_speaker_id] = routes + return route_dict + + def _get_central_fip_host_routes_by_router(self, context, router_id, + bgp_speaker_id): + """Get floating IP host routes with the given router as nexthop.""" + with context.session.begin(subtransactions=True): + dest_alias = aliased(l3_db.FloatingIP, + name='destination') + next_hop_alias = aliased(models_v2.IPAllocation, + name='next_hop') + binding_alias = aliased(BgpSpeakerNetworkBinding, + name='binding') + router_attrs = aliased(l3_attrs_db.RouterExtraAttributes, + name='router_attrs') + query = context.session.query(dest_alias.floating_ip_address, + next_hop_alias.ip_address) + query = query.join( + next_hop_alias, + next_hop_alias.network_id == dest_alias.floating_network_id) + query = query.join(l3_db.Router, + dest_alias.router_id == l3_db.Router.id) + query = query.filter( + l3_db.Router.id == router_id, + dest_alias.router_id == l3_db.Router.id, + l3_db.Router.id == router_attrs.router_id, + router_attrs.distributed == sa.sql.false(), + l3_db.Router.gw_port_id == next_hop_alias.port_id, + next_hop_alias.subnet_id == models_v2.Subnet.id, + models_v2.Subnet.ip_version == 4, + binding_alias.network_id == models_v2.Subnet.network_id, + binding_alias.bgp_speaker_id == bgp_speaker_id, + binding_alias.ip_version == 4, + BgpSpeaker.advertise_floating_ip_host_routes == sa.sql.true()) + query = query.outerjoin(router_attrs, + l3_db.Router.id == router_attrs.router_id) + query = query.filter(router_attrs.distributed != sa.sql.true()) + return self._host_route_list_from_tuples(query.all()) + + def _get_central_fip_host_routes_by_binding(self, context, + network_id, bgp_speaker_id): + """Get all floating IP host routes for the given network binding.""" + with context.session.begin(subtransactions=True): + # Query the DB for floating IP's and the IP address of the + # gateway port + dest_alias = aliased(l3_db.FloatingIP, + name='destination') + next_hop_alias = aliased(models_v2.IPAllocation, + name='next_hop') + binding_alias = aliased(BgpSpeakerNetworkBinding, + name='binding') + router_attrs = aliased(l3_attrs_db.RouterExtraAttributes, + name='router_attrs') + query = context.session.query(dest_alias.floating_ip_address, + next_hop_alias.ip_address) + query = query.join( + next_hop_alias, + next_hop_alias.network_id == dest_alias.floating_network_id) + query = query.join( + binding_alias, + binding_alias.network_id == dest_alias.floating_network_id) + query = query.join(l3_db.Router, + dest_alias.router_id == l3_db.Router.id) + query = query.filter( + dest_alias.floating_network_id == network_id, + dest_alias.router_id == l3_db.Router.id, + l3_db.Router.gw_port_id == next_hop_alias.port_id, + next_hop_alias.subnet_id == models_v2.Subnet.id, + models_v2.Subnet.ip_version == 4, + binding_alias.network_id == models_v2.Subnet.network_id, + binding_alias.bgp_speaker_id == BgpSpeaker.id, + BgpSpeaker.id == bgp_speaker_id, + BgpSpeaker.advertise_floating_ip_host_routes == sa.sql.true()) + query = query.outerjoin(router_attrs, + l3_db.Router.id == router_attrs.router_id) + query = query.filter(router_attrs.distributed != sa.sql.true()) + return self._host_route_list_from_tuples(query.all()) + + def _get_central_fip_host_routes_by_bgp_speaker(self, context, + bgp_speaker_id): + """Get all the floating IP host routes advertised by a BgpSpeaker.""" + with context.session.begin(subtransactions=True): + dest_alias = aliased(l3_db.FloatingIP, + name='destination') + next_hop_alias = aliased(models_v2.IPAllocation, + name='next_hop') + speaker_binding = aliased(BgpSpeakerNetworkBinding, + name="speaker_network_mapping") + router_attrs = aliased(l3_attrs_db.RouterExtraAttributes, + name='router_attrs') + query = context.session.query(dest_alias.floating_ip_address, + next_hop_alias.ip_address) + query = query.select_from(dest_alias, + BgpSpeaker, + l3_db.Router, + models_v2.Subnet) + query = query.join( + next_hop_alias, + next_hop_alias.network_id == dest_alias.floating_network_id) + query = query.join( + speaker_binding, + speaker_binding.network_id == dest_alias.floating_network_id) + query = query.join(l3_db.Router, + dest_alias.router_id == l3_db.Router.id) + query = query.filter( + BgpSpeaker.id == bgp_speaker_id, + BgpSpeaker.advertise_floating_ip_host_routes, + speaker_binding.bgp_speaker_id == BgpSpeaker.id, + dest_alias.floating_network_id == speaker_binding.network_id, + next_hop_alias.network_id == speaker_binding.network_id, + dest_alias.router_id == l3_db.Router.id, + l3_db.Router.gw_port_id == next_hop_alias.port_id, + next_hop_alias.subnet_id == models_v2.Subnet.id, + models_v2.Subnet.ip_version == 4) + query = query.outerjoin(router_attrs, + l3_db.Router.id == router_attrs.router_id) + query = query.filter(router_attrs.distributed != sa.sql.true()) + return self._host_route_list_from_tuples(query.all()) + + def _get_tenant_network_routes_by_binding(self, context, + network_id, bgp_speaker_id): + """Get all tenant network routes for the given network.""" + + with context.session.begin(subtransactions=True): + tenant_networks_query = self._tenant_networks_by_network_query( + context, + network_id, + bgp_speaker_id) + nexthops_query = self._nexthop_ip_addresses_by_binding_query( + context, + network_id, + bgp_speaker_id) + join_q = self._join_tenant_networks_to_next_hops( + context, + tenant_networks_query.subquery(), + nexthops_query.subquery()) + return self._make_advertised_routes_list(join_q.all()) + + def _get_tenant_network_routes_by_router(self, context, router_id, + bgp_speaker_id): + """Get all tenant network routes with the given router as nexthop.""" + + with context.session.begin(subtransactions=True): + scopes = self._get_address_scope_ids_for_bgp_speaker( + context, + bgp_speaker_id) + address_scope = aliased(address_scope_db.AddressScope) + inside_query = context.session.query( + models_v2.Subnet.cidr, + models_v2.IPAllocation.ip_address, + address_scope.id) + outside_query = context.session.query( + address_scope.id, + models_v2.IPAllocation.ip_address) + speaker_binding = aliased(BgpSpeakerNetworkBinding, + name="speaker_network_mapping") + port_alias = aliased(l3_db.RouterPort, name='routerport') + inside_query = inside_query.filter( + port_alias.router_id == router_id, + models_v2.IPAllocation.port_id == port_alias.port_id, + models_v2.IPAllocation.subnet_id == models_v2.Subnet.id, + models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id, + models_v2.SubnetPool.address_scope_id == address_scope.id, + address_scope.id.in_(scopes), + port_alias.port_type != lib_consts.DEVICE_OWNER_ROUTER_GW, + speaker_binding.bgp_speaker_id == bgp_speaker_id) + outside_query = outside_query.filter( + port_alias.router_id == router_id, + port_alias.port_type == lib_consts.DEVICE_OWNER_ROUTER_GW, + models_v2.IPAllocation.port_id == port_alias.port_id, + models_v2.IPAllocation.subnet_id == models_v2.Subnet.id, + models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id, + models_v2.SubnetPool.address_scope_id == address_scope.id, + address_scope.id.in_(scopes), + speaker_binding.bgp_speaker_id == bgp_speaker_id, + speaker_binding.network_id == models_v2.Port.network_id, + port_alias.port_id == models_v2.Port.id) + inside_query = inside_query.subquery() + outside_query = outside_query.subquery() + join_query = context.session.query(inside_query.c.cidr, + outside_query.c.ip_address) + and_cond = and_(inside_query.c.id == outside_query.c.id) + join_query = join_query.join(outside_query, and_cond) + return self._make_advertised_routes_list(join_query.all()) + + def _get_tenant_network_routes_by_bgp_speaker(self, context, + bgp_speaker_id): + """Get all tenant network routes to be advertised by a BgpSpeaker.""" + + with context.session.begin(subtransactions=True): + tenant_nets_q = self._tenant_networks_by_bgp_speaker_query( + context, + bgp_speaker_id) + nexthops_q = self._nexthop_ip_addresses_by_bgp_speaker_query( + context, + bgp_speaker_id) + join_q = self._join_tenant_networks_to_next_hops( + context, + tenant_nets_q.subquery(), + nexthops_q.subquery()) + + return self._make_advertised_routes_list(join_q.all()) + + def _join_tenant_networks_to_next_hops(self, context, + tenant_networks_subquery, + nexthops_subquery): + """Join subquery for tenant networks to subquery for nexthop IP's""" + left_subq = tenant_networks_subquery + right_subq = nexthops_subquery + join_query = context.session.query(left_subq.c.cidr, + right_subq.c.ip_address) + and_cond = and_(left_subq.c.router_id == right_subq.c.router_id, + left_subq.c.ip_version == right_subq.c.ip_version) + join_query = join_query.join(right_subq, and_cond) + return join_query + + def _tenant_networks_by_network_query(self, context, + network_id, bgp_speaker_id): + """Return subquery for tenant networks by binding network ID""" + address_scope = aliased(address_scope_db.AddressScope, + name='address_scope') + router_attrs = aliased(l3_attrs_db.RouterExtraAttributes, + name='router_attrs') + tenant_networks_query = context.session.query( + l3_db.RouterPort.router_id, + models_v2.Subnet.cidr, + models_v2.Subnet.ip_version, + address_scope.id) + tenant_networks_query = tenant_networks_query.filter( + l3_db.RouterPort.port_type != lib_consts.DEVICE_OWNER_ROUTER_GW, + l3_db.RouterPort.port_type != lib_consts.DEVICE_OWNER_ROUTER_SNAT, + l3_db.RouterPort.router_id == router_attrs.router_id, + models_v2.IPAllocation.port_id == l3_db.RouterPort.port_id, + models_v2.IPAllocation.subnet_id == models_v2.Subnet.id, + models_v2.Subnet.network_id != network_id, + models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id, + models_v2.SubnetPool.address_scope_id == address_scope.id, + BgpSpeaker.id == bgp_speaker_id, + BgpSpeaker.ip_version == address_scope.ip_version, + models_v2.Subnet.ip_version == address_scope.ip_version) + return tenant_networks_query + + def _tenant_networks_by_bgp_speaker_query(self, context, bgp_speaker_id): + """Return subquery for tenant networks by binding bgp_speaker_id""" + router_id = l3_db.RouterPort.router_id.distinct().label('router_id') + tenant_nets_subq = context.session.query(router_id, + models_v2.Subnet.cidr, + models_v2.Subnet.ip_version) + scopes = self._get_address_scope_ids_for_bgp_speaker(context, + bgp_speaker_id) + filters = self._tenant_networks_by_bgp_speaker_filters(scopes) + tenant_nets_subq = tenant_nets_subq.filter(*filters) + return tenant_nets_subq + + def _tenant_networks_by_bgp_speaker_filters(self, address_scope_ids): + """Return the filters for querying tenant networks by BGP speaker""" + router_attrs = aliased(l3_attrs_db.RouterExtraAttributes, + name='router_attrs') + return [models_v2.IPAllocation.port_id == l3_db.RouterPort.port_id, + l3_db.RouterPort.router_id == router_attrs.router_id, + l3_db.RouterPort.port_type != lib_consts.DEVICE_OWNER_ROUTER_GW, + l3_db.RouterPort.port_type != lib_consts.DEVICE_OWNER_ROUTER_SNAT, + models_v2.IPAllocation.subnet_id == models_v2.Subnet.id, + models_v2.Subnet.network_id != BgpSpeakerNetworkBinding.network_id, + models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id, + models_v2.SubnetPool.address_scope_id.in_(address_scope_ids), + models_v2.Subnet.ip_version == BgpSpeakerNetworkBinding.ip_version, + BgpSpeakerNetworkBinding.bgp_speaker_id == BgpSpeaker.id, + BgpSpeaker.advertise_tenant_networks == sa.sql.true()] + + def _nexthop_ip_addresses_by_binding_query(self, context, + network_id, bgp_speaker_id): + """Return the subquery for locating nexthops by binding network""" + nexthops_query = context.session.query( + l3_db.RouterPort.router_id, + models_v2.IPAllocation.ip_address, + models_v2.Subnet.ip_version) + filters = self._next_hop_ip_addresses_by_binding_filters( + network_id, + bgp_speaker_id) + nexthops_query = nexthops_query.filter(*filters) + return nexthops_query + + def _next_hop_ip_addresses_by_binding_filters(self, + network_id, + bgp_speaker_id): + """Return the filters for querying nexthops by binding network""" + address_scope = aliased(address_scope_db.AddressScope, + name='address_scope') + return [models_v2.IPAllocation.port_id == l3_db.RouterPort.port_id, + models_v2.IPAllocation.subnet_id == models_v2.Subnet.id, + BgpSpeaker.id == bgp_speaker_id, + BgpSpeakerNetworkBinding.bgp_speaker_id == BgpSpeaker.id, + BgpSpeakerNetworkBinding.network_id == network_id, + models_v2.Subnet.network_id == BgpSpeakerNetworkBinding.network_id, + models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id, + models_v2.SubnetPool.address_scope_id == address_scope.id, + models_v2.Subnet.ip_version == address_scope.ip_version, + l3_db.RouterPort.port_type == DEVICE_OWNER_ROUTER_GW] + + def _nexthop_ip_addresses_by_bgp_speaker_query(self, context, + bgp_speaker_id): + """Return the subquery for locating nexthops by BGP speaker""" + nexthops_query = context.session.query( + l3_db.RouterPort.router_id, + models_v2.IPAllocation.ip_address, + models_v2.Subnet.ip_version) + filters = self._next_hop_ip_addresses_by_bgp_speaker_filters( + bgp_speaker_id) + nexthops_query = nexthops_query.filter(*filters) + return nexthops_query + + def _next_hop_ip_addresses_by_bgp_speaker_filters(self, bgp_speaker_id): + """Return the filters for querying nexthops by BGP speaker""" + router_attrs = aliased(l3_attrs_db.RouterExtraAttributes, + name='router_attrs') + + return [l3_db.RouterPort.port_type == DEVICE_OWNER_ROUTER_GW, + l3_db.RouterPort.router_id == router_attrs.router_id, + BgpSpeakerNetworkBinding.network_id == models_v2.Subnet.network_id, + BgpSpeakerNetworkBinding.ip_version == models_v2.Subnet.ip_version, + BgpSpeakerNetworkBinding.bgp_speaker_id == bgp_speaker_id, + models_v2.IPAllocation.port_id == l3_db.RouterPort.port_id, + models_v2.IPAllocation.subnet_id == models_v2.Subnet.id] + + def _tenant_prefixes_by_router(self, context, router_id, bgp_speaker_id): + with context.session.begin(subtransactions=True): + query = context.session.query(models_v2.Subnet.cidr.distinct()) + filters = self._tenant_prefixes_by_router_filters(router_id, + bgp_speaker_id) + query = query.filter(*filters) + return [x[0] for x in query.all()] + + def _tenant_prefixes_by_router_filters(self, router_id, bgp_speaker_id): + binding = aliased(BgpSpeakerNetworkBinding, name='network_binding') + subnetpool = aliased(models_v2.SubnetPool, + name='subnetpool') + router_attrs = aliased(l3_attrs_db.RouterExtraAttributes, + name='router_attrs') + return [models_v2.Subnet.id == models_v2.IPAllocation.subnet_id, + models_v2.Subnet.subnetpool_id == subnetpool.id, + l3_db.RouterPort.router_id == router_id, + l3_db.Router.id == l3_db.RouterPort.router_id, + l3_db.Router.id == router_attrs.router_id, + l3_db.Router.gw_port_id == models_v2.Port.id, + models_v2.Port.network_id == binding.network_id, + binding.bgp_speaker_id == BgpSpeaker.id, + l3_db.RouterPort.port_type == DEVICE_OWNER_ROUTER_INTF, + models_v2.IPAllocation.port_id == l3_db.RouterPort.port_id] + + def _tenant_prefixes_by_router_interface(self, + context, + router_port_id, + bgp_speaker_id): + with context.session.begin(subtransactions=True): + query = context.session.query(models_v2.Subnet.cidr.distinct()) + filters = self._tenant_prefixes_by_router_filters(router_port_id, + bgp_speaker_id) + query = query.filter(*filters) + return [x[0] for x in query.all()] + + def _tenant_prefixes_by_router_port_filters(self, + router_port_id, + bgp_speaker_id): + binding = aliased(BgpSpeakerNetworkBinding, name='network_binding') + return [models_v2.Subnet.id == models_v2.IPAllocation.subnet_id, + l3_db.RouterPort.port_id == router_port_id, + l3_db.Router.id == l3_db.RouterPort.router_id, + l3_db.Router.gw_port_id == models_v2.Port.id, + models_v2.Port.network_id == binding.network_id, + binding.bgp_speaker_id == BgpSpeaker.id, + models_v2.Subnet.ip_version == binding.ip_version, + l3_db.RouterPort.port_type == DEVICE_OWNER_ROUTER_INTF, + models_v2.IPAllocation.port_id == l3_db.RouterPort.port_id] + + def _bgp_speakers_for_gateway_network(self, context, network_id): + """Return all BgpSpeakers for the given gateway network""" + with context.session.begin(subtransactions=True): + query = context.session.query(BgpSpeaker) + query = query.filter( + BgpSpeakerNetworkBinding.network_id == network_id, + BgpSpeakerNetworkBinding.bgp_speaker_id == BgpSpeaker.id) + try: + return query.all() + except sa_exc.NoResultFound: + raise bgp_ext.NetworkNotBound(network_id=network_id) + + def _bgp_speaker_for_gateway_network(self, context, + network_id, ip_version): + """Return the BgpSpeaker by given gateway network and ip_version""" + with context.session.begin(subtransactions=True): + query = context.session.query(BgpSpeaker) + query = query.filter( + BgpSpeakerNetworkBinding.network_id == network_id, + BgpSpeakerNetworkBinding.bgp_speaker_id == BgpSpeaker.id, + BgpSpeakerNetworkBinding.ip_version == ip_version) + try: + return query.one() + except sa_exc.NoResultFound: + raise bgp_ext.NetworkNotBoundForIpVersion( + network_id=network_id, + ip_version=ip_version) + + def _make_advertised_routes_list(self, routes): + route_list = ({'destination': x, + 'next_hop': y} for x, y in routes) + return route_list + + def _route_list_from_prefixes_and_next_hop(self, routes, next_hop): + route_list = [{'destination': x, + 'next_hop': next_hop} for x in routes] + return route_list + + def _host_route_list_from_tuples(self, ip_next_hop_tuples): + """Return the list of host routes given a list of (IP, nexthop)""" + return ({'destination': x + '/32', + 'next_hop': y} for x, y in ip_next_hop_tuples) diff --git a/neutron/extensions/bgp.py b/neutron/extensions/bgp.py index 1d8ec5b9cc3..e01469d582b 100644 --- a/neutron/extensions/bgp.py +++ b/neutron/extensions/bgp.py @@ -154,6 +154,11 @@ class InvalidBgpPeerMd5Authentication(exceptions.BadRequest): message = _("A password must be supplied when using auth_type md5.") +class NetworkNotBoundForIpVersion(NetworkNotBound): + message = _("Network %(network_id)s is not bound to a IPv%(ip_version)s " + "BgpSpeaker.") + + class Bgp(extensions.ExtensionDescriptor): @classmethod diff --git a/neutron/tests/api/test_bgp_speaker_extensions.py b/neutron/tests/api/test_bgp_speaker_extensions.py index ddb47ccbfe2..a7e82dff7b6 100644 --- a/neutron/tests/api/test_bgp_speaker_extensions.py +++ b/neutron/tests/api/test_bgp_speaker_extensions.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import netaddr from tempest import config from tempest.lib import exceptions as lib_exc from tempest import test @@ -41,8 +42,25 @@ class BgpSpeakerTestJSONBase(base.BaseAdminNetworkTest): msg = "BGP Speaker extension is not enabled." raise cls.skipException(msg) + cls.admin_routerports = [] + cls.admin_floatingips = [] + cls.admin_routers = [] cls.ext_net_id = CONF.network.public_network_id + @classmethod + def resource_cleanup(cls): + for floatingip in cls.admin_floatingips: + cls._try_delete_resource(cls.admin_client.delete_floatingip, + floatingip['id']) + for routerport in cls.admin_routerports: + cls._try_delete_resource( + cls.admin_client.remove_router_interface_with_subnet_id, + routerport['router_id'], routerport['subnet_id']) + for router in cls.admin_routers: + cls._try_delete_resource(cls.admin_client.delete_router, + router['id']) + super(BgpSpeakerTestJSONBase, cls).resource_cleanup() + def create_bgp_speaker(self, auto_delete=True, **args): data = {'bgp_speaker': args} bgp_speaker = self.admin_client.create_bgp_speaker(data) @@ -171,3 +189,94 @@ class BgpSpeakerTestJSON(BgpSpeakerTestJSONBase): bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id) network_list = bgp_speaker['bgp-speaker']['networks'] self.assertTrue(not network_list) + + @test.idempotent_id('5bef22ad-5e70-4f7b-937a-dc1944642996') + def test_get_advertised_routes_null_address_scope(self): + self.useFixture(fixtures.LockFixture('gateway_network_binding')) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['bgp-speaker']['id'] + self.admin_client.add_bgp_gateway_network(bgp_speaker_id, + self.ext_net_id) + routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id) + self.assertEqual(0, len(routes['advertised_routes'])) + + @test.idempotent_id('cae9cdb1-ad65-423c-9604-d4cd0073616e') + def test_get_advertised_routes_floating_ips(self): + self.useFixture(fixtures.LockFixture('gateway_network_binding')) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['bgp-speaker']['id'] + self.admin_client.add_bgp_gateway_network(bgp_speaker_id, + self.ext_net_id) + tenant_net = self.create_network() + tenant_subnet = self.create_subnet(tenant_net) + ext_gw_info = {'network_id': self.ext_net_id} + router = self.admin_client.create_router( + 'my-router', + external_gateway_info=ext_gw_info, + admin_state_up=True, + distributed=False) + self.admin_routers.append(router['router']) + self.admin_client.add_router_interface_with_subnet_id( + router['router']['id'], + tenant_subnet['id']) + self.admin_routerports.append({'router_id': router['router']['id'], + 'subnet_id': tenant_subnet['id']}) + tenant_port = self.create_port(tenant_net) + floatingip = self.create_floatingip(self.ext_net_id) + self.admin_floatingips.append(floatingip) + self.client.update_floatingip(floatingip['id'], + port_id=tenant_port['id']) + routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id) + self.assertEqual(1, len(routes['advertised_routes'])) + self.assertEqual(floatingip['floating_ip_address'] + '/32', + routes['advertised_routes'][0]['destination']) + + @test.idempotent_id('c9ad566e-fe8f-4559-8303-bbad9062a30c') + def test_get_advertised_routes_tenant_networks(self): + self.useFixture(fixtures.LockFixture('gateway_network_binding')) + addr_scope = self.create_address_scope('my-scope', ip_version=4) + ext_net = self.create_shared_network(**{'router:external': True}) + tenant_net = self.create_network() + ext_subnetpool = self.create_subnetpool( + 'test-pool-ext', + is_admin=True, + default_prefixlen=24, + address_scope_id=addr_scope['id'], + prefixes=['8.0.0.0/8']) + tenant_subnetpool = self.create_subnetpool( + 'tenant-test-pool', + default_prefixlen=25, + address_scope_id=addr_scope['id'], + prefixes=['10.10.0.0/16']) + self.create_subnet({'id': ext_net['id']}, + cidr=netaddr.IPNetwork('8.0.0.0/24'), + ip_version=4, + client=self.admin_client, + subnetpool_id=ext_subnetpool['id']) + tenant_subnet = self.create_subnet( + {'id': tenant_net['id']}, + cidr=netaddr.IPNetwork('10.10.0.0/24'), + ip_version=4, + subnetpool_id=tenant_subnetpool['id']) + ext_gw_info = {'network_id': ext_net['id']} + router = self.admin_client.create_router( + 'my-router', + external_gateway_info=ext_gw_info, + distributed=False)['router'] + self.admin_routers.append(router) + self.admin_client.add_router_interface_with_subnet_id( + router['id'], + tenant_subnet['id']) + self.admin_routerports.append({'router_id': router['id'], + 'subnet_id': tenant_subnet['id']}) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['bgp-speaker']['id'] + self.admin_client.add_bgp_gateway_network(bgp_speaker_id, + ext_net['id']) + routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id) + self.assertEqual(1, len(routes['advertised_routes'])) + self.assertEqual(tenant_subnet['cidr'], + routes['advertised_routes'][0]['destination']) + fixed_ip = router['external_gateway_info']['external_fixed_ips'][0] + self.assertEqual(fixed_ip['ip_address'], + routes['advertised_routes'][0]['next_hop']) diff --git a/neutron/tests/api/test_bgp_speaker_extensions_negative.py b/neutron/tests/api/test_bgp_speaker_extensions_negative.py index 51968b732c2..a230e0a891c 100644 --- a/neutron/tests/api/test_bgp_speaker_extensions_negative.py +++ b/neutron/tests/api/test_bgp_speaker_extensions_negative.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import netaddr from tempest.lib import exceptions as lib_exc from neutron.tests.api import test_bgp_speaker_extensions as test_base @@ -51,3 +52,69 @@ class BgpSpeakerTestJSONNegative(test_base.BgpSpeakerTestJSONBase): self.assertRaises(lib_exc.BadRequest, self.update_bgp_speaker, bgp_speaker_id, local_as='4321') + + @test.idempotent_id('9cc33701-51e5-421f-a5d5-fd7b330e550f') + def test_get_advertised_routes_tenant_networks(self): + addr_scope1 = self.create_address_scope('my-scope1', ip_version=4) + addr_scope2 = self.create_address_scope('my-scope2', ip_version=4) + ext_net = self.create_shared_network(**{'router:external': True}) + tenant_net1 = self.create_network() + tenant_net2 = self.create_network() + ext_subnetpool = self.create_subnetpool( + 'test-pool-ext', + is_admin=True, + default_prefixlen=24, + address_scope_id=addr_scope1['id'], + prefixes=['8.0.0.0/8']) + tenant_subnetpool1 = self.create_subnetpool( + 'tenant-test-pool', + default_prefixlen=25, + address_scope_id=addr_scope1['id'], + prefixes=['10.10.0.0/16']) + tenant_subnetpool2 = self.create_subnetpool( + 'tenant-test-pool', + default_prefixlen=25, + address_scope_id=addr_scope2['id'], + prefixes=['11.10.0.0/16']) + self.create_subnet({'id': ext_net['id']}, + cidr=netaddr.IPNetwork('8.0.0.0/24'), + ip_version=4, + client=self.admin_client, + subnetpool_id=ext_subnetpool['id']) + tenant_subnet1 = self.create_subnet( + {'id': tenant_net1['id']}, + cidr=netaddr.IPNetwork('10.10.0.0/24'), + ip_version=4, + subnetpool_id=tenant_subnetpool1['id']) + tenant_subnet2 = self.create_subnet( + {'id': tenant_net2['id']}, + cidr=netaddr.IPNetwork('11.10.0.0/24'), + ip_version=4, + subnetpool_id=tenant_subnetpool2['id']) + ext_gw_info = {'network_id': ext_net['id']} + router = self.admin_client.create_router( + 'my-router', + distributed=False, + external_gateway_info=ext_gw_info)['router'] + self.admin_routers.append(router) + self.admin_client.add_router_interface_with_subnet_id( + router['id'], + tenant_subnet1['id']) + self.admin_routerports.append({'router_id': router['id'], + 'subnet_id': tenant_subnet1['id']}) + self.admin_client.add_router_interface_with_subnet_id( + router['id'], + tenant_subnet2['id']) + self.admin_routerports.append({'router_id': router['id'], + 'subnet_id': tenant_subnet2['id']}) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['bgp-speaker']['id'] + self.admin_client.add_bgp_gateway_network(bgp_speaker_id, + ext_net['id']) + routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id) + self.assertEqual(1, len(routes['advertised_routes'])) + self.assertEqual(tenant_subnet1['cidr'], + routes['advertised_routes'][0]['destination']) + fixed_ip = router['external_gateway_info']['external_fixed_ips'][0] + self.assertEqual(fixed_ip['ip_address'], + routes['advertised_routes'][0]['next_hop']) diff --git a/neutron/tests/tempest/services/network/json/network_client.py b/neutron/tests/tempest/services/network/json/network_client.py index 9f6ad7a058c..3cadcbb471c 100644 --- a/neutron/tests/tempest/services/network/json/network_client.py +++ b/neutron/tests/tempest/services/network/json/network_client.py @@ -320,6 +320,22 @@ class NetworkClientJSON(service_client.ServiceClient): body = json.loads(body) return service_client.ResponseBody(resp, body) + def get_bgp_advertised_routes(self, bgp_speaker_id): + base_uri = '%s/bgp-speakers/%s/get_advertised_routes' + uri = base_uri % (self.uri_prefix, bgp_speaker_id) + resp, body = self.get(uri) + body = {'advertised_routes': self.deserialize_list(body)} + self.expected_success(200, resp.status) + return service_client.ResponseBody(resp, body) + + def get_bgp_router_routes(self, router_id): + base_uri = '%s/router-routes/%s' + uri = base_uri % (self.uri_prefix, router_id) + resp, body = self.get(uri) + body = self.deserialize_list(body) + self.expected_success(200, resp.status) + return service_client.ResponseBody(resp, body) + # Common methods that are hard to automate def create_bulk_network(self, names, shared=False): network_list = [{'name': name, 'shared': shared} for name in names] diff --git a/neutron/tests/unit/db/test_bgp_db.py b/neutron/tests/unit/db/test_bgp_db.py index 2d417d8f97a..a7d384be4ec 100644 --- a/neutron/tests/unit/db/test_bgp_db.py +++ b/neutron/tests/unit/db/test_bgp_db.py @@ -13,10 +13,13 @@ # under the License. import contextlib +import netaddr from oslo_utils import uuidutils +from neutron.api.v2 import attributes as attrs from neutron.common import exceptions as n_exc from neutron.extensions import bgp +from neutron.extensions import external_net from neutron import manager from neutron.plugins.common import constants as p_const from neutron.services.bgp import bgp_plugin @@ -67,10 +70,79 @@ class BgpEntityCreationMixin(object): yield bgp_peer self.bgp_plugin.delete_bgp_peer(self.context, bgp_peer['id']) + @contextlib.contextmanager + def bgp_speaker_with_gateway_network(self, address_scope_id, local_as, + advertise_fip_host_routes=True, + advertise_tenant_networks=True, + network_external=True, + fmt=None, set_context=False): + pass + + @contextlib.contextmanager + def bgp_speaker_with_router(self, address_scope_id, local_as, + gw_network_id=None, gw_subnet_ids=None, + tenant_subnet_ids=None, + advertise_fip_host_routes=True, + advertise_tenant_networks=True, + fmt=None, set_context=False, + router_distributed=False): + pass + + @contextlib.contextmanager + def router(self, name='bgp-test-router', tenant_id=_uuid(), + admin_state_up=True, **kwargs): + request = {'router': {'tenant_id': tenant_id, + 'name': name, + 'admin_state_up': admin_state_up}} + for arg in kwargs: + request['router'][arg] = kwargs[arg] + router = self.l3plugin.create_router(self.context, request) + yield router + + @contextlib.contextmanager + def router_with_external_and_tenant_networks( + self, + tenant_id=_uuid(), + gw_prefix='8.8.8.0/24', + tenant_prefix='192.168.0.0/16', + address_scope=None, + distributed=False): + prefixes = [gw_prefix, tenant_prefix] + gw_ip_net = netaddr.IPNetwork(gw_prefix) + tenant_ip_net = netaddr.IPNetwork(tenant_prefix) + subnetpool_args = {'tenant_id': tenant_id, + 'name': 'bgp-pool'} + if address_scope: + subnetpool_args['address_scope_id'] = address_scope['id'] + + with self.network() as ext_net, self.network() as int_net,\ + self.subnetpool(prefixes, **subnetpool_args) as pool: + subnetpool_id = pool['subnetpool']['id'] + gw_net_id = ext_net['network']['id'] + with self.subnet(ext_net, + cidr=gw_prefix, + subnetpool_id=subnetpool_id, + ip_version=gw_ip_net.version),\ + self.subnet(int_net, + cidr=tenant_prefix, + subnetpool_id=subnetpool_id, + ip_version=tenant_ip_net.version) as int_subnet: + self._update('networks', gw_net_id, + {'network': {external_net.EXTERNAL: True}}) + ext_gw_info = {'network_id': gw_net_id} + with self.router(external_gateway_info=ext_gw_info, + distributed=distributed) as router: + router_id = router['id'] + router_interface_info = {'subnet_id': + int_subnet['subnet']['id']} + self.l3plugin.add_router_interface(self.context, + router_id, + router_interface_info) + yield router, ext_net, int_net + class BgpTests(test_plugin.Ml2PluginV2TestCase, BgpEntityCreationMixin): - #FIXME(tidwellr) Lots of duplicated setup code, try to streamline fmt = 'json' def setUp(self): @@ -79,6 +151,8 @@ class BgpTests(test_plugin.Ml2PluginV2TestCase, p_const.L3_ROUTER_NAT) self.bgp_plugin = bgp_plugin.BgpPlugin() self.plugin = manager.NeutronManager.get_plugin() + self.l3plugin = manager.NeutronManager.get_service_plugins().get( + p_const.L3_ROUTER_NAT) @contextlib.contextmanager def subnetpool_with_address_scope(self, ip_version, prefixes=None, @@ -335,3 +409,422 @@ class BgpTests(test_plugin.Ml2PluginV2TestCase, self.assertRaises(bgp.InvalidBgpPeerMd5Authentication, self.bgp_plugin.create_bgp_peer, self.context, bgp_peer) + + def test__get_address_scope_ids_for_bgp_speaker(self): + prefixes1 = ['8.0.0.0/8'] + prefixes2 = ['9.0.0.0/8'] + prefixes3 = ['10.0.0.0/8'] + tenant_id = _uuid() + with self.bgp_speaker(4, 1234) as speaker,\ + self.subnetpool_with_address_scope(4, + prefixes=prefixes1, + tenant_id=tenant_id) as sp1,\ + self.subnetpool_with_address_scope(4, + prefixes=prefixes2, + tenant_id=tenant_id) as sp2,\ + self.subnetpool_with_address_scope(4, + prefixes=prefixes3, + tenant_id=tenant_id) as sp3,\ + self.network() as network1, self.network() as network2,\ + self.network() as network3: + network1_id = network1['network']['id'] + network2_id = network2['network']['id'] + network3_id = network3['network']['id'] + base_subnet_data = {'allocation_pools': attrs.ATTR_NOT_SPECIFIED, + 'cidr': attrs.ATTR_NOT_SPECIFIED, + 'prefixlen': attrs.ATTR_NOT_SPECIFIED, + 'ip_version': 4, + 'enable_dhcp': True, + 'dns_nameservers': attrs.ATTR_NOT_SPECIFIED, + 'host_routes': attrs.ATTR_NOT_SPECIFIED} + subnet1_data = {'network_id': network1_id, + 'subnetpool_id': sp1['id'], + 'name': 'subnet1', + 'tenant_id': tenant_id} + subnet2_data = {'network_id': network2_id, + 'subnetpool_id': sp2['id'], + 'name': 'subnet2', + 'tenant_id': tenant_id} + subnet3_data = {'network_id': network3_id, + 'subnetpool_id': sp3['id'], + 'name': 'subnet2', + 'tenant_id': tenant_id} + for k in base_subnet_data: + subnet1_data[k] = base_subnet_data[k] + subnet2_data[k] = base_subnet_data[k] + subnet3_data[k] = base_subnet_data[k] + + self.plugin.create_subnet(self.context, {'subnet': subnet1_data}) + self.plugin.create_subnet(self.context, {'subnet': subnet2_data}) + self.plugin.create_subnet(self.context, {'subnet': subnet3_data}) + self.bgp_plugin.add_gateway_network(self.context, speaker['id'], + {'network_id': network1_id}) + self.bgp_plugin.add_gateway_network(self.context, speaker['id'], + {'network_id': network2_id}) + scopes = self.bgp_plugin._get_address_scope_ids_for_bgp_speaker( + self.context, + speaker['id']) + self.assertEqual(2, len(scopes)) + self.assertTrue(sp1['address_scope_id'] in scopes) + self.assertTrue(sp2['address_scope_id'] in scopes) + + def test_get_routes_by_bgp_speaker_binding(self): + gw_prefix = '172.16.10.0/24' + tenant_prefix = '10.10.10.0/24' + tenant_id = _uuid() + scope_data = {'tenant_id': tenant_id, 'ip_version': 4, + 'shared': True, 'name': 'bgp-scope'} + scope = self.plugin.create_address_scope( + self.context, + {'address_scope': scope_data}) + with self.router_with_external_and_tenant_networks( + tenant_id=tenant_id, + gw_prefix=gw_prefix, + tenant_prefix=tenant_prefix, + address_scope=scope) as res: + router, ext_net, int_net = res + ext_gw_info = router['external_gateway_info'] + gw_net_id = ext_net['network']['id'] + with self.bgp_speaker(4, 1234, + networks=[gw_net_id]) as speaker: + bgp_speaker_id = speaker['id'] + routes = self.bgp_plugin.get_routes_by_bgp_speaker_binding( + self.context, + bgp_speaker_id, + gw_net_id) + routes = list(routes) + next_hop = ext_gw_info['external_fixed_ips'][0]['ip_address'] + self.assertEqual(1, len(routes)) + self.assertEqual(tenant_prefix, routes[0]['destination']) + self.assertEqual(next_hop, routes[0]['next_hop']) + + def test_get_routes_by_binding_network(self): + gw_prefix = '172.16.10.0/24' + tenant_prefix = '10.10.10.0/24' + tenant_id = _uuid() + scope_data = {'tenant_id': tenant_id, 'ip_version': 4, + 'shared': True, 'name': 'bgp-scope'} + scope = self.plugin.create_address_scope( + self.context, + {'address_scope': scope_data}) + with self.router_with_external_and_tenant_networks( + tenant_id=tenant_id, + gw_prefix=gw_prefix, + tenant_prefix=tenant_prefix, + address_scope=scope) as res: + router, ext_net, int_net = res + ext_gw_info = router['external_gateway_info'] + gw_net_id = ext_net['network']['id'] + with self.bgp_speaker(4, 1234, networks=[gw_net_id]) as speaker: + bgp_speaker_id = speaker['id'] + routes = self.bgp_plugin.get_routes_by_bgp_speaker_binding( + self.context, + bgp_speaker_id, + gw_net_id) + routes = list(routes) + next_hop = ext_gw_info['external_fixed_ips'][0]['ip_address'] + self.assertEqual(1, len(routes)) + self.assertEqual(tenant_prefix, routes[0]['destination']) + self.assertEqual(next_hop, routes[0]['next_hop']) + + def _advertised_routes_by_bgp_speaker(self, + bgp_speaker_ip_version, + local_as, + tenant_cidr, + gateway_cidr, + fip_routes=True, + router_distributed=False): + tenant_id = _uuid() + scope_data = {'tenant_id': tenant_id, + 'ip_version': bgp_speaker_ip_version, + 'shared': True, + 'name': 'bgp-scope'} + scope = self.plugin.create_address_scope( + self.context, + {'address_scope': scope_data}) + with self.router_with_external_and_tenant_networks( + tenant_id=tenant_id, + gw_prefix=gateway_cidr, + tenant_prefix=tenant_cidr, + address_scope=scope, + distributed=router_distributed) as res: + router, ext_net, int_net = res + gw_net_id = ext_net['network']['id'] + with self.bgp_speaker( + bgp_speaker_ip_version, + local_as, + networks=[gw_net_id], + advertise_fip_host_routes=fip_routes) as speaker: + routes = self.bgp_plugin.get_advertised_routes( + self.context, + speaker['id']) + return routes['advertised_routes'] + + def test__tenant_prefixes_by_router_no_gateway_port(self): + with self.network() as net1, self.network() as net2,\ + self.subnetpool_with_address_scope(6, tenant_id='test-tenant', + prefixes=['2001:db8::/63']) as pool: + subnetpool_id = pool['id'] + with self.subnet(network=net1, + cidr=None, + subnetpool_id=subnetpool_id, + ip_version=6) as ext_subnet,\ + self.subnet(network=net2, + cidr=None, + subnetpool_id=subnetpool_id, + ip_version=6) as int_subnet,\ + self.router() as router: + + router_id = router['id'] + int_subnet_id = int_subnet['subnet']['id'] + ext_subnet_id = ext_subnet['subnet']['id'] + self.l3plugin.add_router_interface(self.context, + router_id, + {'subnet_id': + int_subnet_id}) + self.l3plugin.add_router_interface(self.context, + router_id, + {'subnet_id': + ext_subnet_id}) + with self.bgp_speaker(6, 1234) as speaker: + bgp_speaker_id = speaker['id'] + cidrs = list(self.bgp_plugin._tenant_prefixes_by_router( + self.context, + router_id, + bgp_speaker_id)) + self.assertFalse(cidrs) + + def test_get_ipv6_tenant_subnet_routes_by_bgp_speaker_ipv6(self): + tenant_cidr = '2001:db8::/64' + binding_cidr = '2001:ab8::/64' + routes = self._advertised_routes_by_bgp_speaker(6, 1234, tenant_cidr, + binding_cidr) + self.assertEqual(1, len(routes)) + dest_prefix = routes[0]['destination'] + next_hop = routes[0]['next_hop'] + self.assertEqual(tenant_cidr, dest_prefix) + self.assertTrue(netaddr.IPSet([binding_cidr]).__contains__(next_hop)) + + def test_get_ipv4_tenant_subnet_routes_by_bgp_speaker_ipv4(self): + tenant_cidr = '172.16.10.0/24' + binding_cidr = '20.10.1.0/24' + routes = self._advertised_routes_by_bgp_speaker(4, 1234, tenant_cidr, + binding_cidr) + routes = list(routes) + self.assertEqual(1, len(routes)) + dest_prefix = routes[0]['destination'] + next_hop = routes[0]['next_hop'] + self.assertEqual(tenant_cidr, dest_prefix) + self.assertTrue(netaddr.IPSet([binding_cidr]).__contains__(next_hop)) + + def test_get_ipv4_tenant_subnet_routes_by_bgp_speaker_dvr_router(self): + tenant_cidr = '172.16.10.0/24' + binding_cidr = '20.10.1.0/24' + routes = self._advertised_routes_by_bgp_speaker( + 4, + 1234, + tenant_cidr, + binding_cidr, + router_distributed=True) + routes = list(routes) + self.assertEqual(1, len(routes)) + + def test_all_routes_by_bgp_speaker_different_tenant_address_scope(self): + binding_cidr = '2001:db8::/64' + tenant_cidr = '2002:ab8::/64' + with self.subnetpool_with_address_scope(6, tenant_id='test-tenant', + prefixes=[binding_cidr]) as ext_pool,\ + self.subnetpool_with_address_scope(6, tenant_id='test-tenant', + prefixes=[tenant_cidr]) as int_pool,\ + self.network() as ext_net, self.network() as int_net: + gw_net_id = ext_net['network']['id'] + ext_pool_id = ext_pool['id'] + int_pool_id = int_pool['id'] + self._update('networks', gw_net_id, + {'network': {external_net.EXTERNAL: True}}) + with self.subnet(cidr=None, + subnetpool_id=ext_pool_id, + network=ext_net, + ip_version=6) as ext_subnet,\ + self.subnet(cidr=None, + subnetpool_id=int_pool_id, + network=int_net, + ip_version=6) as int_subnet,\ + self.router() as router: + router_id = router['id'] + int_subnet_id = int_subnet['subnet']['id'] + ext_subnet_id = ext_subnet['subnet']['id'] + self.l3plugin.add_router_interface(self.context, + router_id, + {'subnet_id': + int_subnet_id}) + self.l3plugin.add_router_interface(self.context, + router_id, + {'subnet_id': + ext_subnet_id}) + with self.bgp_speaker(6, 1234, + networks=[gw_net_id]) as speaker: + bgp_speaker_id = speaker['id'] + cidrs = self.bgp_plugin.get_routes_by_bgp_speaker_id( + self.context, + bgp_speaker_id) + self.assertEqual(0, len(list(cidrs))) + + def test__get_routes_by_router_with_fip(self): + gw_prefix = '172.16.10.0/24' + tenant_prefix = '10.10.10.0/24' + tenant_id = _uuid() + scope_data = {'tenant_id': tenant_id, 'ip_version': 4, + 'shared': True, 'name': 'bgp-scope'} + scope = self.plugin.create_address_scope( + self.context, + {'address_scope': scope_data}) + with self.router_with_external_and_tenant_networks( + tenant_id=tenant_id, + gw_prefix=gw_prefix, + tenant_prefix=tenant_prefix, + address_scope=scope) as res: + router, ext_net, int_net = res + ext_gw_info = router['external_gateway_info'] + gw_net_id = ext_net['network']['id'] + tenant_net_id = int_net['network']['id'] + fixed_port_data = {'port': + {'name': 'test', + 'network_id': tenant_net_id, + 'tenant_id': tenant_id, + 'admin_state_up': True, + 'device_id': _uuid(), + 'device_owner': 'compute:nova', + 'mac_address': attrs.ATTR_NOT_SPECIFIED, + 'fixed_ips': attrs.ATTR_NOT_SPECIFIED}} + fixed_port = self.plugin.create_port(self.context, + fixed_port_data) + fip_data = {'floatingip': {'floating_network_id': gw_net_id, + 'tenant_id': tenant_id, + 'port_id': fixed_port['id']}} + fip = self.l3plugin.create_floatingip(self.context, fip_data) + fip_prefix = fip['floating_ip_address'] + '/32' + with self.bgp_speaker(4, 1234, networks=[gw_net_id]) as speaker: + bgp_speaker_id = speaker['id'] + routes = self.bgp_plugin._get_routes_by_router(self.context, + router['id']) + routes = routes[bgp_speaker_id] + next_hop = ext_gw_info['external_fixed_ips'][0]['ip_address'] + self.assertEqual(2, len(routes)) + tenant_prefix_found = False + fip_prefix_found = False + for route in routes: + self.assertEqual(next_hop, route['next_hop']) + if route['destination'] == tenant_prefix: + tenant_prefix_found = True + if route['destination'] == fip_prefix: + fip_prefix_found = True + self.assertTrue(tenant_prefix_found) + self.assertTrue(fip_prefix_found) + + def test_get_routes_by_bgp_speaker_id_with_fip(self): + gw_prefix = '172.16.10.0/24' + tenant_prefix = '10.10.10.0/24' + tenant_id = _uuid() + scope_data = {'tenant_id': tenant_id, 'ip_version': 4, + 'shared': True, 'name': 'bgp-scope'} + scope = self.plugin.create_address_scope( + self.context, + {'address_scope': scope_data}) + with self.router_with_external_and_tenant_networks( + tenant_id=tenant_id, + gw_prefix=gw_prefix, + tenant_prefix=tenant_prefix, + address_scope=scope) as res: + router, ext_net, int_net = res + ext_gw_info = router['external_gateway_info'] + gw_net_id = ext_net['network']['id'] + tenant_net_id = int_net['network']['id'] + fixed_port_data = {'port': + {'name': 'test', + 'network_id': tenant_net_id, + 'tenant_id': tenant_id, + 'admin_state_up': True, + 'device_id': _uuid(), + 'device_owner': 'compute:nova', + 'mac_address': attrs.ATTR_NOT_SPECIFIED, + 'fixed_ips': attrs.ATTR_NOT_SPECIFIED}} + fixed_port = self.plugin.create_port(self.context, + fixed_port_data) + fip_data = {'floatingip': {'floating_network_id': gw_net_id, + 'tenant_id': tenant_id, + 'port_id': fixed_port['id']}} + fip = self.l3plugin.create_floatingip(self.context, fip_data) + fip_prefix = fip['floating_ip_address'] + '/32' + with self.bgp_speaker(4, 1234, networks=[gw_net_id]) as speaker: + bgp_speaker_id = speaker['id'] + routes = self.bgp_plugin.get_routes_by_bgp_speaker_id( + self.context, + bgp_speaker_id) + routes = list(routes) + next_hop = ext_gw_info['external_fixed_ips'][0]['ip_address'] + self.assertEqual(2, len(routes)) + tenant_prefix_found = False + fip_prefix_found = False + for route in routes: + self.assertEqual(next_hop, route['next_hop']) + if route['destination'] == tenant_prefix: + tenant_prefix_found = True + if route['destination'] == fip_prefix: + fip_prefix_found = True + self.assertTrue(tenant_prefix_found) + self.assertTrue(fip_prefix_found) + + def test_get_routes_by_bgp_speaker_binding_with_fip(self): + gw_prefix = '172.16.10.0/24' + tenant_prefix = '10.10.10.0/24' + tenant_id = _uuid() + scope_data = {'tenant_id': tenant_id, 'ip_version': 4, + 'shared': True, 'name': 'bgp-scope'} + scope = self.plugin.create_address_scope( + self.context, + {'address_scope': scope_data}) + with self.router_with_external_and_tenant_networks( + tenant_id=tenant_id, + gw_prefix=gw_prefix, + tenant_prefix=tenant_prefix, + address_scope=scope) as res: + router, ext_net, int_net = res + ext_gw_info = router['external_gateway_info'] + gw_net_id = ext_net['network']['id'] + tenant_net_id = int_net['network']['id'] + fixed_port_data = {'port': + {'name': 'test', + 'network_id': tenant_net_id, + 'tenant_id': tenant_id, + 'admin_state_up': True, + 'device_id': _uuid(), + 'device_owner': 'compute:nova', + 'mac_address': attrs.ATTR_NOT_SPECIFIED, + 'fixed_ips': attrs.ATTR_NOT_SPECIFIED}} + fixed_port = self.plugin.create_port(self.context, + fixed_port_data) + fip_data = {'floatingip': {'floating_network_id': gw_net_id, + 'tenant_id': tenant_id, + 'port_id': fixed_port['id']}} + fip = self.l3plugin.create_floatingip(self.context, fip_data) + fip_prefix = fip['floating_ip_address'] + '/32' + with self.bgp_speaker(4, 1234, networks=[gw_net_id]) as speaker: + bgp_speaker_id = speaker['id'] + routes = self.bgp_plugin.get_routes_by_bgp_speaker_binding( + self.context, + bgp_speaker_id, + gw_net_id) + routes = list(routes) + next_hop = ext_gw_info['external_fixed_ips'][0]['ip_address'] + self.assertEqual(2, len(routes)) + tenant_prefix_found = False + fip_prefix_found = False + for route in routes: + self.assertEqual(next_hop, route['next_hop']) + if route['destination'] == tenant_prefix: + tenant_prefix_found = True + if route['destination'] == fip_prefix: + fip_prefix_found = True + self.assertTrue(tenant_prefix_found) + self.assertTrue(fip_prefix_found) diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py index befb187a3ff..11560dedd93 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -318,7 +318,6 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase): def _create_subnet(self, fmt, net_id, cidr, expected_res_status=None, **kwargs): data = {'subnet': {'network_id': net_id, - 'cidr': cidr, 'ip_version': 4, 'tenant_id': self._tenant_id}} if cidr: