diff --git a/tacker/db/db_base.py b/tacker/db/db_base.py index 487739f4f..266985486 100644 --- a/tacker/db/db_base.py +++ b/tacker/db/db_base.py @@ -101,6 +101,11 @@ class CommonDbMixin(object): # condition, raising an exception if query_filter is not None: query = query.filter(query_filter) + + # Don't list the deleted entries + if hasattr(model, 'deleted_at'): + query = query.filter_by(deleted_at=None) + return query def _fields(self, resource, fields): @@ -138,6 +143,7 @@ class CommonDbMixin(object): if result_filter: query = result_filter(query, filters) + return query def _apply_dict_extend_functions(self, resource_type, diff --git a/tacker/db/migration/alembic_migrations/versions/941b5a6fff9e_enable_soft_delete.py b/tacker/db/migration/alembic_migrations/versions/941b5a6fff9e_enable_soft_delete.py new file mode 100644 index 000000000..086af3d82 --- /dev/null +++ b/tacker/db/migration/alembic_migrations/versions/941b5a6fff9e_enable_soft_delete.py @@ -0,0 +1,39 @@ +# Copyright 2016 OpenStack Foundation +# +# 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. +# + +"""enable soft delete + +Revision ID: 941b5a6fff9e +Revises: 2ff0a0e360f1 +Create Date: 2016-06-06 10:12:49.787430 + +""" + +# revision identifiers, used by Alembic. +revision = '941b5a6fff9e' +down_revision = '2ff0a0e360f1' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(active_plugins=None, options=None): + for table in ['vims', 'vnf', 'vnfd']: + op.add_column(table, + sa.Column('deleted_at', sa.DateTime(), nullable=True)) + + # unique constraint is taken care by the nfvo_db plugin to support + # soft deletion of vim + op.drop_index('auth_url', table_name='vimauths') diff --git a/tacker/db/migration/alembic_migrations/versions/HEAD b/tacker/db/migration/alembic_migrations/versions/HEAD index 3fd0ce5e4..d81214c3b 100644 --- a/tacker/db/migration/alembic_migrations/versions/HEAD +++ b/tacker/db/migration/alembic_migrations/versions/HEAD @@ -1,2 +1,2 @@ -2ff0a0e360f1 +941b5a6fff9e diff --git a/tacker/db/models_v1.py b/tacker/db/models_v1.py index 58817ae04..94a291e44 100644 --- a/tacker/db/models_v1.py +++ b/tacker/db/models_v1.py @@ -40,3 +40,4 @@ class Audit(object): created_at = sa.Column(sa.DateTime, default=lambda: timeutils.utcnow()) updated_at = sa.Column(sa.DateTime) + deleted_at = sa.Column(sa.DateTime) diff --git a/tacker/db/nfvo/nfvo_db.py b/tacker/db/nfvo/nfvo_db.py index cabf7b3f8..7f53ebc8c 100644 --- a/tacker/db/nfvo/nfvo_db.py +++ b/tacker/db/nfvo/nfvo_db.py @@ -16,7 +16,6 @@ import uuid -from oslo_db import exception from oslo_utils import strutils from oslo_utils import timeutils import sqlalchemy as sa @@ -63,7 +62,6 @@ class VimAuth(model_base.BASE, models_v1.HasId): auth_url = sa.Column(sa.String(255), nullable=False) vim_project = sa.Column(types.Json, nullable=False) auth_cred = sa.Column(types.Json, nullable=False) - __table_args__ = (sa.UniqueConstraint('auth_url'), {}) class NfvoPluginDb(nfvo.NFVOPluginBase, db_base.CommonDbMixin): @@ -101,10 +99,23 @@ class NfvoPluginDb(nfvo.NFVOPluginBase, db_base.CommonDbMixin): else: raise + def _does_already_exist(self, context, vim): + try: + query = self._model_query(context, VimAuth) + for v_auth in query.filter(VimAuth.auth_url == vim.get('auth_url') + ).all(): + vim = self._get_by_id(context, Vim, v_auth.get('vim_id')) + if vim.get('deleted_at') is None: + return True + except orm_exc.NoResultFound: + pass + + return False + def create_vim(self, context, vim): self._validate_default_vim(context, vim) vim_cred = vim['auth_cred'] - try: + if not self._does_already_exist(context, vim): with context.session.begin(subtransactions=True): vim_db = Vim( id=vim.get('id'), @@ -124,20 +135,23 @@ class NfvoPluginDb(nfvo.NFVOPluginBase, db_base.CommonDbMixin): auth_url=vim.get('auth_url'), auth_cred=vim_cred) context.session.add(vim_auth_db) - except exception.DBDuplicateEntry: + else: raise nfvo.VimDuplicateUrlException() return self._make_vim_dict(vim_db) - def delete_vim(self, context, vim_id): + def delete_vim(self, context, vim_id, soft_delete=True): with context.session.begin(subtransactions=True): vim_db = self._get_resource(context, Vim, vim_id) - context.session.query(VimAuth).filter_by( - vim_id=vim_id).delete() - context.session.delete(vim_db) + if soft_delete: + vim_db.update({'deleted_at': timeutils.utcnow()}) + else: + context.session.query(VimAuth).filter_by( + vim_id=vim_id).delete() + context.session.delete(vim_db) def is_vim_still_in_use(self, context, vim_id): with context.session.begin(subtransactions=True): - devices_db = context.session.query(vm_db.VNF).filter_by( + devices_db = self._model_query(context, vm_db.VNF).filter_by( vim_id=vim_id).first() if devices_db is not None: raise nfvo.VimInUseException(vim_id=vim_id) diff --git a/tacker/db/vm/vm_db.py b/tacker/db/vm/vm_db.py index b4fa8f4c4..015eac46b 100644 --- a/tacker/db/vm/vm_db.py +++ b/tacker/db/vm/vm_db.py @@ -277,23 +277,29 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin): template_db.update({'updated_at': timeutils.utcnow()}) return self._make_template_dict(template_db) - def delete_device_template(self, context, device_template_id): + def delete_device_template(self, + context, + device_template_id, + soft_delete=True): with context.session.begin(subtransactions=True): # TODO(yamahata): race. prevent from newly inserting hosting device # that refers to this template devices_db = context.session.query(VNF).filter_by( vnfd_id=device_template_id).first() - if devices_db is not None: + if devices_db is not None and devices_db.deleted_at is None: raise vnfm.DeviceTemplateInUse( device_template_id=device_template_id) - context.session.query(ServiceType).filter_by( - vnfd_id=device_template_id).delete() - context.session.query(VNFDAttribute).filter_by( - vnfd_id=device_template_id).delete() template_db = self._get_resource(context, VNFD, device_template_id) - context.session.delete(template_db) + if soft_delete: + template_db.update({'deleted_at': timeutils.utcnow()}) + else: + context.session.query(ServiceType).filter_by( + vnfd_id=device_template_id).delete() + context.session.query(VNFDAttribute).filter_by( + vnfd_id=device_template_id).delete() + context.session.delete(template_db) def get_device_template(self, context, device_template_id, fields=None): template_db = self._get_resource(context, VNFD, @@ -437,10 +443,6 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin): def _update_device_post(self, context, device_id, new_status, new_device_dict=None): with context.session.begin(subtransactions=True): - (self._model_query(context, VNF). - filter(VNF.id == device_id). - filter(VNF.status == constants.PENDING_UPDATE). - update({'status': new_status})) (self._model_query(context, VNF). filter(VNF.id == device_id). filter(VNF.status == constants.PENDING_UPDATE). @@ -466,7 +468,7 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin): return self._make_device_dict(device_db) - def _delete_device_post(self, context, device_id, error): + def _delete_device_post(self, context, device_id, error, soft_delete=True): with context.session.begin(subtransactions=True): query = ( self._model_query(context, VNF). @@ -475,9 +477,12 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin): if error: query.update({'status': constants.ERROR}) else: - (self._model_query(context, VNFAttribute). - filter(VNFAttribute.vnf_id == device_id).delete()) - query.delete() + if soft_delete: + query.update({'deleted_at': timeutils.utcnow()}) + else: + (self._model_query(context, VNFAttribute). + filter(VNFAttribute.vnf_id == device_id).delete()) + query.delete() # reference implementation. needs to be overrided by subclass def create_device(self, context, device): @@ -503,12 +508,15 @@ class VNFMPluginDb(vnfm.VNFMPluginBase, db_base.CommonDbMixin): return device_dict # reference implementation. needs to be overrided by subclass - def delete_device(self, context, device_id): + def delete_device(self, context, device_id, soft_delete=True): self._delete_device_pre(context, device_id) # start actual deletion of hosting device. # Waiting for completion of deletion should be done backgroundly # by another thread if it takes a while. - self._delete_device_post(context, device_id, False) + self._delete_device_post(context, + device_id, + False, + soft_delete=soft_delete) def get_device(self, context, device_id, fields=None): device_db = self._get_resource(context, VNF, device_id)