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 []