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:
parent
5a8a2ffe05
commit
47becd166d
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue