diff --git a/nova/objects/instance.py b/nova/objects/instance.py index c0f0cd5062f8..1810c12a94d7 100644 --- a/nova/objects/instance.py +++ b/nova/objects/instance.py @@ -320,17 +320,32 @@ class Instance(base.NovaPersistentObject, base.NovaObject, def _flavor_from_db(self, db_flavor): """Load instance flavor information from instance_extra.""" + # Before we stored flavors in instance_extra, certain fields, defined + # in nova.compute.flavors.system_metadata_flavor_props, were stored + # in the instance.system_metadata for the embedded instance.flavor. + # The "disabled" field wasn't one of those keys, however, so really + # old instances that had their embedded flavor converted to the + # serialized instance_extra form won't have the disabled attribute + # set and we need to default those here so callers don't explode trying + # to load instance.flavor.disabled. + def _default_disabled(flavor): + if 'disabled' not in flavor: + flavor.disabled = False + flavor_info = jsonutils.loads(db_flavor) self.flavor = objects.Flavor.obj_from_primitive(flavor_info['cur']) + _default_disabled(self.flavor) if flavor_info['old']: self.old_flavor = objects.Flavor.obj_from_primitive( flavor_info['old']) + _default_disabled(self.old_flavor) else: self.old_flavor = None if flavor_info['new']: self.new_flavor = objects.Flavor.obj_from_primitive( flavor_info['new']) + _default_disabled(self.new_flavor) else: self.new_flavor = None self.obj_reset_changes(['flavor', 'old_flavor', 'new_flavor']) diff --git a/nova/tests/unit/objects/test_instance.py b/nova/tests/unit/objects/test_instance.py index cb7df80a93bd..3ea396694745 100644 --- a/nova/tests/unit/objects/test_instance.py +++ b/nova/tests/unit/objects/test_instance.py @@ -350,6 +350,23 @@ class _TestInstanceObject(object): inst.key_name, localonly=True) + @mock.patch.object(db, 'instance_get_by_uuid') + def test_lazy_load_flavor_from_extra(self, mock_get): + inst = objects.Instance(context=self.context, uuid=uuids.instance) + # These disabled values are only here for logic testing purposes to + # make sure we default the "new" flavor's disabled value to False on + # load from the database. + fake_flavor = jsonutils.dumps( + {'cur': objects.Flavor(disabled=False).obj_to_primitive(), + 'old': objects.Flavor(disabled=True).obj_to_primitive(), + 'new': objects.Flavor().obj_to_primitive()}) + fake_inst = dict(self.fake_instance, extra={'flavor': fake_flavor}) + mock_get.return_value = fake_inst + # Assert the disabled values on the flavors. + self.assertFalse(inst.flavor.disabled) + self.assertTrue(inst.old_flavor.disabled) + self.assertFalse(inst.new_flavor.disabled) + @mock.patch.object(db, 'instance_get_by_uuid') def test_get_remote(self, mock_get): # isotime doesn't have microseconds and is always UTC