diff --git a/heat/objects/event.py b/heat/objects/event.py index 7b1ef7bc2..2d5676a9e 100644 --- a/heat/objects/event.py +++ b/heat/objects/event.py @@ -20,7 +20,7 @@ from oslo_versionedobjects import fields from heat.common import identifier from heat.db.sqlalchemy import api as db_api from heat.objects import base as heat_base -from heat.objects import fields as heat_fields +from heat.objects import resource_properties_data as rpd class Event( @@ -37,7 +37,8 @@ class Event( 'physical_resource_id': fields.StringField(nullable=True), 'resource_status_reason': fields.StringField(nullable=True), 'resource_type': fields.StringField(nullable=True), - 'resource_properties': heat_fields.JsonField(nullable=True), + 'rsrc_prop_data': fields.ObjectField( + rpd.ResourcePropertiesData), 'created_at': fields.DateTimeField(read_only=True), 'updated_at': fields.DateTimeField(nullable=True), } @@ -45,11 +46,23 @@ class Event( @staticmethod def _from_db_object(context, event, db_event): for field in event.fields: - event[field] = db_event[field] + event[field] = db_event[field] + if db_event['rsrc_prop_data']: + event['rsrc_prop_data'] = \ + rpd.ResourcePropertiesData._from_db_object( + rpd.ResourcePropertiesData(context), context, + db_event['rsrc_prop_data']) + event._resource_properties = event['rsrc_prop_data'].data + else: + event._resource_properties = db_event['resource_properties'] or {} event._context = context event.obj_reset_changes() return event + @property + def resource_properties(self): + return self._resource_properties + @classmethod def get_by_id(cls, context, event_id): db_event = db_api.event_get(context, event_id) diff --git a/heat/objects/resource.py b/heat/objects/resource.py index 7530bc35c..c1a365c2f 100644 --- a/heat/objects/resource.py +++ b/heat/objects/resource.py @@ -18,6 +18,7 @@ import collections from oslo_config import cfg +from oslo_log import log as logging from oslo_versionedobjects import base from oslo_versionedobjects import fields import six @@ -26,13 +27,17 @@ import tenacity from heat.common import crypt from heat.common import exception from heat.common.i18n import _ +from heat.common.i18n import _LE from heat.db.sqlalchemy import api as db_api from heat.objects import base as heat_base from heat.objects import fields as heat_fields from heat.objects import resource_data +from heat.objects import resource_properties_data as rpd cfg.CONF.import_opt('encrypt_parameters_and_properties', 'heat.common.config') +LOG = logging.getLogger(__name__) + def retry_on_conflict(func): wrapper = tenacity.retry( @@ -74,12 +79,14 @@ class Resource( 'status_reason': fields.StringField(nullable=True), 'action': fields.StringField(nullable=True), 'rsrc_metadata': heat_fields.JsonField(nullable=True), - 'properties_data': heat_fields.JsonField(nullable=True), - 'properties_data_encrypted': fields.BooleanField(default=False), 'data': fields.ListOfObjectsField( resource_data.ResourceData, nullable=True ), + 'rsrc_prop_data': fields.ObjectField( + rpd.ResourcePropertiesData, nullable=True), + 'rsrc_prop_data_id': fields.ObjectField( + fields.IntegerField(nullable=True)), 'engine_id': fields.StringField(nullable=True), 'atomic_key': fields.IntegerField(nullable=True), 'current_template_id': fields.IntegerField(), @@ -102,14 +109,38 @@ class Resource( else: resource[field] = db_resource[field] - if resource.properties_data_encrypted and resource.properties_data: - decrypted_data = crypt.decrypted_dict(resource.properties_data) - resource.properties_data = decrypted_data + if db_resource['rsrc_prop_data'] is not None: + resource['rsrc_prop_data'] = \ + rpd.ResourcePropertiesData._from_db_object( + rpd.ResourcePropertiesData(context), context, + db_resource['rsrc_prop_data']) + resource._properties_data = resource['rsrc_prop_data'].data + if db_resource['properties_data']: + LOG.error( + _LE('Unexpected condition where resource.rsrc_prop_data ' + 'and resource.properties_data are both not null. ' + 'rsrc_prop_data.id: %(rsrc_prop_data_id)s ,' + 'resource id: %(res_id)s') + % {'rsrc_prop_data_id': resource['rsrc_prop_data'].id, + 'res_id': resource['id']}) + elif db_resource['properties_data']: # legacy field + if db_resource['properties_data_encrypted']: + decrypted_data = crypt.decrypted_dict( + db_resource['properties_data']) + resource._properties_data = decrypted_data + else: + resource._properties_data = db_resource['properties_data'] + else: + resource._properties_data = {} resource._context = context resource.obj_reset_changes() return resource + @property + def properties_data(self): + return self._properties_data + @classmethod def get_obj(cls, context, resource_id, refresh=False): resource_db = db_api.resource_get(context, resource_id, diff --git a/heat/tests/test_event.py b/heat/tests/test_event.py index ec61f79e6..c1e8a0b3d 100644 --- a/heat/tests/test_event.py +++ b/heat/tests/test_event.py @@ -14,12 +14,15 @@ import mock from oslo_config import cfg import oslo_db.exception +import uuid +from heat.db.sqlalchemy import models from heat.engine import event from heat.engine import rsrc_defn from heat.engine import stack from heat.engine import template from heat.objects import event as event_object +from heat.objects import resource_properties_data as rpd_object from heat.objects import stack as stack_object from heat.tests import common from heat.tests import generic_resource as generic_rsrc @@ -210,6 +213,24 @@ class EventTest(EventCommon): 'version': '0.1'}} self.assertEqual(expected, e.as_dict()) + def test_event_object_resource_properties_data(self): + cfg.CONF.set_override('encrypt_parameters_and_properties', True, + enforce_type=True) + data = {'p1': 'hello', + 'p2': 'too soon?'} + rpd_obj = rpd_object.ResourcePropertiesData().create(self.ctx, data) + rpd_db_obj = self.ctx.session.query( + models.ResourcePropertiesData).get(rpd_obj.id) + e_obj = event_object.Event().create( + self.ctx, + {'stack_id': self.stack.id, + 'uuid': str(uuid.uuid4()), + 'rsrc_prop_data': rpd_db_obj}) + e_obj = event_object.Event().get_by_id(utils.dummy_context(), + e_obj.id) + # properties data appears unencrypted to event object + self.assertEqual(data, e_obj.rsrc_prop_data.data) + class EventTestSingleLargeProp(EventCommon): diff --git a/heat/tests/test_resource.py b/heat/tests/test_resource.py index 23daf12d3..ea47c61bc 100644 --- a/heat/tests/test_resource.py +++ b/heat/tests/test_resource.py @@ -28,6 +28,7 @@ from heat.common.i18n import _ from heat.common import short_id from heat.common import timeutils from heat.db.sqlalchemy import api as db_api +from heat.db.sqlalchemy import models from heat.engine import attributes from heat.engine.cfn import functions as cfn_funcs from heat.engine import clients @@ -48,6 +49,7 @@ from heat.engine import template from heat.engine import translation from heat.objects import resource as resource_objects from heat.objects import resource_data as resource_data_object +from heat.objects import resource_properties_data as rpd_object from heat.tests import common from heat.tests import generic_resource as generic_rsrc from heat.tests import utils @@ -552,6 +554,40 @@ class ResourceTest(common.HeatTestCase): res._store_or_update(res.UPDATE, res.COMPLETE, 'should not change') self.assertIsNone(res.updated_time) + def test_resource_object_resource_properties_data(self): + cfg.CONF.set_override('encrypt_parameters_and_properties', True, + enforce_type=True) + data = {'p1': 'i see', + 'p2': 'good times, good times'} + rpd_obj = rpd_object.ResourcePropertiesData().create( + self.stack.context, data) + rpd_db_obj = self.stack.context.session.query( + models.ResourcePropertiesData).get(rpd_obj.id) + res_obj1 = resource_objects.Resource().create( + self.stack.context, + {'stack_id': self.stack.id, + 'uuid': str(uuid.uuid4()), + 'rsrc_prop_data': rpd_db_obj}) + res_obj2 = resource_objects.Resource().create( + self.stack.context, + {'stack_id': self.stack.id, + 'uuid': str(uuid.uuid4()), + 'rsrc_prop_data_id': rpd_db_obj.id}) + ctx2 = utils.dummy_context() + res_obj1 = resource_objects.Resource().get_obj( + ctx2, res_obj1.id) + res_obj2 = resource_objects.Resource().get_obj( + ctx2, res_obj2.id) + + # verify the resource_properties_data association + # can be set by id or object + self.assertEqual(rpd_db_obj.id, res_obj1.rsrc_prop_data.id) + self.assertEqual(res_obj1.rsrc_prop_data.id, + res_obj2.rsrc_prop_data.id) + # properties data appears unencrypted to resource object + self.assertEqual(data, res_obj1.rsrc_prop_data.data) + self.assertEqual(data, res_obj2.rsrc_prop_data.data) + def test_store_or_update(self): tmpl = rsrc_defn.ResourceDefinition('test_resource', 'Foo') res = generic_rsrc.GenericResource('test_res_upd', tmpl, self.stack)