Merge "Fix Cloud Server image/flavor combination validation"
This commit is contained in:
commit
8a276a4483
@ -68,6 +68,7 @@ class CloudServer(server.Server):
|
|||||||
'general1', 'memory1', 'performance2', 'performance1',
|
'general1', 'memory1', 'performance2', 'performance1',
|
||||||
'standard1', 'io1', 'onmetal', 'compute1',
|
'standard1', 'io1', 'onmetal', 'compute1',
|
||||||
)
|
)
|
||||||
|
BASE_IMAGE_REF = 'base_image_ref'
|
||||||
|
|
||||||
# flavor classes that can be booted ONLY from volume
|
# flavor classes that can be booted ONLY from volume
|
||||||
BFV_VOLUME_REQUIRED = {MEMORY1, COMPUTE1}
|
BFV_VOLUME_REQUIRED = {MEMORY1, COMPUTE1}
|
||||||
@ -239,8 +240,22 @@ class CloudServer(server.Server):
|
|||||||
|
|
||||||
return self._extend_networks(nets)
|
return self._extend_networks(nets)
|
||||||
|
|
||||||
def _image_flavor_class_match(self, flavor_type, image_obj):
|
def _base_image_obj(self, image):
|
||||||
flavor_class_string = image_obj.get(self.FLAVOR_CLASSES_KEY, '')
|
image_obj = self.client_plugin('glance').get_image(image)
|
||||||
|
if self.BASE_IMAGE_REF in image_obj:
|
||||||
|
base_image = image_obj[self.BASE_IMAGE_REF]
|
||||||
|
return self.client_plugin('glance').get_image(base_image)
|
||||||
|
return image_obj
|
||||||
|
|
||||||
|
def _image_flavor_class_match(self, flavor_type, image):
|
||||||
|
base_image_obj = self._base_image_obj(image)
|
||||||
|
flavor_class_string = base_image_obj.get(self.FLAVOR_CLASSES_KEY)
|
||||||
|
|
||||||
|
# If the flavor_class_string metadata does not exist or is
|
||||||
|
# empty, do not validate image/flavor combo
|
||||||
|
if not flavor_class_string:
|
||||||
|
return True
|
||||||
|
|
||||||
flavor_class_excluded = "!{0}".format(flavor_type)
|
flavor_class_excluded = "!{0}".format(flavor_type)
|
||||||
flavor_classes_accepted = flavor_class_string.split(',')
|
flavor_classes_accepted = flavor_class_string.split(',')
|
||||||
|
|
||||||
@ -273,9 +288,7 @@ class CloudServer(server.Server):
|
|||||||
# is all the validation possible
|
# is all the validation possible
|
||||||
return
|
return
|
||||||
|
|
||||||
image_obj = self.client_plugin('glance').get_image(image)
|
if not self._image_flavor_class_match(flavor_type, image):
|
||||||
|
|
||||||
if not self._image_flavor_class_match(flavor_type, image_obj):
|
|
||||||
msg = _('Flavor %(flavor)s cannot be used with image '
|
msg = _('Flavor %(flavor)s cannot be used with image '
|
||||||
'%(image)s.') % {'image': image, 'flavor': flavor}
|
'%(image)s.') % {'image': image, 'flavor': flavor}
|
||||||
raise exception.StackValidationFailed(message=msg)
|
raise exception.StackValidationFailed(message=msg)
|
||||||
|
@ -545,6 +545,7 @@ class CloudServersValidationTests(common.HeatTestCase):
|
|||||||
|
|
||||||
mock_image = mock.Mock(status='ACTIVE', min_ram=2, min_disk=1)
|
mock_image = mock.Mock(status='ACTIVE', min_ram=2, min_disk=1)
|
||||||
mock_image.get.return_value = "memory1"
|
mock_image.get.return_value = "memory1"
|
||||||
|
mock_image.__iter__ = mock.Mock(return_value=iter([]))
|
||||||
|
|
||||||
mock_plugin().get_flavor.return_value = mock_flavor
|
mock_plugin().get_flavor.return_value = mock_flavor
|
||||||
mock_plugin().get_image.return_value = mock_image
|
mock_plugin().get_image.return_value = mock_image
|
||||||
@ -564,6 +565,7 @@ class CloudServersValidationTests(common.HeatTestCase):
|
|||||||
|
|
||||||
mock_image = mock.Mock(status='ACTIVE', min_ram=2, min_disk=1)
|
mock_image = mock.Mock(status='ACTIVE', min_ram=2, min_disk=1)
|
||||||
mock_image.get.return_value = "!standard1, *"
|
mock_image.get.return_value = "!standard1, *"
|
||||||
|
mock_image.__iter__ = mock.Mock(return_value=iter([]))
|
||||||
|
|
||||||
mock_flavor = mock.Mock(ram=4, disk=4)
|
mock_flavor = mock.Mock(ram=4, disk=4)
|
||||||
mock_flavor.to_dict.return_value = {
|
mock_flavor.to_dict.return_value = {
|
||||||
@ -588,6 +590,7 @@ class CloudServersValidationTests(common.HeatTestCase):
|
|||||||
|
|
||||||
mock_image = mock.Mock(size=1, status='ACTIVE', min_ram=2, min_disk=2)
|
mock_image = mock.Mock(size=1, status='ACTIVE', min_ram=2, min_disk=2)
|
||||||
mock_image.get.return_value = "standard1"
|
mock_image.get.return_value = "standard1"
|
||||||
|
mock_image.__iter__ = mock.Mock(return_value=iter([]))
|
||||||
|
|
||||||
mock_flavor = mock.Mock(ram=4, disk=4)
|
mock_flavor = mock.Mock(ram=4, disk=4)
|
||||||
mock_flavor.to_dict.return_value = {
|
mock_flavor.to_dict.return_value = {
|
||||||
@ -601,3 +604,60 @@ class CloudServersValidationTests(common.HeatTestCase):
|
|||||||
mock_plugin().get_image.return_value = mock_image
|
mock_plugin().get_image.return_value = mock_image
|
||||||
|
|
||||||
self.assertIsNone(server.validate())
|
self.assertIsNone(server.validate())
|
||||||
|
|
||||||
|
def test_validate_image_flavor_empty_metadata(self, mock_client,
|
||||||
|
mock_plugin):
|
||||||
|
server = cloud_server.CloudServer("test", self.rsrcdef, self.mockstack)
|
||||||
|
|
||||||
|
mock_image = mock.Mock(size=1, status='ACTIVE', min_ram=2, min_disk=2)
|
||||||
|
mock_image.get.return_value = ""
|
||||||
|
mock_image.__iter__ = mock.Mock(return_value=iter([]))
|
||||||
|
|
||||||
|
mock_flavor = mock.Mock(ram=4, disk=4)
|
||||||
|
mock_flavor.to_dict.return_value = {
|
||||||
|
'OS-FLV-WITH-EXT-SPECS:extra_specs': {
|
||||||
|
'flavor_classes': '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_plugin().get_flavor.return_value = mock_flavor
|
||||||
|
mock_plugin().get_image.return_value = mock_image
|
||||||
|
|
||||||
|
self.assertIsNone(server.validate())
|
||||||
|
|
||||||
|
def test_validate_image_flavor_no_metadata(self, mock_client, mock_plugin):
|
||||||
|
server = cloud_server.CloudServer("test", self.rsrcdef, self.mockstack)
|
||||||
|
|
||||||
|
mock_image = mock.Mock(size=1, status='ACTIVE', min_ram=2, min_disk=2)
|
||||||
|
mock_image.get.return_value = None
|
||||||
|
mock_image.__iter__ = mock.Mock(return_value=iter([]))
|
||||||
|
|
||||||
|
mock_flavor = mock.Mock(ram=4, disk=4)
|
||||||
|
mock_flavor.to_dict.return_value = {}
|
||||||
|
|
||||||
|
mock_plugin().get_flavor.return_value = mock_flavor
|
||||||
|
mock_plugin().get_image.return_value = mock_image
|
||||||
|
|
||||||
|
self.assertIsNone(server.validate())
|
||||||
|
|
||||||
|
def test_validate_image_flavor_not_base(self, mock_client, mock_plugin):
|
||||||
|
server = cloud_server.CloudServer("test", self.rsrcdef, self.mockstack)
|
||||||
|
|
||||||
|
mock_image = mock.Mock(size=1, status='ACTIVE', min_ram=2, min_disk=2)
|
||||||
|
mock_image.get.return_value = None
|
||||||
|
mock_image.__iter__ = mock.Mock(return_value=iter(
|
||||||
|
['base_image_ref']))
|
||||||
|
mock_image.__getitem__ = mock.Mock(return_value='1234')
|
||||||
|
|
||||||
|
mock_base_image = mock.Mock(size=1, status='ACTIVE', min_ram=2,
|
||||||
|
min_disk=2)
|
||||||
|
mock_base_image.get.return_value = None
|
||||||
|
mock_base_image.__iter__ = mock.Mock(return_value=iter([]))
|
||||||
|
|
||||||
|
mock_flavor = mock.Mock(ram=4, disk=4)
|
||||||
|
mock_flavor.to_dict.return_value = {}
|
||||||
|
|
||||||
|
mock_plugin().get_flavor.return_value = mock_flavor
|
||||||
|
mock_plugin().get_image.side_effect = [mock_image, mock_base_image]
|
||||||
|
|
||||||
|
self.assertIsNone(server.validate())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user