Bridge network, subnet clean during router deletion

1. What is the problem
To connect bottom routers in different pods, we need to create
bridge networks and subnets. These networks and subnets are not
deleted after bottom routers are deleted.

2. What is the solution to the problem
Clean these bridge networks and subnets during top router deletion.

3. What the features need to be implemented to the Tricircle
   to realize the solution
Bridge networks and subnets now can be cleaned up.

Change-Id: I1f2feb7cba3dda14350b3e25a3c33563379eb580
This commit is contained in:
zhiyuan_cai 2016-08-17 17:05:54 +08:00
parent 5a8a2ffe05
commit 47becd166d
4 changed files with 218 additions and 12 deletions

View File

@ -117,13 +117,14 @@ class GlanceResourceHandle(ResourceHandle):
class NeutronResourceHandle(ResourceHandle):
service_type = cons.ST_NEUTRON
support_resource = {'network': LIST | CREATE | DELETE | GET,
'subnet': LIST | CREATE | DELETE | GET | UPDATE,
'port': LIST | CREATE | DELETE | GET,
'router': LIST | CREATE | ACTION | GET | UPDATE,
'security_group': LIST | CREATE | GET,
'security_group_rule': LIST | CREATE | DELETE,
'floatingip': LIST | CREATE | UPDATE | DELETE}
support_resource = {
'network': LIST | CREATE | DELETE | GET,
'subnet': LIST | CREATE | DELETE | GET | UPDATE,
'port': LIST | CREATE | DELETE | GET,
'router': LIST | CREATE | DELETE | ACTION | GET | UPDATE,
'security_group': LIST | CREATE | GET,
'security_group_rule': LIST | CREATE | DELETE,
'floatingip': LIST | CREATE | UPDATE | DELETE}
def _get_client(self, cxt):
return q_client.Client('2.0',

View File

@ -184,6 +184,22 @@ def get_bottom_mappings_by_tenant_pod(context,
return routings
def delete_mappings_by_top_id(context, top_id):
with context.session.begin():
core.delete_resources(
context, models.ResourceRouting,
filters=[{'key': 'top_id', 'comparator': 'eq',
'value': top_id}])
def delete_mappings_by_bottom_id(context, bottom_id):
with context.session.begin():
core.delete_resources(
context, models.ResourceRouting,
filters=[{'key': 'bottom_id', 'comparator': 'eq',
'value': bottom_id}])
def get_next_bottom_pod(context, current_pod_id=None):
pods = list_pods(context, sorts=[(models.Pod.pod_id, True)])
# NOTE(zhiyuan) number of pods is small, just traverse to filter top pod

View File

@ -690,7 +690,127 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2,
def create_router(self, context, router):
return super(TricirclePlugin, self).create_router(context, router)
def _delete_top_bridge_resource(self, t_ctx, q_ctx, resource_type,
resource_id, resource_name):
with t_ctx.session.begin():
core.update_resources(
t_ctx, models.ResourceRouting,
[{'key': 'bottom_id', 'comparator': 'eq',
'value': resource_id}],
{'bottom_id': None,
'created_at': t_constants.expire_time,
'updated_at': t_constants.expire_time})
if resource_type == t_constants.RT_PORT:
getattr(super(TricirclePlugin, self), 'delete_%s' % resource_type)(
q_ctx, resource_id)
else:
getattr(self, 'delete_%s' % resource_type)(q_ctx, resource_id)
with t_ctx.session.begin():
core.delete_resources(t_ctx, models.ResourceRouting,
[{'key': 'top_id',
'comparator': 'eq',
'value': resource_name}])
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)
def _delete_top_bridge_port(self, t_ctx, q_ctx, bridge_port_id,
bridge_port_name):
self._delete_top_bridge_resource(t_ctx, q_ctx, t_constants.RT_PORT,
bridge_port_id, bridge_port_name)
def delete_router(self, context, _id):
router = super(TricirclePlugin,
self)._ensure_router_not_in_use(context, _id)
project_id = router['tenant_id']
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)
for pod, b_router_id in mappings:
b_client = self._get_client(pod['pod_name'])
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_pod_name(
t_ctx, t_ew_port_id, pod['pod_name'], t_constants.RT_PORT)
if b_ew_port_id:
request_body = {'port_id': b_ew_port_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
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_pod_name(
t_ctx, t_ns_subnet_id, pod['pod_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)
routers = super(TricirclePlugin, self).get_routers(
context, {'tenant_id': [project_id]})
if len(routers) <= 1:
self._delete_top_bridge_network_subnet(t_ctx, context)
super(TricirclePlugin, self).delete_router(context, _id)
def _prepare_top_element(self, t_ctx, q_ctx,

View File

@ -199,6 +199,11 @@ class DotDict(dict):
return self.get(item)
class DotList(list):
def all(self):
return self
class FakeNeutronClient(object):
_res_map = {'top': {'port': TOP_PORTS},
@ -367,6 +372,9 @@ class FakeClient(object):
'comparator': 'eq',
'value': net_id}])[0]
def delete_networks(self, ctx, net_id):
self.delete_resources('network', ctx, net_id)
def list_subnets(self, ctx, filters=None):
return self.list_resources('subnet', ctx, filters)
@ -399,6 +407,13 @@ class FakeClient(object):
def delete_ports(self, ctx, port_id):
self.delete_resources('port', ctx, port_id)
index = -1
for i, allocation in enumerate(TOP_IPALLOCATIONS):
if allocation['port_id'] == port_id:
index = i
break
if index != -1:
del TOP_IPALLOCATIONS[index]
def add_gateway_routers(self, ctx, *args, **kwargs):
# only for mock purpose
@ -441,6 +456,9 @@ class FakeClient(object):
'value': router_id}])[0]
return _fill_external_gateway_info(router)
def delete_routers(self, ctx, router_id):
self.delete_resources('router', ctx, router_id)
def action_routers(self, ctx, action, *args, **kwargs):
# divide into three functions for test purpose
if action == 'add_interface':
@ -669,7 +687,7 @@ class FakeQuery(object):
filtered_list.append(record)
return FakeQuery(filtered_list, self.table)
def delete(self):
def delete(self, synchronize_session=False):
for model_obj in self.records:
unlink_models(RES_MAP['routers'], model_obj, 'router_id',
'id', 'attached_ports', 'port_id', 'port_id')
@ -1381,7 +1399,7 @@ class PluginTest(unittest.TestCase,
'name': 'top_router',
'distributed': False,
'tenant_id': tenant_id,
'attached_ports': []
'attached_ports': DotList()
}
TOP_ROUTERS.append(DotDict(t_router))
else:
@ -1875,7 +1893,7 @@ class PluginTest(unittest.TestCase,
'name': 'router',
'distributed': False,
'tenant_id': tenant_id,
'attached_ports': []
'attached_ports': DotList()
}
TOP_ROUTERS.append(DotDict(t_router))
@ -1957,7 +1975,7 @@ class PluginTest(unittest.TestCase,
'name': 'router',
'distributed': False,
'tenant_id': tenant_id,
'attached_ports': []
'attached_ports': DotList()
}
TOP_ROUTERS.append(DotDict(t_router))
@ -2214,7 +2232,6 @@ class PluginTest(unittest.TestCase,
(t_port_id, b_port_id,
fip, e_net) = self._prepare_associate_floatingip_test(t_ctx, q_ctx,
fake_plugin)
# associate floating ip
fip_body = {'port_id': t_port_id}
fake_plugin.update_floatingip(q_ctx, fip['id'],
@ -2241,6 +2258,58 @@ class PluginTest(unittest.TestCase,
# check routing for bridge port in top pod is deleted
self.assertIsNone(mapping)
@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_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)
# 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()