Enable router az and simplify net topology

1. What is the problem
1) Tricircle does not enable router's az
2) Tricircle's network topology is too complex for local
router(router reside only in one region)
2. What is the solution to the problem
1) Enable router's az
2) Remove ns-router and bridge network when used local router

3. What the features need to be implemented to the Tricircle
1)Enable router's az
2)Attach external network to local router directly, no additional
intermediate router is needed.

Implements: blueprint enable-router-az-simplify-net-top
Change-Id: I410a81c9d0e56db8163e611211b8dbd4c5772767
This commit is contained in:
xiulin yin 2017-02-22 20:09:17 +08:00
parent 9e60abfe8f
commit da443110e9
6 changed files with 575 additions and 117 deletions

View File

@ -0,0 +1,9 @@
---
features:
- |
Router
* Support availability zone for router
* Local router, which will reside only inside one region, can be
attached with external network directly, no additional intermediate
router is needed.

View File

@ -19,10 +19,13 @@ Tricircle base exception handling.
import six
from neutron_lib import exceptions
from oslo_log import log as logging
from tricircle.common.i18n import _
from tricircle.common.i18n import _LE
LOG = logging.getLogger(__name__)
@ -233,3 +236,12 @@ class RoutingBindFail(TricircleException):
def __init__(self, _type):
super(RoutingBindFail, self).__init__(_type=_type)
class RouterNetworkLocationMismatch(exceptions.InvalidInput):
message = _("router located in %(router_az_hint)s, but network located "
"in %(net_az_hints)s, location mismatch.")
def __init__(self, router_az_hints, net_az_hints):
super(RouterNetworkLocationMismatch, self).__init__(
router_az_hint=router_az_hints, net_az_hints=net_az_hints)

View File

@ -23,6 +23,10 @@ import oslo_log.helpers as log_helpers
from oslo_log import log
from neutron.api.v2 import attributes
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.callbacks import resources
from neutron.db.availability_zone import router as router_az
from neutron.db import common_db_mixin
from neutron.db import db_base_plugin_v2
from neutron.db import external_net_db
@ -30,7 +34,6 @@ from neutron.db import extradhcpopt_db
# NOTE(zhiyuan) though not used, this import cannot be removed because Router
# relies on one table defined in l3_agentschedulers_db
from neutron.db import l3_agentschedulers_db # noqa
from neutron.db import l3_attrs_db
from neutron.db import l3_db
from neutron.db import l3_dvr_db
# import l3_hamode_db to load l3_ha option
@ -112,7 +115,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
portbindings_db.PortBindingMixin,
extradhcpopt_db.ExtraDhcpOptMixin,
l3_db.L3_NAT_dbonly_mixin,
l3_attrs_db.ExtraAttributesMixin):
router_az.RouterAvailabilityZoneMixin):
__native_bulk_support = True
__native_pagination_support = True
@ -131,7 +134,14 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
"provider",
"network_availability_zone",
"dvr",
"router"]
"router",
"router_availability_zone"]
def __new__(cls, *args, **kwargs):
n = super(TricirclePlugin, cls).__new__(cls, *args, **kwargs)
registry.subscribe(n._set_distributed_flag,
resources.ROUTER, events.PRECOMMIT_CREATE)
return n
def __init__(self):
super(TricirclePlugin, self).__init__()
@ -160,12 +170,23 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
# self.conn.create_consumer(self.topic, self.endpoints, fanout=False)
# return self.conn.consume_in_threads()
def _set_distributed_flag(self, resource, event, trigger, context,
router, router_db, **kwargs):
"""Event handler to set distributed flag on creation."""
dist = l3_dvr_db.is_distributed_router(router)
router['distributed'] = dist
self.set_extra_attr_value(context, router_db, 'distributed', dist)
def validate_availability_zones(self, context, resource_type,
availability_zones):
self._validate_availability_zones(context, availability_zones)
@staticmethod
def _validate_availability_zones(context, az_list):
if not az_list:
return
t_ctx = t_context.get_context_from_neutron_context(context)
with context.session.begin():
with context.session.begin(subtransactions=True):
pods = core.query_resource(t_ctx, models.Pod, [], [])
az_set = set(az_list)
@ -368,16 +389,18 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
def _convert_az2region_for_net(self, context, net):
az_hints = net.get(az_ext.AZ_HINTS)
if context.is_admin and az_hints:
net[az_ext.AZ_HINTS] = []
t_ctx = t_context.get_context_from_neutron_context(context)
for az_hint in az_hints:
pods = db_api.find_pods_by_az_or_region(t_ctx, az_hint)
if not pods:
continue
for pod in pods:
region_name = pod['region_name']
if region_name not in net[az_ext.AZ_HINTS]:
net[az_ext.AZ_HINTS].append(region_name)
net[az_ext.AZ_HINTS] = self._convert_az2region(t_ctx, az_hints)
def _convert_az2region(self, t_ctx, az_hints):
region_names = set()
for az_hint in az_hints:
pods = db_api.find_pods_by_az_or_region(t_ctx, az_hint)
if not pods:
continue
for pod in pods:
region_names.add(pod['region_name'])
return list(region_names)
def get_network(self, context, network_id, fields=None):
net = super(TricirclePlugin, self).get_network(context, network_id,
@ -1068,12 +1091,8 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
with context.session.begin(subtransactions=True):
router_db = super(TricirclePlugin, self).create_router(
context, router)
router_db['extra_attributes'] = None
dist = l3_dvr_db.is_distributed_router(router['router'])
self.set_extra_attr_value(context, router_db, 'distributed', dist)
router_db['distributed'] = router_db[
'extra_attributes'].distributed
return router_db
return router_db
def _delete_top_bridge_resource(self, t_ctx, q_ctx, resource_type,
resource_id, resource_name):
@ -1132,25 +1151,32 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
t_ctx = t_context.get_context_from_neutron_context(context)
mappings = db_api.get_bottom_mappings_by_top_id(t_ctx, _id,
t_constants.RT_ROUTER)
is_local_router = self.helper.is_local_router(t_ctx, router)
for pod, b_router_id in mappings:
b_client = self._get_client(pod['region_name'])
bridge_port_name = t_constants.bridge_port_name % (project_id,
b_router_id)
bridge_ports = super(TricirclePlugin, self).get_ports(
context, {'name': [bridge_port_name]})
if bridge_ports:
t_ns_port_id = bridge_ports[0]['id']
b_client.action_routers(t_ctx, 'remove_gateway', b_router_id)
self._delete_top_bridge_port(t_ctx, context, t_ns_port_id,
bridge_port_name)
if not is_local_router:
bridge_port_name = t_constants.bridge_port_name % (project_id,
b_router_id)
bridge_ports = super(TricirclePlugin, self).get_ports(
context, {'name': [bridge_port_name]})
if bridge_ports:
t_ns_port_id = bridge_ports[0]['id']
b_client.action_routers(t_ctx, 'remove_gateway',
b_router_id)
self._delete_top_bridge_port(t_ctx, context, t_ns_port_id,
bridge_port_name)
b_client.delete_routers(t_ctx, b_router_id)
db_api.delete_mappings_by_bottom_id(t_ctx, b_router_id)
if is_local_router:
super(TricirclePlugin, self).delete_router(context, _id)
return
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, _id, t_constants.RT_NS_ROUTER)
for pod, b_ns_router_id in mappings:
b_client = self._get_client(pod['region_name'])
bridge_subnet_name = t_constants.bridge_subnet_name % project_id
bridge_subnet_name = (t_constants.bridge_subnet_name % project_id)
bridge_subnets = super(TricirclePlugin,
self).get_subnets(
context, {'name': [bridge_subnet_name]})
@ -1267,14 +1293,20 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
return self.helper.get_bottom_bridge_elements(
t_ctx, project_id, pod, t_net, is_external, t_subnet, t_port)
def _get_net_pods_by_interface_info(self, t_ctx, q_ctx, add_by_port,
interface_info):
def _get_net_id_by_interface_info(self, q_ctx, add_by_port,
interface_info):
if add_by_port:
port = self.get_port(q_ctx, interface_info['port_id'])
net_id = port['network_id']
else:
subnet = self.get_subnet(q_ctx, interface_info['subnet_id'])
net_id = subnet['network_id']
return net_id
def _get_net_pods_by_interface_info(self, t_ctx, q_ctx, add_by_port,
interface_info):
net_id = self._get_net_id_by_interface_info(q_ctx, add_by_port,
interface_info)
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, net_id, t_constants.RT_NETWORK)
return net_id, [mapping[0] for mapping in mappings]
@ -1305,6 +1337,11 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
# network ID from resource routing table.
if not network.get(az_ext.AZ_HINTS):
raise t_exceptions.ExternalNetPodNotSpecify()
t_router = self._get_router(context, router_id)
self.validate_router_net_location_match(t_ctx, t_router, network)
is_local_router = self.helper.is_local_router(t_ctx, t_router)
region_name = network[az_ext.AZ_HINTS][0]
pod = db_api.get_pod_by_name(t_ctx, region_name)
b_net_id = db_api.get_bottom_id_by_top_id_region_name(
@ -1312,16 +1349,25 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
# create corresponding bottom router in the pod where external network
# is located.
t_router = self._get_router(context, router_id)
if is_local_router:
# if router is a local router, we will use RT_ROUTER to attach
# external network, else use RT_NS_ROUTER
router_type = t_constants.RT_ROUTER
is_distributed = t_router.get('distributed', False)
body = {'router': {'name': t_router['id'],
'distributed': is_distributed}}
else:
# TODO(zhiyuan) decide router is distributed or not from pod table
# currently "distributed" is set to False, should add a metadata
# field to pod table, and decide distributed or not from the
# metadata later
router_type = t_constants.RT_NS_ROUTER
body = {'router': {'name': t_constants.ns_router_name % router_id,
'distributed': False}}
# TODO(zhiyuan) decide router is distributed or not from pod table
# currently "distributed" is set to False, should add a metadata field
# to pod table, and decide distributed or not from the metadata later
body = {'router': {'name': t_constants.ns_router_name % router_id,
'distributed': False}}
_, b_router_id = self._prepare_bottom_element(
t_ctx, t_router['tenant_id'], pod, t_router,
t_constants.RT_NS_ROUTER, body)
t_ctx, t_router['tenant_id'], pod, t_router, router_type, body)
# both router and external network in bottom pod are ready, attach
# external network to router in bottom pod.
@ -1342,6 +1388,9 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
b_info['external_fixed_ips'] = fixed_ips
b_client.action_routers(t_ctx, 'add_gateway', b_router_id, b_info)
if is_local_router:
return
# when internal network(providing fixed ip) and external network
# (providing floating ip) are in different bottom pods, we utilize a
# bridge network to connect these two networks. here we create the
@ -1391,8 +1440,13 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
raise t_exceptions.ExternalNetPodNotSpecify()
region_name = t_network[az_ext.AZ_HINTS][0]
is_local_router = self.helper.is_local_router(t_ctx, t_router)
b_router_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, router_id, region_name, t_constants.RT_NS_ROUTER)
t_ctx, router_id, region_name,
t_constants.RT_ROUTER if is_local_router
else t_constants.RT_NS_ROUTER)
b_client = self._get_client(region_name)
b_client.action_routers(t_ctx, 'remove_gateway', b_router_id)
@ -1430,30 +1484,63 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
self._remove_router_gateway(context, router_id)
ret = super(TricirclePlugin, self).update_router(
context, router_id, router)
t_ctx = t_context.get_context_from_neutron_context(context)
self.xjob_handler.configure_extra_routes(t_ctx, router_id)
is_local_router = self.helper.is_local_router(t_ctx, router)
if not is_local_router:
self.xjob_handler.configure_extra_routes(t_ctx, router_id)
return ret
def validate_router_net_location_match(self, t_ctx, router, net):
router_az_hints = self.helper.get_router_az_hints(router)
if not router_az_hints:
return
router_region_names = self._convert_az2region(t_ctx, router_az_hints)
router_region_set = set(router_region_names)
net_az_hints = net.get(az_ext.AZ_HINTS)
if not net_az_hints:
raise t_exceptions.RouterNetworkLocationMismatch(
router_az_hints=router_region_names,
net_az_hints=['All Region'])
# net_az_hints already convert to region name when user is admin
if t_ctx.is_admin:
net_region_names = net_az_hints
else:
net_region_names = self._convert_az2region(t_ctx, net_az_hints)
net_region_set = set(net_region_names)
diff = net_region_set - router_region_set
if diff:
raise t_exceptions.RouterNetworkLocationMismatch(
router_az_hints=router_region_names,
net_az_hints=net_region_names)
def add_router_interface(self, context, router_id, interface_info):
t_ctx = t_context.get_context_from_neutron_context(context)
router = self._get_router(context, router_id)
project_id = router['tenant_id']
add_by_port, _ = self._validate_interface_info(interface_info)
net_id = self._get_net_id_by_interface_info(
context, add_by_port, interface_info)
net = self.get_network(context, net_id)
self.validate_router_net_location_match(t_ctx, router, net)
is_local_router = self.helper.is_local_router(t_ctx, router)
t_pod = db_api.get_top_pod(t_ctx)
assert t_pod
# bridge network for E-W and N-S networking
pool_id = self._get_bridge_subnet_pool_id(
t_ctx, context, None, t_pod)
self._get_bridge_network_subnet(
t_ctx, context, project_id, t_pod, pool_id)
if not is_local_router:
# for single external network, need bridge network for
# E-W and N-S networking
pool_id = self._get_bridge_subnet_pool_id(
t_ctx, context, None, t_pod)
self._get_bridge_network_subnet(
t_ctx, context, project_id, t_pod, pool_id)
return_info = super(TricirclePlugin, self).add_router_interface(
context, router_id, interface_info)
net_id, b_pods = self._get_net_pods_by_interface_info(
_, b_pods = self._get_net_pods_by_interface_info(
t_ctx, context, add_by_port, interface_info)
if not b_pods:
LOG.debug('Add router interface: no interfaces found, xjob not'

View File

@ -18,6 +18,7 @@ import six
from neutron_lib import constants
import neutronclient.common.exceptions as q_cli_exceptions
from oslo_serialization import jsonutils
from tricircle.common import client
import tricircle.common.constants as t_constants
@ -642,3 +643,28 @@ class NetworkHelper(object):
'port_range_max': rule.get('port_range_max'),
'port_range_min': rule.get('port_range_min'),
'security_group_id': sg_id}}
@staticmethod
def get_router_az_hints(router):
# when called by api, availability_zone_hints included in
# extra_attributes, but when called by xjob, it included in router
# body directly.
extra_attributes = router.get('extra_attributes')
az_hints = router.get(AZ_HINTS)
if extra_attributes:
az_hints = extra_attributes.get(AZ_HINTS)
if not az_hints:
return None
if not isinstance(az_hints, list):
az_hints = jsonutils.loads(az_hints)
return az_hints
@staticmethod
def is_local_router(t_ctx, router):
router_az_hints = NetworkHelper.get_router_az_hints(router)
if not router_az_hints:
return False
if len(router_az_hints) > 1:
return False
router_az_hint = router_az_hints[0]
return bool(db_api.get_pod_by_name(t_ctx, router_az_hint))

View File

@ -61,9 +61,8 @@ from oslo_utils import uuidutils
from tricircle.common import client
from tricircle.common import constants
from tricircle.common import context
from tricircle.common import exceptions as t_exceptions
from tricircle.common.i18n import _
import tricircle.db.api as db_api
from tricircle.db import core
from tricircle.db import models
@ -107,7 +106,8 @@ BOTTOM2_FIPS = []
RES_LIST = [TOP_NETS, TOP_SUBNETS, TOP_PORTS, TOP_ROUTERS, TOP_ROUTERPORT,
TOP_SUBNETPOOLS, TOP_SUBNETPOOLPREFIXES, TOP_IPALLOCATIONS,
TOP_VLANALLOCATIONS, TOP_SEGMENTS, TOP_EXTNETS, TOP_FLOATINGIPS,
TOP_SGS, TOP_SG_RULES,
TOP_SGS, TOP_SG_RULES, TOP_NETWORK_RBAC, TOP_SUBNETROUTES,
TOP_DNSNAMESERVERS,
BOTTOM1_NETS, BOTTOM1_SUBNETS, BOTTOM1_PORTS, BOTTOM1_ROUTERS,
BOTTOM1_SGS, BOTTOM1_FIPS,
BOTTOM2_NETS, BOTTOM2_SUBNETS, BOTTOM2_PORTS, BOTTOM2_ROUTERS,
@ -251,6 +251,9 @@ class DotDict(dict):
return []
return self.get(item)
def to_dict(self):
return self
def __copy__(self):
return DotDict(self)
@ -572,6 +575,9 @@ class FakeClient(object):
elif action == 'remove_interface':
return self.remove_interface_routers(ctx, *args, **kwargs)
def _is_bridge_network_attached():
pass
def create_floatingips(self, ctx, body):
fip = self.create_resources('floatingip', ctx, body)
for key in ['fixed_port_id']:
@ -1856,20 +1862,24 @@ class PluginTest(unittest.TestCase,
'resource_type': constants.RT_SUBNET})
return t_net_id, t_subnet_id, b_net_id, b_subnet_id
def _prepare_router_test(self, tenant_id, ctx, region_name, index):
def _prepare_router_test(self, tenant_id, ctx, region_name, index,
router_az_hints=None, net_az_hints=None,
create_new_router=False):
(t_net_id, t_subnet_id, b_net_id,
b_subnet_id) = self._prepare_network_test(tenant_id, ctx, region_name,
index)
if len(TOP_ROUTERS) == 0:
t_router_id = uuidutils.generate_uuid()
t_router = {
'id': t_router_id,
'name': 'top_router',
'distributed': False,
'tenant_id': tenant_id,
'attached_ports': DotList()
b_subnet_id) = self._prepare_network_test(
tenant_id, ctx, region_name, index, az_hints=net_az_hints)
t_router_id = uuidutils.generate_uuid()
t_router = {
'id': t_router_id,
'name': 'top_router',
'distributed': False,
'tenant_id': tenant_id,
'attached_ports': DotList(),
'extra_attributes': {
'availability_zone_hints': router_az_hints
}
}
if create_new_router or len(TOP_ROUTERS) == 0:
TOP_ROUTERS.append(DotDict(t_router))
else:
t_router_id = TOP_ROUTERS[0]['id']
@ -2262,6 +2272,196 @@ class PluginTest(unittest.TestCase,
# each router interface adding will call add_gateway once
mock_action.assert_has_calls([call, call])
@patch.object(context, 'get_context_from_neutron_context')
def test_validation_router_net_location_match(self, mock_context):
self._basic_pod_route_setup()
pod4 = {'pod_id': 'pod_id_4',
'region_name': 'pod_4',
'az_name': 'az_name_2'}
db_api.create_pod(self.context, pod4)
fake_plugin = FakePlugin()
q_ctx = FakeNeutronContext()
t_ctx = context.get_db_context()
mock_context.return_value = t_ctx
tenant_id = TEST_TENANT_ID
router_az_hints = '["pod_1"]'
net_az_hints = '["pod_2"]'
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints, True)
router = fake_plugin._get_router(q_ctx, t_router_id)
net = fake_plugin.get_network(q_ctx, t_net_id)
self.assertRaises(t_exceptions.RouterNetworkLocationMismatch,
fake_plugin.validate_router_net_location_match,
t_ctx, router, net)
router_az_hints = '["pod_1"]'
net_az_hints = '["pod_1", "az_name_2"]'
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints, True)
router = fake_plugin._get_router(q_ctx, t_router_id)
net = fake_plugin.get_network(q_ctx, t_net_id)
self.assertRaises(t_exceptions.RouterNetworkLocationMismatch,
fake_plugin.validate_router_net_location_match,
t_ctx, router, net)
router_az_hints = '["az_name_1"]'
net_az_hints = '["az_name_1", "pod_2"]'
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints, True)
router = fake_plugin._get_router(q_ctx, t_router_id)
net = fake_plugin.get_network(q_ctx, t_net_id)
self.assertRaises(t_exceptions.RouterNetworkLocationMismatch,
fake_plugin.validate_router_net_location_match,
t_ctx, router, net)
router_az_hints = '["pod_1"]'
net_az_hints = None
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints, True)
router = fake_plugin._get_router(q_ctx, t_router_id)
net = fake_plugin.get_network(q_ctx, t_net_id)
self.assertRaises(t_exceptions.RouterNetworkLocationMismatch,
fake_plugin.validate_router_net_location_match,
t_ctx, router, net)
router_az_hints = None
net_az_hints = '["pod_1", "az_name_2"]'
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints, True)
router = fake_plugin._get_router(q_ctx, t_router_id)
net = fake_plugin.get_network(q_ctx, t_net_id)
is_local_router = helper.NetworkHelper.is_local_router(t_ctx, router)
fake_plugin.validate_router_net_location_match(t_ctx, router, net)
self.assertEqual(is_local_router, False)
router_az_hints = None
net_az_hints = None
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints, True)
router = fake_plugin._get_router(q_ctx, t_router_id)
net = fake_plugin.get_network(q_ctx, t_net_id)
is_local_router = helper.NetworkHelper.is_local_router(t_ctx, router)
fake_plugin.validate_router_net_location_match(t_ctx, router, net)
self.assertEqual(is_local_router, False)
router_az_hints = '["pod_1"]'
net_az_hints = '["pod_1"]'
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints, True)
router = fake_plugin._get_router(q_ctx, t_router_id)
net = fake_plugin.get_network(q_ctx, t_net_id)
is_local_router = helper.NetworkHelper.is_local_router(t_ctx, router)
fake_plugin.validate_router_net_location_match(t_ctx, router, net)
self.assertEqual(is_local_router, True)
router_az_hints = '["az_name_2"]'
net_az_hints = '["az_name_2"]'
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints, True)
router = fake_plugin._get_router(q_ctx, t_router_id)
net = fake_plugin.get_network(q_ctx, t_net_id)
is_local_router = helper.NetworkHelper.is_local_router(t_ctx, router)
fake_plugin.validate_router_net_location_match(t_ctx, router, net)
self.assertEqual(is_local_router, False)
router_az_hints = '["pod_1", "az_name_2"]'
net_az_hints = '["az_name_2"]'
t_ctx.is_admin = True
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints, True)
router = fake_plugin._get_router(q_ctx, t_router_id)
net = fake_plugin.get_network(q_ctx, t_net_id)
is_local_router = helper.NetworkHelper.is_local_router(t_ctx, router)
fake_plugin.validate_router_net_location_match(t_ctx, router, net)
self.assertEqual(is_local_router, False)
net_az_hints = '["pod_1"]'
t_ctx.is_admin = True
(t_net_id, t_subnet_id, b_net_id,
b_subnet_id) = self._prepare_network_test(
tenant_id, t_ctx, 'pod_1', 1, az_hints=net_az_hints)
# add a use case: router's extra_attributes attr is not exist but
# availability_zone_hints attr exist
t_router = {
'id': uuidutils.generate_uuid(),
'name': 'top_router',
'distributed': False,
'tenant_id': tenant_id,
'attached_ports': DotList(),
'availability_zone_hints': ['pod_1']
}
net = fake_plugin.get_network(q_ctx, t_net_id)
is_local_router = helper.NetworkHelper.is_local_router(t_ctx, t_router)
fake_plugin.validate_router_net_location_match(t_ctx, router, net)
self.assertEqual(is_local_router, True)
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@patch.object(ipam_pluggable_backend.IpamPluggableBackend,
'_allocate_ips_for_port', new=fake_allocate_ips_for_port)
@patch.object(db_base_plugin_common.DbBasePluginCommon,
'_make_subnet_dict', new=fake_make_subnet_dict)
@patch.object(FakePlugin, '_get_bridge_network_subnet')
@patch.object(FakeClient, 'add_gateway_routers')
@patch.object(FakeBaseRPCAPI, 'configure_extra_routes')
@patch.object(context, 'get_context_from_neutron_context')
def test_add_interface_for_local_router(
self, mock_context, mock_rpc, mock_action, mock_get_bridge_net):
self._basic_pod_route_setup()
fake_plugin = FakePlugin()
q_ctx = FakeNeutronContext()
t_ctx = context.get_db_context()
mock_context.return_value = t_ctx
tenant_id = TEST_TENANT_ID
router_az_hints = '["pod_1"]'
net_az_hints = '["pod_1"]'
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, net_az_hints)
fake_plugin.add_router_interface(
q_ctx, t_router_id, {'subnet_id': t_subnet_id})['port_id']
_, b_router_id = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_router_id, constants.RT_ROUTER)[0]
(t_net_id, t_subnet_id, t_router_id,
b_another_net_id, b_another_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 2, router_az_hints, net_az_hints)
fake_plugin.add_router_interface(
q_ctx, t_router_id, {'subnet_id': t_subnet_id})['port_id']
self.assertFalse(mock_rpc.called)
self.assertFalse(mock_action.called)
self.assertFalse(mock_get_bridge_net.called)
device_ids = ['', '']
for port in BOTTOM1_PORTS:
if (port['network_id'] == b_net_id) and (
port['device_owner'] == 'network:router_interface'):
device_ids[0] = port['device_id']
elif port['network_id'] == b_another_net_id and (
port['device_owner'] == 'network:router_interface'):
device_ids[1] = port['device_id']
self.assertEqual(device_ids, [b_router_id, b_router_id])
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@patch.object(ipam_pluggable_backend.IpamPluggableBackend,
@ -2451,23 +2651,8 @@ class PluginTest(unittest.TestCase,
t_ctx, top_net['id'], constants.RT_NETWORK)
self.assertEqual(mappings[0][1], bottom_net['id'])
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@patch.object(ipam_pluggable_backend.IpamPluggableBackend,
'_allocate_ips_for_port', new=fake_allocate_ips_for_port)
@patch.object(l3_db.L3_NAT_dbonly_mixin, '_make_router_dict',
new=fake_make_router_dict)
@patch.object(db_base_plugin_common.DbBasePluginCommon,
'_make_subnet_dict', new=fake_make_subnet_dict)
@patch.object(FakeClient, 'action_routers')
@patch.object(context, 'get_context_from_neutron_context')
def test_set_gateway(self, mock_context, mock_action):
self._basic_pod_route_setup()
fake_plugin = FakePlugin()
q_ctx = FakeNeutronContext()
t_ctx = context.get_db_context()
mock_context.return_value = t_ctx
def _prepare_external_net_router_test(self, q_ctx, fake_plugin,
router_az_hints=None):
tenant_id = TEST_TENANT_ID
t_net_body = {
@ -2502,10 +2687,36 @@ class PluginTest(unittest.TestCase,
'name': 'router',
'distributed': False,
'tenant_id': tenant_id,
'attached_ports': DotList()
'attached_ports': DotList(),
'extra_attributes': {
'availability_zone_hints': router_az_hints
}
}
TOP_ROUTERS.append(DotDict(t_router))
return t_net_id, t_subnet_id, t_router_id,
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@patch.object(ipam_pluggable_backend.IpamPluggableBackend,
'_allocate_ips_for_port', new=fake_allocate_ips_for_port)
@patch.object(l3_db.L3_NAT_dbonly_mixin, '_make_router_dict',
new=fake_make_router_dict)
@patch.object(db_base_plugin_common.DbBasePluginCommon,
'_make_subnet_dict', new=fake_make_subnet_dict)
@patch.object(FakeClient, 'action_routers')
@patch.object(context, 'get_context_from_neutron_context')
def test_set_gateway(self, mock_context, mock_action):
self._basic_pod_route_setup()
fake_plugin = FakePlugin()
q_ctx = FakeNeutronContext()
t_ctx = context.get_db_context()
mock_context.return_value = t_ctx
t_net_id, t_subnet_id, t_router_id = (
self._prepare_external_net_router_test(q_ctx, fake_plugin))
fake_plugin.update_router(
q_ctx, t_router_id,
{'router': {'external_gateway_info': {
@ -2534,6 +2745,54 @@ class PluginTest(unittest.TestCase,
{'subnet_id': b_bridge_subnet_id})]
mock_action.assert_has_calls(calls)
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@patch.object(ipam_pluggable_backend.IpamPluggableBackend,
'_allocate_ips_for_port', new=fake_allocate_ips_for_port)
@patch.object(l3_db.L3_NAT_dbonly_mixin, '_make_router_dict',
new=fake_make_router_dict)
@patch.object(db_base_plugin_common.DbBasePluginCommon,
'_make_subnet_dict', new=fake_make_subnet_dict)
@patch.object(FakePlugin, '_get_bridge_network_subnet')
@patch.object(FakeClient, 'action_routers')
@patch.object(context, 'get_context_from_neutron_context')
def test_set_gateway_for_local_router(self, mock_context, mock_action,
mock_get_bridge_network):
self._basic_pod_route_setup()
fake_plugin = FakePlugin()
q_ctx = FakeNeutronContext()
t_ctx = context.get_db_context()
mock_context.return_value = t_ctx
router_az_hints = '["pod_1"]'
t_net_id, t_subnet_id, t_router_id = (
self._prepare_external_net_router_test(q_ctx, fake_plugin,
router_az_hints))
fake_plugin.update_router(
q_ctx, t_router_id,
{'router': {'external_gateway_info': {
'network_id': TOP_NETS[0]['id'],
'enable_snat': False,
'external_fixed_ips':
[{'subnet_id': t_subnet_id,
'ip_address': '100.64.0.5'}]}}})
b_router_id = BOTTOM1_ROUTERS[0]['id']
b_net_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_net_id, 'pod_1', constants.RT_NETWORK)
b_subnet_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_subnet_id, 'pod_1', constants.RT_SUBNET)
body = {'network_id': b_net_id,
'enable_snat': False,
'external_fixed_ips': [{'subnet_id': b_subnet_id,
'ip_address': '100.64.0.5'}]}
mock_action.assert_called_once_with(t_ctx, 'add_gateway',
b_router_id, body)
self.assertFalse(mock_get_bridge_network.called)
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@patch.object(ipam_pluggable_backend.IpamPluggableBackend,
@ -2606,20 +2865,24 @@ class PluginTest(unittest.TestCase,
{'router': {'external_gateway_info': {}}})
mock_action.assert_called_with(t_ctx, 'remove_gateway', b_ns_router_id)
def _prepare_associate_floatingip_test(self, t_ctx, q_ctx, fake_plugin):
def _prepare_associate_floatingip_test(self, t_ctx, q_ctx, fake_plugin,
router_az_hints=None,
net_az_hints=None,
js_net_az_hints=None):
tenant_id = TEST_TENANT_ID
self._basic_pod_route_setup()
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_1', 1)
tenant_id, t_ctx, 'pod_1', 1, router_az_hints, js_net_az_hints)
if not net_az_hints:
net_az_hints = ['pod_2']
net_body = {
'name': 'ext_net',
'admin_state_up': True,
'shared': False,
'tenant_id': tenant_id,
'router:external': True,
'availability_zone_hints': ['pod_2']
'availability_zone_hints': net_az_hints
}
e_net = fake_plugin.create_network(q_ctx, {'network': net_body})
subnet_body = {
@ -2908,6 +3171,59 @@ class PluginTest(unittest.TestCase,
self.assertEqual(top_res_nums[i] - top_pre_created_res_nums[i],
len(top_res_set))
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@patch.object(ipam_pluggable_backend.IpamPluggableBackend,
'_allocate_ips_for_port', new=fake_allocate_ips_for_port)
@patch.object(l3_db.L3_NAT_dbonly_mixin, '_make_router_dict',
new=fake_make_router_dict)
@patch.object(db_base_plugin_common.DbBasePluginCommon,
'_make_subnet_dict', new=fake_make_subnet_dict)
@patch.object(l3_db.L3_NAT_dbonly_mixin, 'update_floatingip',
new=update_floatingip)
@patch.object(context, 'get_context_from_neutron_context')
def test_delete_local_router(self, mock_context):
fake_plugin = FakePlugin()
q_ctx = FakeNeutronContext()
t_ctx = context.get_db_context()
t_ctx.project_id = 'test_tenant_id'
mock_context.return_value = t_ctx
(t_port_id, b_port_id,
fip, e_net) = self._prepare_associate_floatingip_test(
t_ctx, q_ctx, fake_plugin, '["pod_1"]', ["pod_1"], '["pod_1"]')
# associate floating ip
fip_body = {'port_id': t_port_id}
fake_plugin.update_floatingip(q_ctx, fip['id'],
{'floatingip': fip_body})
# disassociate floating ip
fip_body = {'port_id': None}
fake_plugin.update_floatingip(q_ctx, fip['id'],
{'floatingip': fip_body})
t_router_id = TOP_ROUTERS[0]['id']
for port in TOP_PORTS:
if port['id'] == t_port_id:
t_subnet_id = port['fixed_ips'][0]['subnet_id']
fake_plugin.remove_router_interface(q_ctx, t_router_id,
{'subnet_id': t_subnet_id})
fake_plugin.update_router(q_ctx, t_router_id,
{'router': {'external_gateway_info': {}}})
top_res_sets = [TOP_NETS, TOP_SUBNETS, TOP_PORTS]
top_res_nums = [len(top_res_set) for top_res_set in top_res_sets]
top_pre_created_res_nums = [0, 0, 0]
for i, top_res_set in enumerate(top_res_sets):
for top_res in top_res_set:
if top_res.get('name', '').find('bridge') != -1:
top_pre_created_res_nums[i] += 1
fake_plugin.delete_router(q_ctx, t_router_id)
# check pre-created networks, subnets and ports are all deleted
for i, top_res_set in enumerate(top_res_sets):
self.assertEqual(top_res_nums[i] - top_pre_created_res_nums[i],
len(top_res_set))
@patch.object(context, 'get_context_from_neutron_context')
def test_create_security_group_rule(self, mock_context):
self._basic_pod_route_setup()

View File

@ -311,30 +311,33 @@ class XManager(PeriodicTasks):
_, b_router_id = self.helper.prepare_bottom_element(
ctx, project_id, b_pod, t_router, constants.RT_ROUTER, router_body)
# create top bridge port
q_ctx = None # no need to pass neutron context when using client
t_bridge_port_id = self.helper.get_bridge_interface(
ctx, q_ctx, project_id, t_pod, t_bridge_net['id'], b_router_id)
is_local_router = self.helper.is_local_router(ctx, t_router)
if not is_local_router:
# create top bridge port
t_bridge_port_id = self.helper.get_bridge_interface(
ctx, q_ctx, project_id, t_pod, t_bridge_net['id'], b_router_id)
# create bottom bridge port
# if target bottom pod is hosting real external network, we create
# another bottom router and attach the bridge network as internal
# network, but this work is done by central plugin when user sets
# router gateway.
t_bridge_port = t_client.get_ports(ctx, t_bridge_port_id)
(is_new, b_bridge_port_id, b_bridge_subnet_id,
b_bridge_net_id) = self.helper.get_bottom_bridge_elements(
ctx, project_id, b_pod, t_bridge_net, True, t_bridge_subnet, None)
# create bottom bridge port
# if target bottom pod is hosting real external network, we create
# another bottom router and attach the bridge network as internal
# network, but this work is done by central plugin when user sets
# router gateway.
t_bridge_port = t_client.get_ports(ctx, t_bridge_port_id)
(is_new, b_bridge_port_id, b_bridge_subnet_id,
b_bridge_net_id) = self.helper.get_bottom_bridge_elements(
ctx, project_id, b_pod, t_bridge_net,
True, t_bridge_subnet, None)
# we attach the bridge port as router gateway
# add_gateway is update operation, which can run multiple times
gateway_ip = t_bridge_port['fixed_ips'][0]['ip_address']
b_client.action_routers(
ctx, 'add_gateway', b_router_id,
{'network_id': b_bridge_net_id,
'enable_snat': False,
'external_fixed_ips': [{'subnet_id': b_bridge_subnet_id,
'ip_address': gateway_ip}]})
# we attach the bridge port as router gateway
# add_gateway is update operation, which can run multiple times
gateway_ip = t_bridge_port['fixed_ips'][0]['ip_address']
b_client.action_routers(
ctx, 'add_gateway', b_router_id,
{'network_id': b_bridge_net_id,
'enable_snat': False,
'external_fixed_ips': [{'subnet_id': b_bridge_subnet_id,
'ip_address': gateway_ip}]})
# attach internal port to bottom router
t_ports = self._get_router_interfaces(t_client, ctx, t_router['id'],
@ -521,13 +524,17 @@ class XManager(PeriodicTasks):
project_id = t_router['tenant_id']
b_pod = db_api.get_pod(ctx, b_pod_id)
t_bridge_net_name = constants.bridge_net_name % project_id
t_bridge_subnet_name = constants.bridge_subnet_name % project_id
t_bridge_net = self._get_resource_by_name(t_client, ctx, 'network',
t_bridge_net_name)
t_bridge_subnet = self._get_resource_by_name(
t_client, ctx, 'subnet', t_bridge_subnet_name)
is_local_router = self.helper.is_local_router(ctx, t_router)
if is_local_router:
t_bridge_net = None
t_bridge_subnet = None
else:
t_bridge_net_name = constants.bridge_net_name % project_id
t_bridge_subnet_name = constants.bridge_subnet_name % project_id
t_bridge_net = self._get_resource_by_name(t_client, ctx, 'network',
t_bridge_net_name)
t_bridge_subnet = self._get_resource_by_name(
t_client, ctx, 'subnet', t_bridge_subnet_name)
ext_nets = t_client.list_networks(ctx,
filters=[{'key': 'router:external',
@ -542,11 +549,12 @@ class XManager(PeriodicTasks):
is_ext_net_pod = True
else:
is_ext_net_pod = False
self._setup_router_one_pod(ctx, t_pod, b_pod, t_client, t_net,
t_router, t_bridge_net,
t_bridge_subnet, is_ext_net_pod)
self.xjob_handler.configure_extra_routes(ctx, t_router_id)
self._setup_router_one_pod(
ctx, t_pod, b_pod, t_client, t_net, t_router, t_bridge_net,
t_bridge_subnet, is_ext_net_pod)
if not is_local_router:
self.xjob_handler.configure_extra_routes(ctx, t_router_id)
@_job_handle(constants.JT_ROUTER)
def configure_extra_routes(self, ctx, payload):