From aada048b165c2ca70a986b4adf1a635b05025992 Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Tue, 25 Jun 2013 16:07:23 +0800 Subject: [PATCH] Add validation of available_zone during instance create When using nova boot command with --availability_zone parameter and only input the zone without host and node, now will check if the zone is available, if not, then return an error message directly rather than create an instance with an Error state Add a get_only_available option to function get_availability_zones To disable available zone, one should have to disable both the scheduler filter and the API extension, if the API extension is not disabled, this patch will still do the validation of available zone DocImpact Change-Id: Ibff117d48b60ac4f4ae33e3b3d2840d7313ce0d4 --- nova/availability_zones.py | 31 ++++++++++++------- nova/compute/api.py | 18 ++++++++--- .../plugins/v3/test_availability_zone.py | 8 +++++ .../api/openstack/compute/test_servers.py | 14 +++++++++ nova/tests/test_availability_zones.py | 11 +++++-- 5 files changed, 64 insertions(+), 18 deletions(-) diff --git a/nova/availability_zones.py b/nova/availability_zones.py index 216c4604114a..d627fa0d79d1 100644 --- a/nova/availability_zones.py +++ b/nova/availability_zones.py @@ -94,12 +94,16 @@ def get_host_availability_zone(context, host, conductor_api=None): return az -def get_availability_zones(context): - """Return available and unavailable zones.""" +def get_availability_zones(context, get_only_available=False): + """Return available and unavailable zones on demands. + + :param get_only_available: flag to determine whether to return + available zones only, default False indicates return both + available zones and not available zones, True indicates return + available zones only + """ enabled_services = db.service_get_all(context, False) - disabled_services = db.service_get_all(context, True) enabled_services = set_availability_zones(context, enabled_services) - disabled_services = set_availability_zones(context, disabled_services) available_zones = [] for zone in [service['availability_zone'] for service @@ -107,13 +111,18 @@ def get_availability_zones(context): if zone not in available_zones: available_zones.append(zone) - not_available_zones = [] - zones = [service['availability_zone'] for service in disabled_services - if service['availability_zone'] not in available_zones] - for zone in zones: - if zone not in not_available_zones: - not_available_zones.append(zone) - return (available_zones, not_available_zones) + if not get_only_available: + disabled_services = db.service_get_all(context, True) + disabled_services = set_availability_zones(context, disabled_services) + not_available_zones = [] + zones = [service['availability_zone'] for service in disabled_services + if service['availability_zone'] not in available_zones] + for zone in zones: + if zone not in not_available_zones: + not_available_zones.append(zone) + return (available_zones, not_available_zones) + else: + return available_zones def get_instance_availability_zone(context, instance): diff --git a/nova/compute/api.py b/nova/compute/api.py index 7230c8a9b8e8..bddd3c4a4e50 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -598,8 +598,8 @@ class API(base.Base): max_count, display_name, display_description, key_name, key_data, security_groups, - availability_zone, user_data, - metadata, injected_files, + availability_zone, forced_host, + user_data, metadata, injected_files, access_ip_v4, access_ip_v6, requested_networks, config_drive, block_device_mapping, @@ -612,6 +612,13 @@ class API(base.Base): msg = _('Cannot attach one or more volumes to multiple' ' instances') raise exception.InvalidRequest(msg) + if availability_zone: + available_zones = availability_zones.\ + get_availability_zones(context.elevated(), True) + if forced_host is None and availability_zone not in \ + available_zones: + msg = _('The requested availability zone is not available') + raise exception.InvalidRequest(msg) if instance_type['disabled']: raise exception.InstanceTypeNotFound( instance_type_id=instance_type['id']) @@ -799,9 +806,10 @@ class API(base.Base): instance_type, boot_meta, image_href, image_id, kernel_id, ramdisk_id, min_count, max_count, display_name, display_description, key_name, key_data, security_groups, - availability_zone, user_data, metadata, injected_files, - access_ip_v4, access_ip_v6, requested_networks, config_drive, - block_device_mapping, auto_disk_config, reservation_id) + availability_zone, forced_host, user_data, metadata, + injected_files, access_ip_v4, access_ip_v6, requested_networks, + config_drive, block_device_mapping, auto_disk_config, + reservation_id) instances = self._provision_instances(context, instance_type, min_count, max_count, base_options, boot_meta, security_groups, diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_availability_zone.py b/nova/tests/api/openstack/compute/plugins/v3/test_availability_zone.py index 3626d781bef1..4861674a4bce 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_availability_zone.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_availability_zone.py @@ -447,6 +447,14 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = jsonutils.dumps(body) req.headers["content-type"] = "application/json" + admin_context = context.get_admin_context() + service1 = db.service_create(admin_context, {'host': 'host1_zones', + 'binary': "nova-compute", + 'topic': 'compute', + 'report_count': 0}) + agg = db.aggregate_create(admin_context, + {'name': 'agg1'}, {'availability_zone': 'nova'}) + db.aggregate_host_add(admin_context, agg['id'], 'host1_zones') res = self.controller.create(req, body).obj server = res['server'] self.assertEqual(FAKE_UUID, server['id']) diff --git a/nova/tests/api/openstack/compute/test_servers.py b/nova/tests/api/openstack/compute/test_servers.py index bde3a521ad19..86891853e18c 100644 --- a/nova/tests/api/openstack/compute/test_servers.py +++ b/nova/tests/api/openstack/compute/test_servers.py @@ -2716,6 +2716,20 @@ class ServersControllerCreateTest(test.TestCase): return old_create(*args, **kwargs) self.stubs.Set(compute_api.API, 'create', create) + + try: + self._test_create_extra(params) + except webob.exc.HTTPBadRequest as e: + expected = 'The requested availability zone is not available' + self.assertEquals(e.explanation, expected) + admin_context = context.get_admin_context() + service1 = db.service_create(admin_context, {'host': 'host1_zones', + 'binary': "nova-compute", + 'topic': 'compute', + 'report_count': 0}) + agg = db.aggregate_create(admin_context, + {'name': 'agg1'}, {'availability_zone': availability_zone}) + db.aggregate_host_add(admin_context, agg['id'], 'host1_zones') self._test_create_extra(params) def test_create_instance_with_availability_zone_disabled(self): diff --git a/nova/tests/test_availability_zones.py b/nova/tests/test_availability_zones.py index 0c58cd08aed0..7b29616ff7b0 100644 --- a/nova/tests/test_availability_zones.py +++ b/nova/tests/test_availability_zones.py @@ -157,8 +157,11 @@ class AvailabilityZoneTestCases(test.TestCase): def test_get_availability_zones(self): """Test get_availability_zones.""" - # get_availability_zones returns two lists, zones with at least one - # enabled services, and zones with no enabled services. + # When the param get_only_available of get_availability_zones is set + # to default False, it returns two lists, zones with at least one + # enabled services, and zones with no enabled services, + # when get_only_available is set to True, only return a list of zones + # with at least one enabled servies. # Use the following test data: # # zone host enabled @@ -192,6 +195,10 @@ class AvailabilityZoneTestCases(test.TestCase): self.assertEquals(zones, ['nova-test', 'nova-test2']) self.assertEquals(not_zones, ['nova-test3', 'nova']) + zones = az.get_availability_zones(self.context, True) + + self.assertEquals(zones, ['nova-test', 'nova-test2']) + def test_get_instance_availability_zone_default_value(self): """Test get right availability zone by given an instance.""" fake_inst_id = 162