Created
August 26, 2016 18:54
-
-
Save mgagne/3d6d7596537ac6da4f9195a54df12909 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From: =?UTF-8?q?Mathieu=20Gagn=C3=A9?= <mgagne@iweb.com> | |
Date: Mon, 11 Apr 2016 22:09:41 -0400 | |
Subject: Add ability to restrict image to specific flavor classes | |
--- | |
nova/compute/api.py | 28 ++++++++++ | |
nova/tests/unit/compute/test_compute.py | 88 +++++++++++++++++++++++++++++++ | |
2 files changed, 116 insertions(+) | |
diff --git a/nova/compute/api.py b/nova/compute/api.py | |
index f48d027..2598396 100644 | |
--- a/nova/compute/api.py | |
+++ b/nova/compute/api.py | |
@@ -652,6 +652,26 @@ class API(base.Base): | |
# reason, we rely on the DB to cast True to a String. | |
return True if bool_val else '' | |
+ def _validate_image_flavor_classes(self, image_flavor_classes, | |
+ instance_type_class): | |
+ # Following cases are supported: | |
+ # - *: All flavor classes | |
+ # - *,!virtual: All flavor classes but "virtual" | |
+ # - virtual: "virtual" flavor classes only | |
+ flavor_classes = image_flavor_classes.split(',') | |
+ passed = (flavor_classes[0] == '*') | |
+ for flavor_class in flavor_classes: | |
+ negative = False | |
+ if flavor_class.startswith('!'): | |
+ negative = True | |
+ flavor_class = flavor_class[1:] | |
+ if instance_type_class == flavor_class: | |
+ # Negative operator are absolute | |
+ if negative: | |
+ return False | |
+ passed |= True | |
+ return passed | |
+ | |
def _check_requested_image(self, context, image_id, image, | |
instance_type, root_bdm): | |
if not image: | |
@@ -660,7 +680,15 @@ class API(base.Base): | |
if image['status'] != 'active': | |
raise exception.ImageNotActive(image_id=image_id) | |
+ # NOTE(mgagne) Restrict image usage to flavor classes (if applicable) | |
image_properties = image.get('properties', {}) | |
+ image_flavor_classes = image_properties.get('flavor_classes', '*') | |
+ instance_type_class = instance_type.get('extra_specs', {}).get('class') | |
+ if not self._validate_image_flavor_classes(image_flavor_classes, | |
+ instance_type_class): | |
+ msg = "Image cannot be used with provided flavor." | |
+ raise exception.InvalidRequest(msg) | |
+ | |
config_drive_option = image_properties.get( | |
'img_config_drive', 'optional') | |
if config_drive_option not in ['optional', 'mandatory']: | |
diff --git a/nova/tests/unit/compute/test_compute.py b/nova/tests/unit/compute/test_compute.py | |
index 3549338..2a4d163 100644 | |
--- a/nova/tests/unit/compute/test_compute.py | |
+++ b/nova/tests/unit/compute/test_compute.py | |
@@ -7354,6 +7354,94 @@ class ComputeAPITestCase(BaseTestCase): | |
self.assertIsNone(instance['task_state']) | |
return instance, instance_uuid | |
+ def test_create_with_image_flavor_classes_any(self): | |
+ # Test an image with no flavor class restrictions. | |
+ | |
+ inst_type = flavors.get_default_flavor() | |
+ inst_type['extra_specs'] = {'class': 'virtual'} | |
+ | |
+ self.fake_image['properties'] = {'flavor_classes': '*'} | |
+ self.stubs.Set(fake_image._FakeImageService, 'show', self.fake_show) | |
+ | |
+ (refs, resv_id) = self.compute_api.create(self.context, | |
+ inst_type, self.fake_image['id']) | |
+ | |
+ def test_create_with_image_flavor_classes_only_one(self): | |
+ # Test an image with no flavor class restrictions. | |
+ | |
+ inst_type = flavors.get_default_flavor() | |
+ inst_type['extra_specs'] = {'class': 'virtual'} | |
+ | |
+ self.fake_image['properties'] = {'flavor_classes': 'metal'} | |
+ self.stubs.Set(fake_image._FakeImageService, 'show', self.fake_show) | |
+ | |
+ # Negative test | |
+ self.assertRaises(exception.InvalidRequest, | |
+ self.compute_api.create, self.context, | |
+ inst_type, self.fake_image['id']) | |
+ | |
+ # Positive test | |
+ inst_type['extra_specs'] = {'class': 'metal'} | |
+ (refs, resv_id) = self.compute_api.create(self.context, | |
+ inst_type, self.fake_image['id']) | |
+ | |
+ def test_create_with_image_flavor_classes_only_two(self): | |
+ # Test an image with no flavor class restrictions. | |
+ | |
+ inst_type = flavors.get_default_flavor() | |
+ inst_type['extra_specs'] = {'class': 'container'} | |
+ | |
+ self.fake_image['properties'] = {'flavor_classes': 'virtual,metal'} | |
+ self.stubs.Set(fake_image._FakeImageService, 'show', self.fake_show) | |
+ | |
+ # Negative test | |
+ self.assertRaises(exception.InvalidRequest, | |
+ self.compute_api.create, self.context, | |
+ inst_type, self.fake_image['id']) | |
+ | |
+ # Positive test | |
+ inst_type['extra_specs'] = {'class': 'metal'} | |
+ (refs, resv_id) = self.compute_api.create(self.context, | |
+ inst_type, self.fake_image['id']) | |
+ | |
+ def test_create_with_image_flavor_classes_all_but_one(self): | |
+ # Test an image with no flavor class restrictions. | |
+ | |
+ inst_type = flavors.get_default_flavor() | |
+ inst_type['extra_specs'] = {'class': 'virtual'} | |
+ | |
+ self.fake_image['properties'] = {'flavor_classes': '*,!virtual'} | |
+ self.stubs.Set(fake_image._FakeImageService, 'show', self.fake_show) | |
+ | |
+ # Negative test | |
+ self.assertRaises(exception.InvalidRequest, | |
+ self.compute_api.create, self.context, | |
+ inst_type, self.fake_image['id']) | |
+ | |
+ # Positive test | |
+ inst_type['extra_specs'] = {'class': 'metal'} | |
+ (refs, resv_id) = self.compute_api.create(self.context, | |
+ inst_type, self.fake_image['id']) | |
+ | |
+ def test_create_with_image_flavor_classes_all_but_two(self): | |
+ # Test an image with no flavor class restrictions. | |
+ | |
+ inst_type = flavors.get_default_flavor() | |
+ inst_type['extra_specs'] = {'class': 'metal'} | |
+ | |
+ self.fake_image['properties'] = {'flavor_classes': '*,!virtual,!metal'} | |
+ self.stubs.Set(fake_image._FakeImageService, 'show', self.fake_show) | |
+ | |
+ # Negative test | |
+ self.assertRaises(exception.InvalidRequest, | |
+ self.compute_api.create, self.context, | |
+ inst_type, self.fake_image['id']) | |
+ | |
+ # Positive test | |
+ inst_type['extra_specs'] = {'class': 'container'} | |
+ (refs, resv_id) = self.compute_api.create(self.context, | |
+ inst_type, self.fake_image['id']) | |
+ | |
def test_create_with_too_little_ram(self): | |
# Test an instance type with too little memory. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment