Merge "Implement diffs of ResourceDefinitions"
This commit is contained in:
commit
5a0d8283e0
|
@ -501,15 +501,7 @@ class Resource(object):
|
|||
If something has been removed in after which exists in before we set it
|
||||
to None.
|
||||
"""
|
||||
# Create a set containing the keys in both current and update template
|
||||
template_keys = set(six.iterkeys(before))
|
||||
template_keys.update(set(six.iterkeys(after)))
|
||||
|
||||
# Create a set of keys which differ (or are missing/added)
|
||||
changed_keys_set = set([k for k in template_keys
|
||||
if before.get(k) != after.get(k)])
|
||||
|
||||
return dict((k, after.get(k)) for k in changed_keys_set)
|
||||
return after - before
|
||||
|
||||
def update_template_diff_properties(self, after_props, before_props):
|
||||
"""Return changed Properties between the before and after properties.
|
||||
|
@ -1035,13 +1027,11 @@ class Resource(object):
|
|||
"""
|
||||
if self._needs_update(after, before, after_props, before_props,
|
||||
prev_resource, check_init_complete):
|
||||
tmpl_diff = self.update_template_diff(function.resolve(after),
|
||||
before)
|
||||
tmpl_diff = self.update_template_diff(after.freeze(), before)
|
||||
if tmpl_diff and self.needs_replace_with_tmpl_diff(tmpl_diff):
|
||||
raise exception.UpdateReplace(self)
|
||||
|
||||
self.update_template_diff_properties(after_props,
|
||||
before_props)
|
||||
self.update_template_diff_properties(after_props, before_props)
|
||||
return True
|
||||
|
||||
def _check_restricted_actions(self, actions, after, before,
|
||||
|
@ -1140,8 +1130,7 @@ class Resource(object):
|
|||
with self._action_recorder(action, exception.UpdateReplace):
|
||||
after_props.validate()
|
||||
|
||||
tmpl_diff = self.update_template_diff(function.resolve(after),
|
||||
before)
|
||||
tmpl_diff = self.update_template_diff(after.freeze(), before)
|
||||
if tmpl_diff and self.needs_replace_with_tmpl_diff(tmpl_diff):
|
||||
raise exception.UpdateReplace(self)
|
||||
|
||||
|
|
|
@ -31,6 +31,37 @@ __all__ = ['ResourceDefinition']
|
|||
class ResourceDefinitionCore(object):
|
||||
"""A definition of a resource, independent of any template format."""
|
||||
|
||||
class Diff(object):
|
||||
"""A diff between two versions of the same resource definition."""
|
||||
|
||||
def __init__(self, old_defn, new_defn):
|
||||
if not (isinstance(old_defn, ResourceDefinitionCore) and
|
||||
isinstance(new_defn, ResourceDefinitionCore)):
|
||||
raise TypeError
|
||||
|
||||
self.old_defn = old_defn
|
||||
self.new_defn = new_defn
|
||||
|
||||
def properties_changed(self):
|
||||
"""Return True if the resource properties have changed."""
|
||||
return self.old_defn._properties != self.new_defn._properties
|
||||
|
||||
def metadata_changed(self):
|
||||
"""Return True if the resource metadata has changed."""
|
||||
return self.old_defn._metadata != self.new_defn._metadata
|
||||
|
||||
def update_policy_changed(self):
|
||||
"""Return True if the resource update policy has changed."""
|
||||
return self.old_defn._update_policy != self.new_defn._update_policy
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True if anything has changed."""
|
||||
return (self.properties_changed() or
|
||||
self.metadata_changed() or
|
||||
self.update_policy_changed())
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
DELETION_POLICIES = (
|
||||
DELETE, RETAIN, SNAPSHOT,
|
||||
) = (
|
||||
|
@ -100,6 +131,9 @@ class ResourceDefinitionCore(object):
|
|||
intrinsic functions). Named arguments passed to this method override
|
||||
the values passed as arguments to the constructor.
|
||||
"""
|
||||
if getattr(self, '_frozen', False) and not overrides:
|
||||
return self
|
||||
|
||||
def arg_item(attr_name):
|
||||
name = attr_name.lstrip('_')
|
||||
if name in overrides:
|
||||
|
@ -227,6 +261,17 @@ class ResourceDefinitionCore(object):
|
|||
|
||||
return self._rendering
|
||||
|
||||
def __sub__(self, previous):
|
||||
"""Calculate the difference between this definition and a previous one.
|
||||
|
||||
Return a Diff object that can be used to establish differences between
|
||||
this definition and a previous definition of the same resource.
|
||||
"""
|
||||
if not isinstance(previous, ResourceDefinitionCore):
|
||||
return NotImplemented
|
||||
|
||||
return self.Diff(previous, self)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Compare this resource definition for equality with another.
|
||||
|
||||
|
@ -301,6 +346,51 @@ class ResourceDefinition(ResourceDefinitionCore, collections.Mapping):
|
|||
'the ResourceDefinition API to work with the definition of the '
|
||||
'resource instance.')
|
||||
|
||||
class Diff(ResourceDefinitionCore.Diff, collections.Mapping):
|
||||
"""A resource definition diff that acts like a cfn template snippet.
|
||||
|
||||
This class exists only for backwards compatibility with existing
|
||||
resource plugins and unit tests; it is deprecated and could be removed
|
||||
as soon as the Ocata release. Prefer using the API directly rather than
|
||||
treating the diff as a dict containing the differences between two cfn
|
||||
template snippets.
|
||||
"""
|
||||
|
||||
_deprecation_msg = (
|
||||
'Reading the ResourceDefinition Diff as if it were a diff of two '
|
||||
'snippets from CloudFormation templates is deprecated, and the '
|
||||
'ability to treat it as such will be removed in the future. '
|
||||
'Resource plugins should use the ResourceDefinition.Diff API and '
|
||||
'the ResourceDefinition API to detect changes in the definition '
|
||||
'and work with the new definition of the resource.')
|
||||
|
||||
def __contains__(self, key):
|
||||
warnings.warn(self._deprecation_msg, DeprecationWarning)
|
||||
|
||||
if key == PROPERTIES:
|
||||
return self.properties_changed()
|
||||
elif key == METADATA:
|
||||
return self.metadata_changed()
|
||||
elif key == UPDATE_POLICY:
|
||||
return self.update_policy_changed()
|
||||
else:
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
return (k for k in _KEYS if k in self)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
return self.new_defn.get(key)
|
||||
|
||||
def __len__(self):
|
||||
return len(list(iter(self)))
|
||||
|
||||
def __repr__(self):
|
||||
"""Return a string representation of the diff."""
|
||||
return 'ResourceDefinition.Diff %s' % repr(dict(self))
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Compare this resource definition for equality with another.
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ import six
|
|||
from heat.common import exception
|
||||
from heat.common import grouputils
|
||||
from heat.common import template_format
|
||||
from heat.engine import function
|
||||
from heat.engine import rsrc_defn
|
||||
from heat.tests.autoscaling import inline_templates
|
||||
from heat.tests import common
|
||||
|
@ -552,7 +551,7 @@ class RollingUpdatePolicyDiffTest(common.HeatTestCase):
|
|||
# get the updated json snippet for the InstanceGroup resource in the
|
||||
# context of the current stack
|
||||
updated_grp = updated_stack['my-group']
|
||||
updated_grp_json = function.resolve(updated_grp.t)
|
||||
updated_grp_json = updated_grp.t.freeze()
|
||||
|
||||
# identify the template difference
|
||||
tmpl_diff = updated_grp.update_template_diff(
|
||||
|
|
|
@ -20,7 +20,6 @@ from heat.common import exception
|
|||
from heat.common import grouputils
|
||||
from heat.common import template_format
|
||||
from heat.engine.clients.os import nova
|
||||
from heat.engine import function
|
||||
from heat.engine import rsrc_defn
|
||||
from heat.engine import scheduler
|
||||
from heat.tests.autoscaling import inline_templates
|
||||
|
@ -701,7 +700,7 @@ class RollingUpdatePolicyDiffTest(common.HeatTestCase):
|
|||
# get the updated json snippet for the InstanceGroup resource in the
|
||||
# context of the current stack
|
||||
updated_grp = updated_stack['WebServerGroup']
|
||||
updated_grp_json = function.resolve(updated_grp.t)
|
||||
updated_grp_json = updated_grp.t.freeze()
|
||||
|
||||
# identify the template difference
|
||||
tmpl_diff = updated_grp.update_template_diff(
|
||||
|
|
|
@ -331,7 +331,6 @@ class LoadbalancerReloadTest(common.HeatTestCase):
|
|||
u'LoadBalancerPort': u'80',
|
||||
u'Protocol': u'HTTP'}],
|
||||
u'AvailabilityZones': ['abc', 'xyz']},
|
||||
u'DeletionPolicy': 'Delete',
|
||||
u'Metadata': {}}
|
||||
|
||||
stack = utils.parse_stack(t, params=inline_templates.as_params)
|
||||
|
|
|
@ -17,7 +17,6 @@ import mock
|
|||
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.engine import function
|
||||
from heat.engine.resources.openstack.heat import instance_group as instgrp
|
||||
from heat.engine import rsrc_defn
|
||||
from heat.tests import common
|
||||
|
@ -241,7 +240,7 @@ class InstanceGroupTest(common.HeatTestCase):
|
|||
# get the updated json snippet for the InstanceGroup resource in the
|
||||
# context of the current stack
|
||||
updated_grp = updated_stack['JobServerGroup']
|
||||
updated_grp_json = function.resolve(updated_grp.t)
|
||||
updated_grp_json = updated_grp.t.freeze()
|
||||
|
||||
# identify the template difference
|
||||
tmpl_diff = updated_grp.update_template_diff(
|
||||
|
|
|
@ -19,7 +19,6 @@ import six
|
|||
from heat.common import exception
|
||||
from heat.common import grouputils
|
||||
from heat.common import template_format
|
||||
from heat.engine import function
|
||||
from heat.engine.resources.openstack.heat import resource_group
|
||||
from heat.engine import rsrc_defn
|
||||
from heat.engine import scheduler
|
||||
|
@ -1015,8 +1014,7 @@ class RollingUpdatePolicyDiffTest(common.HeatTestCase):
|
|||
|
||||
updated_stack = utils.parse_stack(updated)
|
||||
updated_grp = updated_stack['group1']
|
||||
updated_grp_json = function.resolve(
|
||||
updated_grp.t)
|
||||
updated_grp_json = updated_grp.t.freeze()
|
||||
|
||||
# identify the template difference
|
||||
tmpl_diff = updated_grp.update_template_diff(
|
||||
|
@ -1083,7 +1081,7 @@ class RollingUpdateTest(common.HeatTestCase):
|
|||
'type': 'OverwrittenFnGetRefIdType'}})
|
||||
updated_stack = utils.parse_stack(updated)
|
||||
updated_grp = updated_stack['group1']
|
||||
updated_grp_json = function.resolve(updated_grp.t)
|
||||
updated_grp_json = updated_grp.t.freeze()
|
||||
tmpl_diff = updated_grp.update_template_diff(
|
||||
updated_grp_json, current_grp_json)
|
||||
|
||||
|
|
|
@ -186,6 +186,76 @@ class ResourceDefinitionTest(common.HeatTestCase):
|
|||
self.assertNotEqual(hash(rd1), hash(rd2))
|
||||
|
||||
|
||||
class ResourceDefinitionDiffTest(common.HeatTestCase):
|
||||
def test_properties_diff(self):
|
||||
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'Foo': 'blarg'},
|
||||
update_policy={'baz': 'quux'},
|
||||
metadata={'baz': 'quux'})
|
||||
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'Foo': 'wibble'},
|
||||
update_policy={'baz': 'quux'},
|
||||
metadata={'baz': 'quux'})
|
||||
|
||||
diff = after - before
|
||||
self.assertTrue(diff.properties_changed())
|
||||
self.assertFalse(diff.update_policy_changed())
|
||||
self.assertFalse(diff.metadata_changed())
|
||||
self.assertTrue(diff)
|
||||
|
||||
def test_update_policy_diff(self):
|
||||
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'baz': 'quux'},
|
||||
update_policy={'Foo': 'blarg'},
|
||||
metadata={'baz': 'quux'})
|
||||
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'baz': 'quux'},
|
||||
update_policy={'Foo': 'wibble'},
|
||||
metadata={'baz': 'quux'})
|
||||
|
||||
diff = after - before
|
||||
self.assertFalse(diff.properties_changed())
|
||||
self.assertTrue(diff.update_policy_changed())
|
||||
self.assertFalse(diff.metadata_changed())
|
||||
self.assertTrue(diff)
|
||||
|
||||
def test_metadata_diff(self):
|
||||
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'baz': 'quux'},
|
||||
update_policy={'baz': 'quux'},
|
||||
metadata={'Foo': 'blarg'})
|
||||
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'baz': 'quux'},
|
||||
update_policy={'baz': 'quux'},
|
||||
metadata={'Foo': 'wibble'})
|
||||
|
||||
diff = after - before
|
||||
self.assertFalse(diff.properties_changed())
|
||||
self.assertFalse(diff.update_policy_changed())
|
||||
self.assertTrue(diff.metadata_changed())
|
||||
self.assertTrue(diff)
|
||||
|
||||
def test_no_diff(self):
|
||||
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'Foo': 'blarg'},
|
||||
update_policy={'bar': 'quux'},
|
||||
metadata={'baz': 'wibble'},
|
||||
depends=['other_resource'],
|
||||
deletion_policy='Delete')
|
||||
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'Foo': 'blarg'},
|
||||
update_policy={'bar': 'quux'},
|
||||
metadata={'baz': 'wibble'},
|
||||
depends=['other_other_resource'],
|
||||
deletion_policy='Retain')
|
||||
|
||||
diff = after - before
|
||||
self.assertFalse(diff.properties_changed())
|
||||
self.assertFalse(diff.update_policy_changed())
|
||||
self.assertFalse(diff.metadata_changed())
|
||||
self.assertFalse(diff)
|
||||
|
||||
|
||||
class ResourceDefinitionSnippetTest(common.HeatTestCase):
|
||||
scenarios = [
|
||||
('type',
|
||||
|
@ -226,3 +296,64 @@ class ResourceDefinitionSnippetTest(common.HeatTestCase):
|
|||
self.assertTrue(ws)
|
||||
for warn in ws:
|
||||
self.assertTrue(issubclass(warn.category, DeprecationWarning))
|
||||
|
||||
|
||||
class ResourceDefinitionDiffSnippetTest(common.HeatTestCase):
|
||||
def test_properties_diff(self):
|
||||
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'Foo': 'blarg'},
|
||||
update_policy={'baz': 'quux'},
|
||||
metadata={'baz': 'quux'})
|
||||
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'Foo': 'wibble'},
|
||||
update_policy={'baz': 'quux'},
|
||||
metadata={'baz': 'quux'})
|
||||
|
||||
diff = after - before
|
||||
self.assertEqual({'Properties': after['Properties']}, dict(diff))
|
||||
|
||||
def test_update_policy_diff(self):
|
||||
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'baz': 'quux'},
|
||||
update_policy={'Foo': 'blarg'},
|
||||
metadata={'baz': 'quux'})
|
||||
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'baz': 'quux'},
|
||||
update_policy={'Foo': 'wibble'},
|
||||
metadata={'baz': 'quux'})
|
||||
|
||||
diff = after - before
|
||||
self.assertEqual({'UpdatePolicy': after['UpdatePolicy']}, dict(diff))
|
||||
|
||||
def test_metadata_diff(self):
|
||||
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'baz': 'quux'},
|
||||
update_policy={'baz': 'quux'},
|
||||
metadata={'Foo': 'blarg'})
|
||||
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'baz': 'quux'},
|
||||
update_policy={'baz': 'quux'},
|
||||
metadata={'Foo': 'wibble'})
|
||||
|
||||
diff = after - before
|
||||
self.assertEqual({'Metadata': after['Metadata']}, dict(diff))
|
||||
|
||||
def test_all_diff(self):
|
||||
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'Foo': 'blarg'},
|
||||
update_policy={'bar': 'quux'},
|
||||
metadata={'baz': 'wibble'},
|
||||
depends=['other_resource'],
|
||||
deletion_policy='Delete')
|
||||
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
|
||||
properties={'Foo': 'wibble'},
|
||||
update_policy={'bar': 'blarg'},
|
||||
metadata={'baz': 'quux'},
|
||||
depends=['other_other_resource'],
|
||||
deletion_policy='Retain')
|
||||
|
||||
diff = after - before
|
||||
self.assertEqual({'Properties': after['Properties'],
|
||||
'UpdatePolicy': after['UpdatePolicy'],
|
||||
'Metadata': after['Metadata']},
|
||||
dict(diff))
|
||||
|
|
Loading…
Reference in New Issue