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
This commit is contained in:
He Jie Xu
2013-06-25 16:07:23 +08:00
committed by LeiDuan
parent 536f37906d
commit aada048b16
5 changed files with 64 additions and 18 deletions

View File

@@ -94,12 +94,16 @@ def get_host_availability_zone(context, host, conductor_api=None):
return az return az
def get_availability_zones(context): def get_availability_zones(context, get_only_available=False):
"""Return available and unavailable zones.""" """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) enabled_services = db.service_get_all(context, False)
disabled_services = db.service_get_all(context, True)
enabled_services = set_availability_zones(context, enabled_services) enabled_services = set_availability_zones(context, enabled_services)
disabled_services = set_availability_zones(context, disabled_services)
available_zones = [] available_zones = []
for zone in [service['availability_zone'] for service for zone in [service['availability_zone'] for service
@@ -107,13 +111,18 @@ def get_availability_zones(context):
if zone not in available_zones: if zone not in available_zones:
available_zones.append(zone) available_zones.append(zone)
not_available_zones = [] if not get_only_available:
zones = [service['availability_zone'] for service in disabled_services disabled_services = db.service_get_all(context, True)
if service['availability_zone'] not in available_zones] disabled_services = set_availability_zones(context, disabled_services)
for zone in zones: not_available_zones = []
if zone not in not_available_zones: zones = [service['availability_zone'] for service in disabled_services
not_available_zones.append(zone) if service['availability_zone'] not in available_zones]
return (available_zones, not_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): def get_instance_availability_zone(context, instance):

View File

@@ -598,8 +598,8 @@ class API(base.Base):
max_count, display_name, max_count, display_name,
display_description, key_name, display_description, key_name,
key_data, security_groups, key_data, security_groups,
availability_zone, user_data, availability_zone, forced_host,
metadata, injected_files, user_data, metadata, injected_files,
access_ip_v4, access_ip_v6, access_ip_v4, access_ip_v6,
requested_networks, config_drive, requested_networks, config_drive,
block_device_mapping, block_device_mapping,
@@ -612,6 +612,13 @@ class API(base.Base):
msg = _('Cannot attach one or more volumes to multiple' msg = _('Cannot attach one or more volumes to multiple'
' instances') ' instances')
raise exception.InvalidRequest(msg) 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']: if instance_type['disabled']:
raise exception.InstanceTypeNotFound( raise exception.InstanceTypeNotFound(
instance_type_id=instance_type['id']) instance_type_id=instance_type['id'])
@@ -799,9 +806,10 @@ class API(base.Base):
instance_type, boot_meta, image_href, image_id, kernel_id, instance_type, boot_meta, image_href, image_id, kernel_id,
ramdisk_id, min_count, max_count, display_name, ramdisk_id, min_count, max_count, display_name,
display_description, key_name, key_data, security_groups, display_description, key_name, key_data, security_groups,
availability_zone, user_data, metadata, injected_files, availability_zone, forced_host, user_data, metadata,
access_ip_v4, access_ip_v6, requested_networks, config_drive, injected_files, access_ip_v4, access_ip_v6, requested_networks,
block_device_mapping, auto_disk_config, reservation_id) config_drive, block_device_mapping, auto_disk_config,
reservation_id)
instances = self._provision_instances(context, instance_type, instances = self._provision_instances(context, instance_type,
min_count, max_count, base_options, boot_meta, security_groups, min_count, max_count, base_options, boot_meta, security_groups,

View File

@@ -447,6 +447,14 @@ class ServersControllerCreateTest(test.TestCase):
req.method = 'POST' req.method = 'POST'
req.body = jsonutils.dumps(body) req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json" 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 res = self.controller.create(req, body).obj
server = res['server'] server = res['server']
self.assertEqual(FAKE_UUID, server['id']) self.assertEqual(FAKE_UUID, server['id'])

View File

@@ -2716,6 +2716,20 @@ class ServersControllerCreateTest(test.TestCase):
return old_create(*args, **kwargs) return old_create(*args, **kwargs)
self.stubs.Set(compute_api.API, 'create', create) 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) self._test_create_extra(params)
def test_create_instance_with_availability_zone_disabled(self): def test_create_instance_with_availability_zone_disabled(self):

View File

@@ -157,8 +157,11 @@ class AvailabilityZoneTestCases(test.TestCase):
def test_get_availability_zones(self): def test_get_availability_zones(self):
"""Test get_availability_zones.""" """Test get_availability_zones."""
# get_availability_zones returns two lists, zones with at least one # When the param get_only_available of get_availability_zones is set
# enabled services, and zones with no enabled services. # 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: # Use the following test data:
# #
# zone host enabled # zone host enabled
@@ -192,6 +195,10 @@ class AvailabilityZoneTestCases(test.TestCase):
self.assertEquals(zones, ['nova-test', 'nova-test2']) self.assertEquals(zones, ['nova-test', 'nova-test2'])
self.assertEquals(not_zones, ['nova-test3', 'nova']) 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): def test_get_instance_availability_zone_default_value(self):
"""Test get right availability zone by given an instance.""" """Test get right availability zone by given an instance."""
fake_inst_id = 162 fake_inst_id = 162