Browse Source

Merge "Combine bridge networks"

changes/62/411762/12
Jenkins 5 years ago
committed by Gerrit Code Review
parent
commit
25a852d100
  1. 1
      devstack/local.conf.node_2.sample
  2. 4
      releasenotes/notes/combine-bridge-network-c137a03f067c49a7.yaml
  3. 7
      tricircle/common/client.py
  4. 20
      tricircle/common/constants.py
  5. 6
      tricircle/common/lock_handle.py
  6. 4
      tricircle/db/models.py
  7. 262
      tricircle/network/central_plugin.py
  8. 35
      tricircle/network/helper.py
  9. 60
      tricircle/network/local_l3_plugin.py
  10. 9
      tricircle/network/local_plugin.py
  11. 331
      tricircle/tests/unit/network/test_central_plugin.py
  12. 209
      tricircle/tests/unit/xjob/test_xmanager.py
  13. 364
      tricircle/xjob/xmanager.py

1
devstack/local.conf.node_2.sample

@ -70,6 +70,7 @@ TRICIRCLE_NEUTRON_PORT=20001
[DEFAULT]
core_plugin=tricircle.network.local_plugin.TricirclePlugin
service_plugins=tricircle.network.local_l3_plugin.TricircleL3Plugin
[client]
admin_username=admin

4
releasenotes/notes/combine-bridge-network-c137a03f067c49a7.yaml

@ -0,0 +1,4 @@
---
features:
- North-south bridge network and east-west bridge network are combined into
one to bring better DVR and shared VxLAN network support.

7
tricircle/common/client.py

@ -64,12 +64,9 @@ client_opts = [
default='Default',
help='tenant domain name of admin account, needed when'
' auto_refresh_endpoint set to True'),
cfg.StrOpt('ew_bridge_cidr',
cfg.StrOpt('bridge_cidr',
default='100.0.0.0/9',
help='cidr pool of the east-west bridge network'),
cfg.StrOpt('ns_bridge_cidr',
default='100.128.0.0/9',
help='cidr pool of the north-south bridge network')
help='cidr pool of the bridge network')
]
client_opt_group = cfg.OptGroup('client')
cfg.CONF.register_group(client_opt_group)

20
tricircle/common/constants.py

@ -30,9 +30,13 @@ RT_VOl_METADATA = 'volume_metadata'
RT_BACKUP = 'backup'
RT_SNAPSHOT = 'snapshot'
RT_NETWORK = 'network'
RT_SD_NETWORK = 'shadow_network'
RT_SUBNET = 'subnet'
RT_SD_SUBNET = 'shadow_subnet'
RT_PORT = 'port'
RT_SD_PORT = 'shadow_port'
RT_ROUTER = 'router'
RT_NS_ROUTER = 'ns_router'
RT_SG = 'security_group'
@ -52,23 +56,23 @@ R_LIBERTY = 'liberty'
R_MITAKA = 'mitaka'
# l3 bridge networking elements
ew_bridge_subnet_pool_name = 'ew_bridge_subnet_pool'
ew_bridge_net_name = 'ew_bridge_net_%s' # project_id
ew_bridge_subnet_name = 'ew_bridge_subnet_%s' # project_id
ew_bridge_port_name = 'ew_bridge_port_%s_%s' # project_id b_router_id
ns_bridge_subnet_pool_name = 'ns_bridge_subnet_pool'
ns_bridge_net_name = 'ns_bridge_net_%s' # project_id
ns_bridge_subnet_name = 'ns_bridge_subnet_%s' # project_id
bridge_subnet_pool_name = 'bridge_subnet_pool'
bridge_net_name = 'bridge_net_%s' # project_id
bridge_subnet_name = 'bridge_subnet_%s' # project_id
bridge_port_name = 'bridge_port_%s_%s' # project_id b_router_id
# for external gateway port: project_id b_router_id None
# for floating ip port: project_id None b_internal_port_id
ns_bridge_port_name = 'ns_bridge_port_%s_%s_%s'
ns_router_name = 'ns_router_%s'
shadow_port_name = 'shadow_port_%s'
dhcp_port_name = 'dhcp_port_%s' # subnet_id
interface_port_name = 'interface_%s_%s' # b_pod_id t_subnet_id
interface_port_device_id = 'reserved_gateway_port'
MAX_INT = 0x7FFFFFFF
DEFAULT_DESTINATION = '0.0.0.0/0'
expire_time = datetime.datetime(2000, 1, 1)
# job status

6
tricircle/common/lock_handle.py

@ -40,7 +40,9 @@ def get_or_create_route(t_ctx, q_ctx,
t_ctx, models.ResourceRouting,
[{'key': 'top_id', 'comparator': 'eq', 'value': _id},
{'key': 'pod_id', 'comparator': 'eq',
'value': pod['pod_id']}], [])
'value': pod['pod_id']},
{'key': 'resource_type', 'comparator': 'eq',
'value': _type}], [])
if routes:
route = routes[0]
if route['bottom_id']:
@ -109,7 +111,7 @@ def get_or_create_element(t_ctx, q_ctx,
core.delete_resource(t_ctx,
models.ResourceRouting,
route['id'])
except db_exc.ResourceNotFound:
except db_exc.ColumnError:
# NOTE(zhiyuan) this is a rare case that other worker
# considers the route expires and delete it though it
# was just created, maybe caused by out-of-sync time

4
tricircle/db/models.py

@ -60,8 +60,8 @@ class ResourceRouting(core.ModelBase, core.DictBase, models.TimestampMixin):
__tablename__ = 'resource_routings'
__table_args__ = (
schema.UniqueConstraint(
'top_id', 'pod_id',
name='resource_routings0top_id0pod_id'),
'top_id', 'pod_id', 'resource_type',
name='resource_routings0top_id0pod_id0resource_type'),
)
attributes = ['id', 'top_id', 'bottom_id', 'pod_id', 'project_id',
'resource_type', 'created_at', 'updated_at']

262
tricircle/network/central_plugin.py

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import copy
from oslo_config import cfg
@ -274,11 +275,22 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
try:
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, network_id, t_constants.RT_NETWORK)
mappings.extend(db_api.get_bottom_mappings_by_top_id(
t_ctx, network_id, t_constants.RT_SD_NETWORK))
processed_pod_set = set()
for mapping in mappings:
region_name = mapping[0]['region_name']
if region_name in processed_pod_set:
continue
processed_pod_set.add(region_name)
bottom_network_id = mapping[1]
self._get_client(region_name).delete_networks(
t_ctx, bottom_network_id)
# we do not specify resource_type when deleting routing entries
# so if both "network" and "shadow_network" type entries exist
# in one pod(this is possible for cross-pod network), we delete
# them at the same time
with t_ctx.session.begin():
core.delete_resources(
t_ctx, models.ResourceRouting,
@ -386,14 +398,25 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
try:
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, subnet_id, t_constants.RT_SUBNET)
mappings.extend(db_api.get_bottom_mappings_by_top_id(
t_ctx, subnet_id, t_constants.RT_SD_SUBNET))
processed_pod_set = set()
for mapping in mappings:
region_name = mapping[0]['region_name']
if region_name in processed_pod_set:
continue
processed_pod_set.add(region_name)
bottom_subnet_id = mapping[1]
self._get_client(region_name).delete_subnets(
t_ctx, bottom_subnet_id)
interface_name = t_constants.interface_port_name % (
mapping[0]['region_name'], subnet_id)
self._delete_pre_created_port(t_ctx, context, interface_name)
# we do not specify resource_type when deleting routing entries
# so if both "subnet" and "shadow_subnet" type entries exist in
# one pod(this is possible for cross-pod network), we delete
# them at the same time
with t_ctx.session.begin():
core.delete_resources(
t_ctx, models.ResourceRouting,
@ -473,6 +496,9 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
self.xjob_handler.setup_bottom_router(
admin_context, res['network_id'],
interfaces[0]['device_id'], pod['pod_id'])
else:
LOG.debug('Update port: no interfaces found, xjob not'
'triggered')
self.xjob_handler.configure_security_group_rules(t_ctx,
res['tenant_id'])
@ -661,10 +687,18 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
id_list.append(_id)
return id_list
@staticmethod
def _filter_shadow_port(ports, pod_id, port_pod_map):
port_list = []
for port in ports:
if pod_id not in port_pod_map[port['id']]:
port_list.append(port)
return port_list
def _get_ports_from_pod_with_number(self, context,
current_pod, number, last_port_id,
bottom_top_map, top_bottom_map,
filters=None):
port_pod_map, filters=None):
# NOTE(zhiyuan) last_port_id is top id, also id in returned port dict
# also uses top id. when interacting with bottom pod, need to map
# top to bottom in request and map bottom to top in response
@ -678,7 +712,8 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
for key, value in _filters:
if key == 'fixed_ips':
if 'ip_address' in value:
_filters[key] = 'ip_address=%s' % value['ip_address']
_filters[key] = 'ip_address=%s' % value[
'ip_address'][0]
continue
id_list = self._get_map_filter_ids(
key, value, current_pod['pod_id'], top_bottom_map)
@ -689,8 +724,10 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
# map top id to bottom id in request
params['marker'] = top_bottom_map[last_port_id]
res = q_client.get(q_client.ports_path, params=params)
ports = self._filter_shadow_port(res['ports'], current_pod['pod_id'],
port_pod_map)
# map bottom id to top id in client response
mapped_port_list = self._map_ports_from_bottom_to_top(res['ports'],
mapped_port_list = self._map_ports_from_bottom_to_top(ports,
bottom_top_map)
del res['ports']
res['ports'] = mapped_port_list
@ -712,7 +749,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
# need to map
next_res = self._get_ports_from_pod_with_number(
context, next_pod, number - len(res['ports']), '',
bottom_top_map, top_bottom_map, filters)
bottom_top_map, top_bottom_map, port_pod_map, filters)
next_res['ports'].extend(res['ports'])
return next_res
@ -780,6 +817,16 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
key = '%s_%s' % (route['pod_id'], route['top_id'])
top_bottom_map[key] = route['bottom_id']
port_pod_map = collections.defaultdict(set)
route_filters = [{'key': 'resource_type',
'comparator': 'eq',
'value': t_constants.RT_SD_PORT}]
routes = core.query_resource(t_ctx, models.ResourceRouting,
route_filters, [])
for route in routes:
if route['bottom_id']:
port_pod_map[route['bottom_id']].add(route['pod_id'])
if limit:
if marker:
mappings = db_api.get_bottom_mappings_by_top_id(
@ -791,7 +838,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
current_pod = db_api.get_pod(t_ctx, pod_id)
res = self._get_ports_from_pod_with_number(
context, current_pod, limit, marker,
bottom_top_map, top_bottom_map, filters)
bottom_top_map, top_bottom_map, port_pod_map, filters)
else:
res = self._get_ports_from_top_with_number(
context, limit, marker, top_bottom_map, filters)
@ -802,7 +849,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
if current_pod:
res = self._get_ports_from_pod_with_number(
context, current_pod, limit, '',
bottom_top_map, top_bottom_map, filters)
bottom_top_map, top_bottom_map, port_pod_map, filters)
else:
res = self._get_ports_from_top_with_number(
context, limit, marker, top_bottom_map, filters)
@ -829,7 +876,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
_filters.append(
{'key': key, 'comparator': 'eq',
'value': 'ip_address=%s' % value[
'ip_address']})
'ip_address'][0]})
continue
id_list = self._get_map_filter_ids(
key, value, pod['pod_id'], top_bottom_map)
@ -842,7 +889,9 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
'comparator': 'eq',
'value': value})
client = self._get_client(pod['region_name'])
ret.extend(client.list_ports(t_ctx, filters=_filters))
ports = client.list_ports(t_ctx, filters=_filters)
ret.extend(self._filter_shadow_port(ports, pod['pod_id'],
port_pod_map))
ret = self._map_ports_from_bottom_to_top(ret, bottom_top_map)
ret.extend(self._get_ports_from_top(context, top_bottom_map,
filters))
@ -854,6 +903,11 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
def _delete_top_bridge_resource(self, t_ctx, q_ctx, resource_type,
resource_id, resource_name):
# first we update the routing entry to clear bottom_id and expire the
# entry, if we succeed to delete the bridge resource next, we continue
# to delete this expired entry; otherwise, we fail to delete the bridge
# resource, then when the resource is accessed via lock_handle module,
# that module will find the resource and update the entry
with t_ctx.session.begin():
core.update_resources(
t_ctx, models.ResourceRouting,
@ -877,26 +931,20 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
def _delete_top_bridge_network_subnet(self, t_ctx, q_ctx):
project_id = t_ctx.project_id
ew_bridge_subnet_name = t_constants.ew_bridge_subnet_name % project_id
ns_bridge_subnet_name = t_constants.ns_bridge_subnet_name % project_id
subnet_names = [ew_bridge_subnet_name, ns_bridge_subnet_name]
for subnet_name in subnet_names:
bridge_subnets = super(TricirclePlugin, self).get_subnets(
q_ctx, {'name': [subnet_name]})
if bridge_subnets:
self._delete_top_bridge_resource(
t_ctx, q_ctx, t_constants.RT_SUBNET,
bridge_subnets[0]['id'], subnet_name)
ew_bridge_net_name = t_constants.ew_bridge_net_name % project_id
ns_bridge_net_name = t_constants.ns_bridge_net_name % project_id
net_names = [ew_bridge_net_name, ns_bridge_net_name]
for net_name in net_names:
bridge_nets = super(TricirclePlugin, self).get_networks(
q_ctx, {'name': [net_name]})
if bridge_nets:
self._delete_top_bridge_resource(
t_ctx, q_ctx, t_constants.RT_NETWORK, bridge_nets[0]['id'],
net_name)
bridge_subnet_name = t_constants.bridge_subnet_name % project_id
bridge_subnets = super(TricirclePlugin, self).get_subnets(
q_ctx, {'name': [bridge_subnet_name]})
if bridge_subnets:
self._delete_top_bridge_resource(
t_ctx, q_ctx, t_constants.RT_SUBNET,
bridge_subnets[0]['id'], bridge_subnet_name)
bridge_net_name = t_constants.bridge_net_name % project_id
bridge_nets = super(TricirclePlugin, self).get_networks(
q_ctx, {'name': [bridge_net_name]})
if bridge_nets:
self._delete_top_bridge_resource(
t_ctx, q_ctx, t_constants.RT_NETWORK, bridge_nets[0]['id'],
bridge_net_name)
def _delete_top_bridge_port(self, t_ctx, q_ctx, bridge_port_id,
bridge_port_name):
@ -912,65 +960,46 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
t_constants.RT_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)
b_client.delete_routers(t_ctx, b_router_id)
db_api.delete_mappings_by_bottom_id(t_ctx, b_router_id)
ew_port_name = t_constants.ew_bridge_port_name % (project_id,
b_router_id)
ew_ports = super(TricirclePlugin, self).get_ports(
context, {'name': [ew_port_name]})
if ew_ports:
t_ew_port_id = ew_ports[0]['id']
b_ew_port_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_ew_port_id, pod['region_name'],
t_constants.RT_PORT)
if b_ew_port_id:
request_body = {'port_id': b_ew_port_id}
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_subnets = super(TricirclePlugin,
self).get_subnets(
context, {'name': [bridge_subnet_name]})
if bridge_subnets:
t_bridge_subnet_id = bridge_subnets[0]['id']
b_bridge_subnet_id = \
db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_bridge_subnet_id, pod['region_name'],
t_constants.RT_SUBNET)
if b_bridge_subnet_id:
request_body = {'subnet_id': b_bridge_subnet_id}
try:
b_client.action_routers(t_ctx, 'remove_interface',
b_router_id, request_body)
b_ns_router_id, request_body)
except Exception as e:
if e.status_code == 404:
# 404 error means that the router interface has
# been already detached, skip this exception
pass
raise
db_api.delete_mappings_by_top_id(t_ctx, t_ew_port_id)
self._delete_top_bridge_port(t_ctx, context, t_ew_port_id,
ew_port_name)
ns_port_name = t_constants.ns_bridge_port_name % (
project_id, b_router_id, None)
ns_ports = super(TricirclePlugin, self).get_ports(
context, {'name': [ns_port_name]})
if ns_ports:
t_ns_port_id = ns_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,
ns_port_name)
else:
ns_subnet_name = t_constants.ns_bridge_subnet_name % project_id
ns_subnets = super(TricirclePlugin,
self).get_subnets(
context, {'name': [ns_subnet_name]})
if ns_subnets:
t_ns_subnet_id = ns_subnets[0]['id']
b_ns_subnet_id = \
db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_ns_subnet_id, pod['region_name'],
t_constants.RT_SUBNET)
if b_ns_subnet_id:
request_body = {'subnet_id': b_ns_subnet_id}
try:
b_client.action_routers(t_ctx, 'remove_interface',
b_router_id, request_body)
except Exception as e:
if e.status_code == 404:
# 404 error means that the router interface has
# been already detached, skip this exception
pass
raise
b_client.delete_routers(t_ctx, b_router_id)
db_api.delete_mappings_by_bottom_id(t_ctx, b_router_id)
b_client.delete_routers(t_ctx, b_ns_router_id)
db_api.delete_mappings_by_bottom_id(t_ctx, b_ns_router_id)
routers = super(TricirclePlugin, self).get_routers(
context, {'tenant_id': [project_id]})
@ -989,13 +1018,9 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
return self.helper.prepare_bottom_element(
t_ctx, project_id, pod, ele, _type, body)
def _get_bridge_subnet_pool_id(self, t_ctx, q_ctx, project_id, pod, is_ew):
if is_ew:
pool_name = t_constants.ew_bridge_subnet_pool_name
pool_cidr = cfg.CONF.client.ew_bridge_cidr
else:
pool_name = t_constants.ns_bridge_subnet_pool_name
pool_cidr = cfg.CONF.client.ns_bridge_cidr
def _get_bridge_subnet_pool_id(self, t_ctx, q_ctx, project_id, pod):
pool_name = t_constants.bridge_subnet_pool_name
pool_cidr = cfg.CONF.client.bridge_cidr
pool_ele = {'id': pool_name}
body = {'subnetpool': {'tenant_id': project_id,
'name': pool_name,
@ -1012,17 +1037,11 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
return pool_id
def _get_bridge_network_subnet(self, t_ctx, q_ctx, project_id, pod,
pool_id, is_ew):
if is_ew:
net_name = t_constants.ew_bridge_net_name % project_id
net_ele = {'id': net_name}
subnet_name = t_constants.ew_bridge_subnet_name % project_id
subnet_ele = {'id': subnet_name}
else:
net_name = t_constants.ns_bridge_net_name % project_id
net_ele = {'id': net_name}
subnet_name = t_constants.ns_bridge_subnet_name % project_id
subnet_ele = {'id': subnet_name}
pool_id):
net_name = t_constants.bridge_net_name % project_id
net_ele = {'id': net_name}
subnet_name = t_constants.bridge_subnet_name % project_id
subnet_ele = {'id': subnet_name}
is_admin = q_ctx.is_admin
q_ctx.is_admin = True
@ -1063,10 +1082,9 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
return net, subnet
def _get_bridge_interface(self, t_ctx, q_ctx, project_id, pod,
t_net_id, b_router_id, b_port_id, is_ew):
t_net_id, b_router_id):
port_id = self.helper.get_bridge_interface(t_ctx, q_ctx, project_id,
pod, t_net_id, b_router_id,
b_port_id, is_ew)
pod, t_net_id, b_router_id)
return super(TricirclePlugin, self).get_port(q_ctx, port_id)
def _get_bottom_bridge_elements(self, q_ctx, project_id,
@ -1125,11 +1143,11 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
# 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': router_id,
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_ROUTER, body)
t_constants.RT_NS_ROUTER, body)
# both router and external network in bottom pod are ready, attach
# external network to router in bottom pod.
@ -1156,13 +1174,12 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
# bridge network.
t_pod = db_api.get_top_pod(t_ctx)
project_id = t_router['tenant_id']
pool_id = self._get_bridge_subnet_pool_id(
t_ctx, context, None, t_pod, False)
pool_id = self._get_bridge_subnet_pool_id(t_ctx, context, None, t_pod)
t_bridge_net, t_bridge_subnet = self._get_bridge_network_subnet(
t_ctx, context, project_id, t_pod, pool_id, False)
t_ctx, context, project_id, t_pod, pool_id)
(_, _, b_bridge_subnet_id,
b_bridge_net_id) = self._get_bottom_bridge_elements(
context, project_id, pod, t_bridge_net, False, t_bridge_subnet,
context, project_id, pod, t_bridge_net, True, t_bridge_subnet,
None)
# here we attach the bridge network to the router in bottom pod. to
@ -1181,6 +1198,8 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
is_attach = _is_bridge_network_attached()
if not is_attach:
# no need to explicitly create the top bridge port, the ip reserved
# for router interface will be used.
b_client.action_routers(t_ctx, 'add_interface', b_router_id,
{'subnet_id': b_bridge_subnet_id})
@ -1199,7 +1218,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
region_name = t_network[az_ext.AZ_HINTS][0]
b_router_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, router_id, region_name, t_constants.RT_ROUTER)
t_ctx, router_id, region_name, t_constants.RT_NS_ROUTER)
b_client = self._get_client(region_name)
b_client.action_routers(t_ctx, 'remove_gateway', b_router_id)
@ -1233,12 +1252,15 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
context, router_id, router)
router_data[l3.EXTERNAL_GW_INFO].update(ret[l3.EXTERNAL_GW_INFO])
self._add_router_gateway(context, router_id, router_data)
return ret
else:
self._remove_router_gateway(context, router_id)
return super(TricirclePlugin, self).update_router(
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)
return ret
def add_router_interface(self, context, router_id, interface_info):
t_ctx = t_context.get_context_from_neutron_context(context)
@ -1251,33 +1273,17 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
t_pod = db_api.get_top_pod(t_ctx)
assert t_pod
# bridge network for E-W networking
# bridge network for E-W and N-S networking
pool_id = self._get_bridge_subnet_pool_id(
t_ctx, context, None, t_pod, True)
t_ctx, context, None, t_pod)
self._get_bridge_network_subnet(
t_ctx, context, project_id, t_pod, pool_id, True)
# bridge network for N-S networking
ext_nets = self.get_networks(context, {external_net.EXTERNAL: [True]})
if not ext_nets:
need_ns_bridge = False
else:
ext_net_region_names = set(
[ext_net[az_ext.AZ_HINTS][0] for ext_net in ext_nets])
need_ns_bridge = False
for b_pod in b_pods:
if b_pod['region_name'] not in ext_net_region_names:
need_ns_bridge = True
break
if need_ns_bridge:
pool_id = self._get_bridge_subnet_pool_id(
t_ctx, context, None, t_pod, False)
self._get_bridge_network_subnet(
t_ctx, context, project_id, t_pod, pool_id, False)
t_ctx, context, project_id, t_pod, pool_id)
return_info = super(TricirclePlugin, self).add_router_interface(
context, router_id, interface_info)
if not b_pods:
LOG.debug('Add router interface: no interfaces found, xjob not'
'triggered')
return return_info
try:
if len(b_pods) == 1:

35
tricircle/network/helper.py

@ -117,7 +117,7 @@ class NetworkHelper(object):
t_ctx, q_ctx, project_id, pod, ele, _type, body)
def get_bridge_interface(self, t_ctx, q_ctx, project_id, pod,
t_net_id, b_router_id, b_port_id, is_ew):
t_net_id, b_router_id):
"""Get or create top bridge interface
:param t_ctx: tricircle context
@ -126,19 +126,10 @@ class NetworkHelper(object):
:param pod: dict of top pod
:param t_net_id: top bridge network id
:param b_router_id: bottom router id
:param b_port_id: needed when creating bridge interface for south-
north network, id of the internal port bound to floating ip
:param is_ew: create the bridge interface for east-west network or
south-north network
:return: bridge interface id
"""
if is_ew:
port_name = t_constants.ew_bridge_port_name % (project_id,
b_router_id)
else:
port_name = t_constants.ns_bridge_port_name % (project_id,
b_router_id,
b_port_id)
port_name = t_constants.bridge_port_name % (project_id,
b_router_id)
port_ele = {'id': port_name}
port_body = {
'port': {
@ -174,7 +165,13 @@ class NetworkHelper(object):
"""
def list_resources(t_ctx_, q_ctx, pod_, ele_, _type_):
client = self._get_client(pod_['region_name'])
if _type_ == t_constants.RT_NETWORK:
if _type_ == t_constants.RT_NS_ROUTER:
_type_ = t_constants.RT_ROUTER
value = t_constants.ns_router_name % ele_['id']
elif _type_ == t_constants.RT_SD_PORT:
_type_ = t_constants.RT_PORT
value = t_constants.shadow_port_name % ele_['id']
elif _type_ == t_constants.RT_NETWORK:
value = utils.get_bottom_network_name(ele_)
else:
value = ele_['id']
@ -183,6 +180,10 @@ class NetworkHelper(object):
'value': value}])
def create_resources(t_ctx_, q_ctx, pod_, body_, _type_):
if _type_ == t_constants.RT_NS_ROUTER:
_type_ = t_constants.RT_ROUTER
elif _type_ == t_constants.RT_SD_PORT:
_type_ = t_constants.RT_PORT
client = self._get_client(pod_['region_name'])
return client.create_resources(_type_, t_ctx_, body_)
@ -421,14 +422,6 @@ class NetworkHelper(object):
'cidr': t_subnet['cidr'],
'enable_dhcp': False,
'tenant_id': project_id}}
# In the pod hosting external network, where ns bridge network is used
# as an internal network, need to allocate ip address from .3 because
# .2 is used by the router gateway port in the pod hosting servers,
# where ns bridge network is used as an external network.
# if t_subnet['name'].startswith('ns_bridge_') and not is_external:
# prefix = t_subnet['cidr'][:t_subnet['cidr'].rindex('.')]
# subnet_body['subnet']['allocation_pools'] = [
# {'start': prefix + '.3', 'end': prefix + '.254'}]
_, b_subnet_id = self.prepare_bottom_element(
t_ctx, project_id, pod, t_subnet, 'subnet', subnet_body)

60
tricircle/network/local_l3_plugin.py

@ -0,0 +1,60 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from sqlalchemy import orm
from neutron_lib import constants
from neutron.db.models import l3 as l3_models
from neutron.db import models_v2
from neutron.extensions import l3
from neutron.services.l3_router import l3_router_plugin
class TricircleL3Plugin(l3_router_plugin.L3RouterPlugin):
# Override the original implementation to allow associating a floating ip
# to a port whose network is not attached to the router. Tricircle will
# configures extra routes to guarantee packets can reach the port.
def get_router_for_floatingip(self, context, internal_port,
internal_subnet, external_network_id):
"""Find a router to handle the floating-ip association.
:param internal_port: The port for the fixed-ip.
:param internal_subnet: The subnet for the fixed-ip.
:param external_network_id: The external network for floating-ip.
:raises: ExternalGatewayForFloatingIPNotFound if no suitable router
is found.
"""
router_port = l3_models.RouterPort
gw_port = orm.aliased(models_v2.Port, name="gw_port")
router_port_qry = context.session.query(
router_port.router_id
).join(gw_port, gw_port.device_id == router_port.router_id).filter(
gw_port.network_id == external_network_id,
gw_port.device_owner == constants.DEVICE_OWNER_ROUTER_GW
).distinct()
first_router_id = None
for router in router_port_qry:
if not first_router_id:
first_router_id = router.router_id
if first_router_id:
return first_router_id
raise l3.ExternalGatewayForFloatingIPNotFound(
subnet_id=internal_subnet['id'],
external_network_id=external_network_id,
port_id=internal_port['id'])

9
tricircle/network/local_plugin.py

@ -480,10 +480,15 @@ class TricirclePlugin(plugin.Ml2Plugin):
t_ctx = t_context.get_context_from_neutron_context(context)
try:
b_port = self.core_plugin.get_port(context, _id)
# to support floating ip, we create a copy port if the target port
# is not in the pod where the real external network is located. to
# distinguish it from normal port, we name it with a prefix
do_top_delete = b_port['device_owner'].startswith(
q_constants.DEVICE_OWNER_COMPUTE_PREFIX)
skip_top_delete = t_constants.RT_SD_PORT in b_port['name']
except q_exceptions.NotFound:
return
if b_port['device_owner'].startswith(
q_constants.DEVICE_OWNER_COMPUTE_PREFIX):
if do_top_delete and not skip_top_delete:
self.neutron_handle.handle_delete(t_ctx, t_constants.RT_PORT, _id)
self.core_plugin.delete_port(context, _id, l3_port_check)

331
tricircle/tests/unit/network/test_central_plugin.py

@ -294,7 +294,7 @@ class FakeClient(object):
'security_group': BOTTOM2_SGS,
'floatingip': BOTTOM2_FIPS}}
def __init__(self, region_name):
def __init__(self, region_name=None):
if not region_name:
self.region_name = 'top'
else:
@ -336,6 +336,12 @@ class FakeClient(object):
if ip in ip_range:
fixed_ip['subnet_id'] = subnet['id']
break
if 'subnet_id' not in fixed_ip:
# we still cannot find the proper subnet, that's because
# this is a copy port. local plugin will create the missing
# subnet for this port but FakeClient won't. we just skip
# the ip address check
continue
if fixed_ip['ip_address'] in subnet_ips_map.get(
fixed_ip['subnet_id'], set()):
raise q_exceptions.IpAddressInUseClient()
@ -1118,6 +1124,11 @@ class PluginTest(unittest.TestCase,
DotDict({'physical_network': phynet,
'vlan_id': vlan, 'allocated': False}))
def fake_get_plugin(alias=q_constants.CORE):
return FakePlugin()
from neutron_lib.plugins import directory
directory.get_plugin = fake_get_plugin
def _basic_pod_route_setup(self):
pod1 = {'pod_id': 'pod_id_1',
'region_name': 'pod_1',
@ -1440,12 +1451,11 @@ class PluginTest(unittest.TestCase,
# test _prepare_top_element
pool_id = fake_plugin._get_bridge_subnet_pool_id(
t_ctx, q_ctx, 'project_id', t_pod, True)
t_ctx, q_ctx, 'project_id', t_pod)
net, subnet = fake_plugin._get_bridge_network_subnet(
t_ctx, q_ctx, 'project_id', t_pod, pool_id, True)
t_ctx, q_ctx, 'project_id', t_pod, pool_id)
port = fake_plugin._get_bridge_interface(t_ctx, q_ctx, 'project_id',
pod, net['id'], 'b_router_id',
None, True)
pod, net['id'], 'b_router_id')
top_entry_map = {}
with t_ctx.session.begin():
@ -1485,10 +1495,8 @@ class PluginTest(unittest.TestCase,
@staticmethod
def _prepare_network_test(tenant_id, ctx, region_name, index):
t_net_id = uuidutils.generate_uuid()
t_subnet_id = uuidutils.generate_uuid()
b_net_id = uuidutils.generate_uuid()
b_subnet_id = uuidutils.generate_uuid()
t_net_id = b_net_id = uuidutils.generate_uuid()
t_subnet_id = b_subnet_id = uuidutils.generate_uuid()
# no need to specify az, we will setup router in the pod where bottom
# network is created
@ -1632,9 +1640,10 @@ class PluginTest(unittest.TestCase,
'_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(FakeClient, 'add_gateway_routers')
@patch.object(FakeBaseRPCAPI, 'configure_extra_routes')
@patch.object(context, 'get_context_from_neutron_context')
def test_add_interface(self, mock_context, mock_rpc):
def test_add_interface(self, mock_context, mock_rpc, mock_action):
self._basic_pod_route_setup()
fake_plugin = FakePlugin()
@ -1651,25 +1660,31 @@ class PluginTest(unittest.TestCase,
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, 'router')[0]
t_ctx, t_router_id, constants.RT_ROUTER)[0]
mock_rpc.assert_called_once_with(t_ctx, t_router_id)
for b_net in BOTTOM1_NETS:
if 'provider:segmentation_id' in b_net:
self.assertIn(b_net['provider:segmentation_id'], (2000, 2001))
# only one VLAN allocated, for E-W bridge network
# only one VLAN allocated since we just create one bridge network
allocations = [
allocation['allocated'] for allocation in TOP_VLANALLOCATIONS]
self.assertItemsEqual([True, False], allocations)
for segment in TOP_SEGMENTS:
self.assertIn(segment['segmentation_id'], (2000, 2001))
bridge_port_name = constants.ew_bridge_port_name % (tenant_id,
b_router_id)
bridge_port_name = constants.bridge_port_name % (tenant_id,
b_router_id)
_, t_bridge_port_id = db_api.get_bottom_mappings_by_top_id(
t_ctx, bridge_port_name, 'port')[0]
_, b_bridge_port_id = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_bridge_port_id, 'port')[0]
for t_port in TOP_PORTS:
if t_port['id'] == t_bridge_port_id:
t_ns_bridge_net_id = t_port['network_id']
t_ns_bridge_subnet_id = t_port['fixed_ips'][0]['subnet_id']
b_ns_bridge_net_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_ns_bridge_net_id, 'pod_1', constants.RT_NETWORK)
b_ns_bridge_subnet_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_ns_bridge_subnet_id, 'pod_1', constants.RT_SUBNET)
(t_net_id, t_subnet_id, t_router_id,
b_another_net_id, b_another_subnet_id) = self._prepare_router_test(
@ -1678,158 +1693,30 @@ class PluginTest(unittest.TestCase,
fake_plugin.add_router_interface(
q_ctx, t_router_id, {'subnet_id': t_subnet_id})['port_id']
t_ns_bridge_net_id = None
for net in TOP_NETS:
if net['name'].startswith('ns_bridge'):
t_ns_bridge_net_id = net['id']
# N-S bridge not created since no external network created
self.assertIsNone(t_ns_bridge_net_id)
mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_router_id, constants.RT_NS_ROUTER)
# router for north-south networking is not created since no external
# network created
self.assertEqual(len(mappings), 0)
device_ids = ['', '', '']
device_ids = ['', '']
for port in BOTTOM1_PORTS:
if port['id'] == b_bridge_port_id:
device_ids[0] = port['device_id']
elif port['network_id'] == b_net_id and (
port['device_owner'] == 'network:router_interface'):
device_ids[1] = port['device_id']
elif port['network_id'] == b_another_net_id and (
if port['network_id'] == b_net_id and (
port['device_owner'] == 'network:router_interface'):
device_ids[2] = port['device_id']
self.assertEqual(device_ids, [b_router_id, 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,
'_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(FakeBaseRPCAPI, 'configure_extra_routes')
@patch.object(FakeClient, 'add_gateway_routers')
@patch.object(context, 'get_context_from_neutron_context')
def test_add_interface_with_external_network(self, mock_context,
mock_action, mock_rpc):
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
(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)
e_net_id = uuidutils.generate_uuid()
e_net = {'id': e_net_id,
'name': 'ext-net',
'admin_state_up': True,
'shared': False,
'tenant_id': tenant_id,
'router:external': True,
'availability_zone_hints': '["pod_2"]'}
TOP_NETS.append(e_net)
fake_plugin.add_router_interface(
q_ctx, t_router_id, {'subnet_id': t_subnet_id})['port_id']
b_router_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_router_id, 'pod_1', 'router')
mock_rpc.assert_called_once_with(t_ctx, t_router_id)
for b_net in BOTTOM1_NETS:
if 'provider:segmentation_id' in b_net:
self.assertIn(b_net['provider:segmentation_id'], (2000, 2001))
# two VLANs allocated, for E-W and N-S bridge network
allocations = [
allocation['allocated'] for allocation in TOP_VLANALLOCATIONS]
self.assertItemsEqual([True, True], allocations)
for segment in TOP_SEGMENTS:
self.assertIn(segment['segmentation_id'], (2000, 2001))
bridge_port_name = constants.ew_bridge_port_name % (tenant_id,
b_router_id)
_, t_bridge_port_id = db_api.get_bottom_mappings_by_top_id(
t_ctx, bridge_port_name, 'port')[0]
_, b_bridge_port_id = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_bridge_port_id, 'port')[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)
fake_plugin.add_router_interface(
q_ctx, t_router_id, {'subnet_id': t_subnet_id})['port_id']
for net in TOP_NETS:
if net['name'].startswith('ns_bridge'):
t_ns_bridge_net_id = net['id']
for subnet in TOP_SUBNETS:
if subnet['name'].startswith('ns_bridge'):
t_ns_bridge_subnet_id = subnet['id']
b_ns_bridge_net_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_ns_bridge_net_id, 'pod_1', constants.RT_NETWORK)
b_ns_bridge_subnet_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_ns_bridge_subnet_id, 'pod_1', constants.RT_SUBNET)
# internal network and external network are in different pods, need
# to create N-S bridge network and set gateway, add_router_interface
# is called two times, so add_gateway is also called two times.
# add_interface is called three times because the second time
# add_router_interface is called, bottom router is already attached
# to E-W bridge network, only need to attach internal network to
# bottom router
calls = [mock.call(t_ctx, b_router_id,
{'network_id': b_ns_bridge_net_id,
'external_fixed_ips': [
{'subnet_id': b_ns_bridge_subnet_id,
'ip_address': '100.128.0.2'}]}),
mock.call(t_ctx, b_router_id,
{'network_id': b_ns_bridge_net_id,
'external_fixed_ips': [
{'subnet_id': b_ns_bridge_subnet_id,
'ip_address': '100.128.0.2'}]})]
mock_action.assert_has_calls(calls)
device_ids = ['', '', '']
for port in BOTTOM1_PORTS:
if port['id'] == b_bridge_port_id:
device_ids[0] = port['device_id']
elif port['network_id'] == b_net_id and (
port['device_owner'] == 'network:router_interface'):
device_ids[1] = port['device_id']
elif port['network_id'] == b_another_net_id and (
port['device_owner'] == 'network:router_interface'):
device_ids[2] = port['device_id']
self.assertEqual(device_ids, [b_router_id, b_router_id, b_router_id])
(t_net_id, t_subnet_id,
t_router_id, b_net_id, b_subnet_id) = self._prepare_router_test(
tenant_id, t_ctx, 'pod_2', 2)
fake_plugin.add_router_interface(
q_ctx, t_router_id, {'subnet_id': t_subnet_id})['port_id']
b_router_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_router_id, 'pod_2', 'router')
bridge_port_name = constants.ew_bridge_port_name % (tenant_id,
b_router_id)
_, t_bridge_port_id = db_api.get_bottom_mappings_by_top_id(
t_ctx, bridge_port_name, 'port')[0]
_, b_bridge_port_id = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_bridge_port_id, 'port')[0]
# internal network and external network are in the same pod, no need
# to create N-S bridge network when attaching router interface(N-S
# bridge network is created when setting router external gateway), so
# add_gateway is not called.
device_ids = ['', '']
for port in BOTTOM2_PORTS:
if port['id'] == b_bridge_port_id:
device_ids[0] = port['device_id']
elif port['network_id'] == b_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])
call = mock.call(t_ctx, b_router_id,
{'network_id': b_ns_bridge_net_id,
'enable_snat': False,
'external_fixed_ips': [
{'subnet_id': b_ns_bridge_subnet_id,
'ip_address': '100.0.0.2'}]})
# each router interface adding will call add_gateway once
mock_action.assert_has_calls([call, call])
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@ -1870,24 +1757,25 @@ class PluginTest(unittest.TestCase,
[{'key': 'resource_type',
'comparator': 'eq',
'value': 'port'}], [])
# two new entries, for top and bottom bridge ports
self.assertEqual(entry_num + 2, len(entries))
# top and bottom interface is deleted, only bridge port left
# one new entry, for top bridge port
self.assertEqual(entry_num + 1, len(entries))
# top and bottom interface is deleted, only top bridge port left
self.assertEqual(1, len(TOP_PORTS))
self.assertEqual(1, len(BOTTOM1_PORTS))
self.assertEqual(0, len(BOTTOM1_PORTS))
mock_action.side_effect = None
fake_plugin.add_router_interface(q_ctx, t_router_id,
{'subnet_id': t_subnet_id})
# bottom dhcp port and bridge port
self.assertEqual(2, len(BOTTOM1_PORTS))
# just bottom dhcp port, bottom interface is not created because
# action_routers function is mocked
self.assertEqual(1, len(BOTTOM1_PORTS))
with t_ctx.session.begin():
entries = core.query_resource(t_ctx, models.ResourceRouting,
[{'key': 'resource_type',
'comparator': 'eq',
'value': 'port'}], [])
# three more entries, for top and bottom dhcp ports, top interface
self.assertEqual(entry_num + 2 + 3, len(entries))
self.assertEqual(entry_num + 1 + 3, len(entries))
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@ -1921,8 +1809,8 @@ class PluginTest(unittest.TestCase,
# test that we can success when bottom pod comes back
fake_plugin.add_router_interface(
q_ctx, t_router_id, {'subnet_id': t_subnet_id})
# bottom dhcp port, bottom interface and bridge port
self.assertEqual(3, len(BOTTOM1_PORTS))
# bottom dhcp port and bottom interface
self.assertEqual(2, len(BOTTOM1_PORTS))
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@ -2082,24 +1970,24 @@ class PluginTest(unittest.TestCase,
'external_fixed_ips': [{'subnet_id': TOP_SUBNETS[0]['id'],
'ip_address': '100.64.0.5'}]}}})
b_router_id = BOTTOM1_ROUTERS[0]['id']
b_ns_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)
for subnet in TOP_SUBNETS:
if subnet['name'].startswith('ns_bridge_subnet'):
t_ns_bridge_subnet_id = subnet['id']
b_ns_bridge_subnet_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_ns_bridge_subnet_id, 'pod_1', constants.RT_SUBNET)
if subnet['name'].startswith('bridge_subnet'):
t_bridge_subnet_id = subnet['id']
b_bridge_subnet_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, t_bridge_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'}]}
calls = [mock.call(t_ctx, 'add_gateway', b_router_id, body),
mock.call(t_ctx, 'add_interface', b_router_id,
{'subnet_id': b_ns_bridge_subnet_id})]
calls = [mock.call(t_ctx, 'add_gateway', b_ns_router_id, body),
mock.call(t_ctx, 'add_interface', b_ns_router_id,
{'subnet_id': b_bridge_subnet_id})]
mock_action.assert_has_calls(calls)
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@ -2165,14 +2053,14 @@ class PluginTest(unittest.TestCase,
'enable_snat': False,
'external_fixed_ips': [{'subnet_id': t_subnet_id,
'ip_address': '100.64.0.5'}]}}})
_, b_router_id = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_router_id, constants.RT_ROUTER)[0]
_, b_ns_router_id = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_router_id, constants.RT_NS_ROUTER)[0]
# then remove router gateway
fake_plugin.update_router(
q_ctx, t_router_id,
{'router': {'external_gateway_info': {}}})
mock_action.assert_called_with(t_ctx, 'remove_gateway', b_router_id)
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):
tenant_id = TEST_TENANT_ID
@ -2221,7 +2109,8 @@ class PluginTest(unittest.TestCase,
{'subnet_id': t_subnet_id})
# create internal port
t_port_id = uuidutils.generate_uuid()
b_port_id = uuidutils.generate_uuid()
# now top id and bottom id are the same
b_port_id = t_port_id
t_port = {
'id': t_port_id,
'network_id': t_net_id,
@ -2272,6 +2161,12 @@ class PluginTest(unittest.TestCase,
(t_port_id, b_port_id,
fip, e_net) = self._prepare_associate_floatingip_test(t_ctx, q_ctx,
fake_plugin)
# here we attach a subnet in pod2 to the router to test if the two
# bottom routers can be successfully created
_, t_subnet_id2, t_router_id, _, _ = self._prepare_router_test(
TEST_TENANT_ID, t_ctx, 'pod_2', 2)
fake_plugin.add_router_interface(q_ctx, t_router_id,
{'subnet_id': t_subnet_id2})
# associate floating ip
fip_body = {'port_id': t_port_id}
@ -2280,25 +2175,28 @@ class PluginTest(unittest.TestCase,
b_ext_net_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, e_net['id'], 'pod_2', constants.RT_NETWORK)
for port in BOTTOM2_PORTS:
if port['name'] == 'ns_bridge_port':
ns_bridge_port = port
for net in TOP_NETS:
if net['name'].startswith('ns_bridge'):
b_bridge_net_id = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, net['id'], 'pod_1', constants.RT_NETWORK)
calls = [mock.call(t_ctx,
{'floatingip': {
'floating_network_id': b_bridge_net_id,
'floating_ip_address': '100.128.0.3',
'port_id': b_port_id}}),
mock.call(t_ctx,
{'floatingip': {
'floating_network_id': b_ext_net_id,
'floating_ip_address': fip[
'floating_ip_address'],
'port_id': ns_bridge_port['id']}})]
'port_id': b_port_id}})]
mock_create.assert_has_calls(calls)
# routers for east-west networking and north-south networking
self.assertEqual(2, len(BOTTOM2_ROUTERS))
# check routing entries for copied resources have been created
fake_client = FakeClient()
t_port = fake_client.get_ports(t_ctx, t_port_id)
cp_port_mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_port_id, constants.RT_SD_PORT)
cp_subnet_mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_port['fixed_ips'][0]['subnet_id'], constants.RT_SD_SUBNET)
cp_network_mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_port['network_id'], constants.RT_SD_NETWORK)
self.assertEqual(1, len(cp_port_mappings))
self.assertEqual(1, len(cp_subnet_mappings))
self.assertEqual(1, len(cp_network_mappings))
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@ -2364,34 +2262,22 @@ class PluginTest(unittest.TestCase,
fake_plugin.update_floatingip(q_ctx, fip['id'],
{'floatingip': fip_body})
bridge_port_name = constants.ns_bridge_port_name % (
e_net['tenant_id'], None, b_port_id)
t_pod = db_api.get_top_pod(t_ctx)
mapping = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, bridge_port_name, t_pod['region_name'], constants.RT_PORT)
# check routing for bridge port in top pod exists
self.assertIsNotNone(mapping)
# disassociate floating ip
fip_body = {'port_id': None}
fake_plugin.update_floatingip(q_ctx, fip['id'],
{'floatingip': fip_body})
fip_id1 = BOTTOM1_FIPS[0]['id']
fip_id2 = BOTTOM2_FIPS[0]['id']
calls = [mock.call(t_ctx, fip_id1),
mock.call(t_ctx, fip_id2)]
mock_delete.assert_has_calls(calls)
mapping = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, bridge_port_name, t_pod['region_name'], constants.RT_PORT)
# check routing for bridge port in top pod is deleted
self.assertIsNone(mapping)
fip_id = BOTTOM2_FIPS[0]['id']
mock_delete.assert_called_once_with(t_ctx, fip_id)
# check the association information is cleared
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_port_id'])
self.assertIsNone(TOP_FLOATINGIPS[0]['fixed_ip_address'])
self.assertIsNone(TOP_FLOATINGIPS[0]['router_id'])
# check routing entry for copied port has been created
cp_port_mappings = db_api.get_bottom_mappings_by_top_id(
t_ctx, t_port_id, constants.RT_SD_PORT)
self.assertEqual(0, len(cp_port_mappings))
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)
@ -2419,26 +2305,11 @@ class PluginTest(unittest.TestCase,
fake_plugin.update_floatingip(q_ctx, fip['id'],
{'floatingip': fip_body})
bridge_port_name = constants.ns_bridge_port_name % (
e_net['tenant_id'], None, b_port_id)
t_pod = db_api.get_top_pod(t_ctx)
mapping = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, bridge_port_name, t_pod['region_name'], constants.RT_PORT)
# check routing for bridge port in top pod exists
self.assertIsNotNone(mapping)
# disassociate floating ip
fake_plugin.delete_floatingip(q_ctx, fip['id'])
fip_id1 = BOTTOM1_FIPS[0]['id']
fip_id2 = BOTTOM2_FIPS[0]['id']
calls = [mock.call(t_ctx, fip_id1),
mock.call(t_ctx, fip_id2)]
mock_delete.assert_has_calls(calls)
mapping = db_api.get_bottom_id_by_top_id_region_name(
t_ctx, bridge_port_name, t_pod['region_name'], constants.RT_PORT)
# check routing for bridge port in top pod is deleted
self.assertIsNone(mapping)
fip_id = BOTTOM2_FIPS[0]['id']
mock_delete.assert_called_once_with(t_ctx, fip_id)
@patch.object(directory, 'get_plugin', new=fake_get_plugin)
@patch.object(driver.Pool, 'get_instance', new=fake_get_instance)

209
tricircle/tests/unit/xjob/test_xmanager.py

@ -38,27 +38,36 @@ BOTTOM1_SUBNET = []
BOTTOM2_SUBNET = []
BOTTOM1_PORT = []
BOTTOM2_PORT = []
TOP_ROUTER = []
BOTTOM1_ROUTER = []
BOTTOM2_ROUTER = []
TOP_SG = []
BOTTOM1_SG = []
BOTTOM2_SG = []
TOP_FIP = []
BOTTOM1_FIP = []
BOTTOM2_FIP = []
RES_LIST = [TOP_NETWORK, BOTTOM1_NETWORK, BOTTOM2_NETWORK, TOP_SUBNET,
BOTTOM1_SUBNET, BOTTOM2_SUBNET, BOTTOM1_PORT, BOTTOM2_PORT,
BOTTOM1_ROUTER, BOTTOM2_ROUTER, TOP_SG, BOTTOM1_SG, BOTTOM2_SG]
TOP_ROUTER, BOTTOM1_ROUTER, BOTTOM2_ROUTER, TOP_SG, BOTTOM1_SG,
BOTTOM2_SG, TOP_FIP, BOTTOM1_FIP, BOTTOM2_FIP]
RES_MAP = {'top': {'network': TOP_NETWORK,
'subnet': TOP_SUBNET,
'security_group': TOP_SG},
'router': TOP_ROUTER,
'security_group': TOP_SG,
'floatingips': TOP_FIP},
'pod_1': {'network': BOTTOM1_NETWORK,
'subnet': BOTTOM1_SUBNET,
'port': BOTTOM1_PORT,
'router': BOTTOM1_ROUTER,
'security_group': BOTTOM1_SG},
'security_group': BOTTOM1_SG,
'floatingips': BOTTOM1_FIP},
'pod_2': {'network': BOTTOM2_NETWORK,
'subnet': BOTTOM2_SUBNET,
'port': BOTTOM2_PORT,
'router': BOTTOM2_ROUTER,
'security_group': BOTTOM2_SG}}
'security_group': BOTTOM2_SG,
'floatingips': BOTTOM2_FIP}}
class FakeXManager(xmanager.XManager)