Do not try to save event resource if too big for db column

Change-Id: Idedcfb5bf6678cd24990d1503c223ea9a9ac41cf
Closes-Bug: #1552431
This commit is contained in:
Crag Wolfe 2015-12-17 23:28:15 -05:00
parent f87c59b9c2
commit b48452fc9a
2 changed files with 96 additions and 33 deletions

View File

@ -11,15 +11,21 @@
# License for the specific language governing permissions and limitations
# under the License.
import pickle
import six
import oslo_db.exception
from oslo_log import log as logging
from heat.common import exception
from heat.common.i18n import _
from heat.common import identifier
from heat.objects import event as event_object
LOG = logging.getLogger(__name__)
MAX_EVENT_RESOURCE_PROPERTIES_SIZE = (1 << 16) - 1
class Event(object):
"""Class representing a Resource state change."""
@ -86,22 +92,38 @@ class Event(object):
if self.timestamp is not None:
ev['created_at'] = self.timestamp
# Workaround: we don't want to attempt to store the
# event.resource_properties column if the data is too large
# (greater than permitted by BLOB). Otherwise, we end up with
# an unsightly log message.
rp_size = len(pickle.dumps(ev['resource_properties']))
if rp_size > MAX_EVENT_RESOURCE_PROPERTIES_SIZE:
LOG.debug('event\'s resource_properties too large to store at '
'%d bytes', rp_size)
# Try truncating the largest value and see if that gets us under
# the db column's size constraint.
max_key, max_val = max(ev['resource_properties'].items(),
key=lambda i: len(repr(i[1])))
err = 'Resource properties are too large to store fully'
ev['resource_properties'].update({'Error': err})
ev['resource_properties'][max_key] = '<Deleted, too large>'
rp_size = len(pickle.dumps(ev['resource_properties']))
if rp_size > MAX_EVENT_RESOURCE_PROPERTIES_SIZE:
LOG.debug('event\'s resource_properties STILL too large '
'after truncating largest key at %d bytes', rp_size)
err = 'Resource properties are too large to attempt to store'
ev['resource_properties'] = {'Error': err}
# We should have worked around the issue, but let's be extra
# careful.
try:
new_ev = event_object.Event.create(self.context, ev)
except oslo_db.exception.DBError:
# Attempt do drop the largest key and re-store as we expect
# This to mostly happen with one large config blob property
max_key, max_val = max(ev['resource_properties'].items(),
key=lambda i: len(repr(i[1])))
# Give up and drop all properties..
err = 'Resource properties are too large to store'
ev['resource_properties'].update({'Error': err})
ev['resource_properties'][max_key] = '<Deleted, too large>'
try:
new_ev = event_object.Event.create(self.context, ev)
except oslo_db.exception.DBError:
# Give up and drop all properties..
ev['resource_properties'] = {'Error': err}
new_ev = event_object.Event.create(self.context, ev)
ev['resource_properties'] = {'Error': err}
new_ev = event_object.Event.create(self.context, ev)
self.id = new_ev.id
self.timestamp = new_ev.created_at
self.uuid = new_ev.uuid

View File

@ -52,6 +52,30 @@ tmpl_multiple = {
}
}
tmpl_multiple_too_large = {
'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {
'EventTestResource': {
'Type': 'ResourceWithMultipleRequiredProps',
'Properties': {'Foo1': 'zoo',
'Foo2': 'A' * (1 << 16),
'Foo3': '99999'}
}
}
}
tmpl_multiple_srsly_too_large = {
'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {
'EventTestResource': {
'Type': 'ResourceWithMultipleRequiredProps',
'Properties': {'Foo1': 'Z' * (1 << 16),
'Foo2': 'A' * (1 << 16),
'Foo3': '99999'}
}
}
}
class EventCommon(common.HeatTestCase):
@ -226,13 +250,13 @@ class EventTest(EventCommon):
self.assertEqual(expected, e.as_dict())
class EventTestProps(EventCommon):
class EventTestSingleLargeProp(EventCommon):
def setUp(self):
super(EventTestProps, self).setUp()
self._setup_stack(tmpl_multiple)
super(EventTestSingleLargeProp, self).setUp()
self._setup_stack(tmpl_multiple_too_large)
def test_store_fail_all_props(self):
def test_too_large_single_prop(self):
self.resource.resource_id_set('resource_physical_id')
e = event.Event(self.ctx, self.stack, 'TEST', 'IN_PROGRESS', 'Testing',
@ -242,22 +266,42 @@ class EventTestProps(EventCommon):
self.assertIsNotNone(e.id)
ev = event_object.Event.get_by_id(self.ctx, e.id)
errors = [oslo_db.exception.DBError, oslo_db.exception.DBError]
self.assertEqual(
{'Foo1': 'zoo',
'Foo2': '<Deleted, too large>',
'Foo3': '99999',
'Error': 'Resource properties are too large to store fully'},
ev['resource_properties'])
def side_effect(*args):
try:
raise errors.pop()
except IndexError:
self.assertEqual(
{'Error': 'Resource properties are too large to store'},
args[1]['resource_properties'])
return ev
with mock.patch("heat.objects.event.Event") as mock_event:
mock_event.create.side_effect = side_effect
e.store()
class EventTestMultipleLargeProp(EventCommon):
def test_store_fail_one_prop(self):
def setUp(self):
super(EventTestMultipleLargeProp, self).setUp()
self._setup_stack(tmpl_multiple_srsly_too_large)
def test_too_large_multiple_prop(self):
self.resource.resource_id_set('resource_physical_id')
e = event.Event(self.ctx, self.stack, 'TEST', 'IN_PROGRESS', 'Testing',
'alabama', self.resource.properties,
self.resource.name, self.resource.type())
e.store()
self.assertIsNotNone(e.id)
ev = event_object.Event.get_by_id(self.ctx, e.id)
self.assertEqual(
{'Error': 'Resource properties are too large to attempt to store'},
ev['resource_properties'])
class EventTestStoreProps(EventCommon):
def setUp(self):
super(EventTestStoreProps, self).setUp()
self._setup_stack(tmpl_multiple)
def test_store_fail_all_props(self):
self.resource.resource_id_set('resource_physical_id')
e = event.Event(self.ctx, self.stack, 'TEST', 'IN_PROGRESS', 'Testing',
@ -274,10 +318,7 @@ class EventTestProps(EventCommon):
raise errors.pop()
except IndexError:
self.assertEqual(
{'Foo1': 'zoo',
'Foo2': '<Deleted, too large>',
'Foo3': '99999',
'Error': 'Resource properties are too large to store'},
{'Error': 'Resource properties are too large to store'},
args[1]['resource_properties'])
return ev