From 4aedae3caa81f7b4d5175eef688399b8ec9a8022 Mon Sep 17 00:00:00 2001 From: songbaisen Date: Tue, 26 Dec 2017 09:56:29 +0800 Subject: [PATCH] code for resource deleting 1. What is the problem? During the deletion of a network which is mapped to several local networks, if the local neutron server receives a network-get request, it will bring some conflict operations. For example, we delete local networks before deleting central network. If a "get_network" request comes to a local neutron server after the local network is completely deleted in that region, if central network is still there (assuming it takes certain time to delete all local networks), Tricircle will also query central neutron and the deleted local network will be recreated. 2. What is the solution to the problem? The solution to resource deleting is covered in specs/queens/resource deleting.rst 3. What the features to be implemented in the Tricircle to realize the solution? No new features. Signed-off-by: song baisen Co-Authored-By: CR_hui , zhiyuan_cai Change-Id: I7b3c1efb88c693c3babccdfc865fa560922ede28 --- tricircle/common/exceptions.py | 5 + .../versions/011_add_deleting_resources.py | 35 +++++++ tricircle/db/models.py | 20 ++++ tricircle/network/central_plugin.py | 95 +++++++++++++++++++ tricircle/network/local_plugin.py | 3 +- 5 files changed, 157 insertions(+), 1 deletion(-) create mode 100755 tricircle/db/migrate_repo/versions/011_add_deleting_resources.py diff --git a/tricircle/common/exceptions.py b/tricircle/common/exceptions.py index f4a163ee..326194ff 100644 --- a/tricircle/common/exceptions.py +++ b/tricircle/common/exceptions.py @@ -244,3 +244,8 @@ class RouterNetworkLocationMismatch(exceptions.InvalidInput): 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) + + +class ResourceIsInDeleting(TricircleException): + message = 'resource is in deleting now' + code = 204 diff --git a/tricircle/db/migrate_repo/versions/011_add_deleting_resources.py b/tricircle/db/migrate_repo/versions/011_add_deleting_resources.py new file mode 100755 index 00000000..dbb0c597 --- /dev/null +++ b/tricircle/db/migrate_repo/versions/011_add_deleting_resources.py @@ -0,0 +1,35 @@ +# Copyright 2017 SZZT 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. + +import sqlalchemy as sql + + +def upgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + + recycle_resources = sql.Table( + 'deleting_resources', meta, + sql.Column('resource_id', sql.String(length=127), nullable=False), + sql.Column('resource_type', sql.String(length=64), nullable=False), + sql.Column('deleted_at', sql.DateTime), + mysql_engine='InnoDB', + mysql_charset='utf8') + + recycle_resources.create() + + +def downgrade(migrate_engine): + raise NotImplementedError('downgrade not support') diff --git a/tricircle/db/models.py b/tricircle/db/models.py index 06521413..7fd47cee 100644 --- a/tricircle/db/models.py +++ b/tricircle/db/models.py @@ -147,3 +147,23 @@ class RecycleResources(core.ModelBase, core.DictBase): sql.String(length=64), nullable=False) project_id = sql.Column('project_id', sql.String(length=36), nullable=False, index=True) + + +class DeletingResources(core.ModelBase, core.DictBase): + __tablename__ = 'deleting_resources' + + __table_args__ = ( + schema.UniqueConstraint( + 'resource_id', 'resource_type', + name='deleting_resources0resource_id0resource_type'), + ) + + attributes = ['resource_id', 'resource_type', 'deleted_at'] + + resource_id = sql.Column('resource_id', sql.String(length=127), + nullable=False, primary_key=True) + + resource_type = sql.Column('resource_type', sql.String(length=64), + nullable=False) + + deleted_at = sql.Column('deleted_at', sql.DateTime) diff --git a/tricircle/network/central_plugin.py b/tricircle/network/central_plugin.py index 588a1115..9febd349 100644 --- a/tricircle/network/central_plugin.py +++ b/tricircle/network/central_plugin.py @@ -15,6 +15,7 @@ import collections import copy +import datetime import re import six @@ -353,14 +354,83 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, res['tags'] = [] return res + def check_resource_not_in_deleting(self, context, dict_para): + t_ctx = t_context.get_context_from_neutron_context(context) + with t_ctx.session.begin(): + resource_filters = [] + for key in dict_para.keys(): + resource_filters.append({'key': key, + 'comparator': 'eq', + 'value': dict_para[key]}) + + deleting_resource = core.query_resource(t_ctx, + models.DeletingResources, + resource_filters, []) + + if len(deleting_resource): + if not hasattr(context, "USER_AGENT") or \ + context.USER_AGENT == t_constants.USER_AGENT: + raise t_exceptions.ResourceIsInDeleting() + elif context.USER_AGENT == t_constants.LOCAL: + raise t_exceptions.ResourceNotFound( + models.DeletingResources, dict_para['resource_id']) + + def _check_network_not_in_use(self, context, t_ctx, network_id): + # use a different name to avoid override _ensure_entwork_not_in_use + subnets = self._get_subnets_by_network(context, network_id) + auto_delete_port_names = [] + + for subnet in subnets: + subnet_id = subnet['id'] + region_names = [e[0] for e in t_ctx.session.query( + sql.distinct(models.Pod.region_name)).join( + models.ResourceRouting, + models.Pod.pod_id == models.ResourceRouting.pod_id).filter( + models.ResourceRouting.top_id == subnet_id)] + auto_delete_port_names.extend([t_constants.interface_port_name % ( + region_name, subnet_id) for region_name in region_names]) + dhcp_port_name = t_constants.dhcp_port_name % subnet_id + snat_port_name = t_constants.snat_port_name % subnet_id + auto_delete_port_names.append(dhcp_port_name) + auto_delete_port_names.append(snat_port_name) + + if not auto_delete_port_names: + # pre-created port not found, any ports left need to be deleted + # before deleting network + non_auto_delete_ports = context.session.query( + models_v2.Port.id).filter_by(network_id=network_id) + if non_auto_delete_ports.count(): + raise exceptions.NetworkInUse(net_id=network_id) + return + + t_pod = db_api.get_top_pod(t_ctx) + auto_delete_port_ids = [e[0] for e in t_ctx.session.query( + models.ResourceRouting.bottom_id).filter_by( + pod_id=t_pod['pod_id'], resource_type=t_constants.RT_PORT).filter( + models.ResourceRouting.top_id.in_(auto_delete_port_names))] + + non_auto_delete_ports = context.session.query( + models_v2.Port.id).filter_by(network_id=network_id).filter( + ~models_v2.Port.id.in_(auto_delete_port_ids)) + if non_auto_delete_ports.count(): + raise exceptions.NetworkInUse(net_id=network_id) + def delete_network(self, context, network_id): t_ctx = t_context.get_context_from_neutron_context(context) + dict_para = {'resource_id': network_id, 'resource_type': 'network'} + self.check_resource_not_in_deleting(context, dict_para) + self._check_network_not_in_use(context, t_ctx, network_id) + dict_para['deleted_at'] = datetime.datetime.utcnow() + with t_ctx.session.begin(): + core.create_resource(t_ctx, models.DeletingResources, dict_para) + try: for pod, bottom_network_id in ( self.helper.get_real_shadow_resource_iterator( t_ctx, t_constants.RT_NETWORK, network_id)): self._get_client(pod['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 @@ -380,10 +450,19 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, 'comparator': 'eq', 'value': network_id}]) + subnets = self._get_subnets_by_network(context, network_id) + for subnet in subnets: + self.delete_subnet(context, subnet['id']) with context.session.begin(subtransactions=True): self.type_manager.release_network_segments(context, network_id) super(TricirclePlugin, self).delete_network(context, network_id) + with t_ctx.session.begin(): + core.delete_resources(t_ctx, models.DeletingResources, + filters=[{'key': 'resource_id', + 'comparator': 'eq', + 'value': network_id}]) + def _raise_if_updates_external_attribute(self, attrs): """Raise exception if external attributes are present. @@ -480,8 +559,24 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, return policy['id'] if policy else None def get_network(self, context, network_id, fields=None): + delete = False + if network_id.endswith('_delete'): + delete = True + network_id = network_id[0: network_id.find('_delete')] + + dict_para = {'resource_id': network_id, 'resource_type': 'network'} + try: + self.check_resource_not_in_deleting(context, dict_para) + except t_exceptions.ResourceIsInDeleting(): + return network_id + except t_exceptions.ResourceNotFound: + if delete: + pass + else: + raise exceptions.NotFound() net = super(TricirclePlugin, self).get_network(context, network_id, fields) + if not fields or 'id' in fields: self.type_manager.extend_network_dict_provider(context, net) diff --git a/tricircle/network/local_plugin.py b/tricircle/network/local_plugin.py index fc0e90c7..b30b406e 100644 --- a/tricircle/network/local_plugin.py +++ b/tricircle/network/local_plugin.py @@ -182,8 +182,9 @@ class TricirclePlugin(plugin.Ml2Plugin): t_ctx = t_context.get_context_from_neutron_context(context) if self._skip_non_api_query(t_ctx): return [] + para = network['id'] + '_delete' t_network = self.neutron_handle.handle_get( - t_ctx, 'network', network['id']) + t_ctx, 'network', para) return self._ensure_subnet(context, t_network) if not subnet_ids: return []