Always rollback to the target revision

This PS removes some additional validations around not being
able to rollback to a target revision:
  - if the target revision == current revision
  - if the target revision is effectively equivalent to the current
    revision (in terms of constituent documents)

Change-Id: I92f8f9557f96b6a27f0dcef4f3138d542e5aa915
This commit is contained in:
Felipe Monteiro 2017-11-30 03:24:48 +00:00
parent fb15186b44
commit 14f1b7a0e8
5 changed files with 47 additions and 59 deletions

View File

@ -1021,10 +1021,9 @@ def revision_rollback(revision_id, latest_revision, session=None):
(d['data_hash'], d['metadata_hash'])
for d in latest_revision['documents']]
# If the rollback revision is the same as the latest revision, then there's
# no point in rolling back.
if latest_revision['id'] == revision_id:
raise errors.InvalidRollback(revision_id=revision_id)
LOG.debug('The revision being rolled back to is the current revision.'
'Expect no meaningful changes.')
orig_revision = revision_get(revision_id, session=session)
@ -1039,10 +1038,12 @@ def revision_rollback(revision_id, latest_revision, session=None):
else:
doc_diff[orig_doc['id']] = False
# If no changes have been made between the target revision to rollback to
# and the latest revision, raise an exception.
# No changes have been made between the target revision to rollback to
# and the latest revision.
if set(doc_diff.values()) == set([False]):
raise errors.InvalidRollback(revision_id=revision_id)
LOG.debug('The revision being rolled back to has the same documents '
'as that of the current revision. Expect no meaningful '
'changes.')
# Create the new revision,
new_revision = models.Revision()

View File

@ -263,12 +263,6 @@ class RevisionTagBadFormat(DeckhandException):
code = 400
class InvalidRollback(DeckhandException):
msg_fmt = ("The requested rollback for target revision %(revision)s is "
"invalid as the latest revision matches the target revision.")
code = 400
class BarbicanException(DeckhandException):
def __init__(self, message, code):

View File

@ -45,6 +45,31 @@ class DeckhandTestCase(testtools.TestCase):
elif isinstance(collection, dict):
self.assertEqual(0, len(collection.keys()))
def assertDictItemsAlmostEqual(self, first, second, ignore):
"""Assert that the items in a dictionary or list of dictionaries
are equal, except for the keys specified in ``ignore``.
Both first and second must contain the keys specified in ``ignore``.
:param first: First dictionary or list of dictionaries to compare.
:type first: dict or list[dict]
:param second: Second dictionary or list of dictionaries to compare.
:type second: dict or list[dict]
:param ignore: List of keys to ignore in both dictionaries or list
of dictionaries.
:type ignore: list or tuple
"""
if not isinstance(first, list):
first = [first]
if not isinstance(second, list):
second = [second]
for key in ignore:
for item in first:
item.pop(key)
for item in second:
item.pop(key)
self.assertEqual(first, second)
def patch(self, target, autospec=True, **kwargs):
"""Returns a started `mock.patch` object for the supplied target.

View File

@ -83,3 +83,18 @@ class TestRevisionRollback(base.TestDbBase):
[d['revision_id'] for d in rollback_documents])
self.assertEqual([1, 1, 4, 4],
[d['orig_revision_id'] for d in rollback_documents])
def test_rollback_to_revision_same_as_current_revision(self):
payload = base.DocumentFixture.get_minimal_multi_fixture(count=4)
bucket_name = test_utils.rand_name('bucket')
created_documents = self.create_documents(bucket_name, payload)
orig_revision_id = created_documents[0]['revision_id']
orig_documents = self.list_revision_documents(orig_revision_id)
rollback_revision = self.rollback_revision(orig_revision_id)
self.assertDictItemsAlmostEqual(
sorted(orig_documents, key=lambda d: d['created_at']),
sorted(rollback_revision['documents'],
key=lambda d: d['created_at']),
ignore=['created_at', 'updated_at', 'revision_id',
'orig_revision_id', 'id'])

View File

@ -1,47 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
from deckhand import errors
from deckhand.tests import test_utils
from deckhand.tests.unit.db import base
class TestRevisionRollbackNegative(base.TestDbBase):
def test_rollback_same_revision_raises_error(self):
# Revision 1: Create 4 documents.
payload = base.DocumentFixture.get_minimal_multi_fixture(count=4)
bucket_name = test_utils.rand_name('bucket')
created_documents = self.create_documents(bucket_name, payload)
orig_revision_id = created_documents[0]['revision_id']
# Attempt to rollback to the latest revision, which should result
# in an error.
self.assertRaises(
errors.InvalidRollback, self.rollback_revision, orig_revision_id)
def test_rollback_unchanged_revision_history_raises_error(self):
# Revision 1: Create 4 documents.
payload = base.DocumentFixture.get_minimal_multi_fixture(count=4)
bucket_name = test_utils.rand_name('bucket')
created_documents = self.create_documents(bucket_name, payload)
orig_revision_id = created_documents[0]['revision_id']
# Create a 2nd revision that is a carbon-copy of 1st.
self.create_documents(bucket_name, payload)
# Attempt to rollback to the 1st revision, which should result in an
# error, as it is identical to the latest revision.
self.assertRaises(
errors.InvalidRollback, self.rollback_revision, orig_revision_id)