From 301cdf245c4021999828f83a81c471db4f511e1b Mon Sep 17 00:00:00 2001 From: Steve Leon Date: Fri, 10 May 2013 16:45:48 -0700 Subject: [PATCH] Ephemeral volume support This feature enables reddwarf to create instance and run mysql on ephemeral disk. To enable this feature, set the flag "reddwarf_volume_support to False and specify the device_path and mount_point variables. Also added int tests for ephemeral support fixes LP bug# 1175719 BP: https://blueprints.launchpad.net/reddwarf/+spec/ephemeral-storage-volume Change-Id: I869297e7da288ac42b359c8cdb731e8b7281d51b --- etc/tests/localhost.test.conf | 12 ++ reddwarf/common/exception.py | 20 +++ reddwarf/common/wsgi.py | 5 + reddwarf/instance/models.py | 47 ++++-- reddwarf/instance/service.py | 2 +- reddwarf/limits/views.py | 4 +- reddwarf/quota/quota.py | 3 +- reddwarf/taskmanager/models.py | 46 +++--- reddwarf/tests/api/backups.py | 2 +- reddwarf/tests/api/instances.py | 146 +++++++++++++----- reddwarf/tests/api/instances_actions.py | 32 +++- reddwarf/tests/api/instances_delete.py | 20 ++- reddwarf/tests/api/instances_mysql_down.py | 24 ++- reddwarf/tests/api/limits.py | 23 ++- reddwarf/tests/api/mgmt/accounts.py | 11 +- reddwarf/tests/api/mgmt/instances.py | 17 +- reddwarf/tests/api/mgmt/malformed_json.py | 9 +- reddwarf/tests/fakes/nova.py | 8 +- .../tests/unittests/api/common/test_limits.py | 28 ++-- reddwarf/tests/unittests/quota/test_quota.py | 19 ++- 20 files changed, 351 insertions(+), 127 deletions(-) diff --git a/etc/tests/localhost.test.conf b/etc/tests/localhost.test.conf index 0b36f72206..ac58cea8c3 100644 --- a/etc/tests/localhost.test.conf +++ b/etc/tests/localhost.test.conf @@ -101,6 +101,18 @@ "id": 10, "name": "m1.rd-tiny", "ram": 512 + }, + { + "id": 11, + "name": "eph.rd-tiny", + "ram": 512, + "local_storage": 1 + }, + { + "id": 12, + "name": "eph.rd-smaller", + "ram": 768, + "local_storage": 2 } ], diff --git a/reddwarf/common/exception.py b/reddwarf/common/exception.py index 2a98efc0b1..66ca52cc93 100644 --- a/reddwarf/common/exception.py +++ b/reddwarf/common/exception.py @@ -157,6 +157,26 @@ class VolumeCreationFailure(ReddwarfError): message = _("Failed to create a volume in Nova.") +class VolumeSizeNotSpecified(BadRequest): + + message = _("Volume size was not specified.") + + +class LocalStorageNotSpecified(BadRequest): + + message = _("Local storage not specified in flavor ID: %(flavor)s.") + + +class LocalStorageNotSupported(ReddwarfError): + + message = _("Local storage support is not enabled.") + + +class VolumeNotSupported(ReddwarfError): + + message = _("Volume support is not enabled.") + + class TaskManagerError(ReddwarfError): message = _("An error occurred communicating with the task manager: " diff --git a/reddwarf/common/wsgi.py b/reddwarf/common/wsgi.py index 5ddded093e..5ad3c3abe5 100644 --- a/reddwarf/common/wsgi.py +++ b/reddwarf/common/wsgi.py @@ -376,6 +376,7 @@ class Controller(object): exception.BadValue, exception.DatabaseAlreadyExists, exception.UserAlreadyExists, + exception.LocalStorageNotSpecified, ], webob.exc.HTTPNotFound: [ exception.NotFound, @@ -398,6 +399,10 @@ class Controller(object): exception.VolumeCreationFailure, exception.UpdateGuestError, ], + webob.exc.HTTPNotImplemented: [ + exception.VolumeNotSupported, + exception.LocalStorageNotSupported + ], } def create_resource(self): diff --git a/reddwarf/instance/models.py b/reddwarf/instance/models.py index b4e6dcfafc..ce7467dd09 100644 --- a/reddwarf/instance/models.py +++ b/reddwarf/instance/models.py @@ -68,6 +68,8 @@ class InstanceStatus(object): def validate_volume_size(size): + if size is None: + raise exception.VolumeSizeNotSpecified() max_size = CONF.max_accepted_volume_size if long(size) > max_size: msg = ("Volume 'size' cannot exceed maximum " @@ -336,9 +338,11 @@ class BaseInstance(SimpleInstance): self.update_db(task_status=InstanceTasks.DELETING) task_api.API(self.context).delete_instance(self.id) + deltas = {'instances': -1} + if CONF.reddwarf_volume_support: + deltas['volumes'] = -self.volume_size return run_with_quotas(self.tenant_id, - {'instances': -1, - 'volumes': -self.volume_size}, + deltas, _delete_resources) def _delete_resources(self): @@ -415,13 +419,25 @@ class Instance(BuiltInstance): def create(cls, context, name, flavor_id, image_id, databases, users, service_type, volume_size, backup_id): + client = create_nova_client(context) + try: + flavor = client.flavors.get(flavor_id) + except nova_exceptions.NotFound: + raise exception.FlavorNotFound(uuid=flavor_id) + + deltas = {'instances': 1} + if CONF.reddwarf_volume_support: + validate_volume_size(volume_size) + deltas['volumes'] = volume_size + else: + if volume_size is not None: + raise exception.VolumeNotSupported() + ephemeral_support = CONF.device_path + if ephemeral_support and flavor.ephemeral == 0: + raise exception.LocalStorageNotSpecified(flavor=flavor_id) + def _create_resources(): - client = create_nova_client(context) security_groups = None - try: - flavor = client.flavors.get(flavor_id) - except nova_exceptions.NotFound: - raise exception.FlavorNotFound(uuid=flavor_id) if backup_id is not None: backup_info = Backup.get_by_id(backup_id) @@ -464,9 +480,8 @@ class Instance(BuiltInstance): return SimpleInstance(context, db_info, service_status) - validate_volume_size(volume_size) return run_with_quotas(context.tenant, - {'instances': 1, 'volumes': volume_size}, + deltas, _create_resources) def resize_flavor(self, new_flavor_id): @@ -483,8 +498,18 @@ class Instance(BuiltInstance): old_flavor = client.flavors.get(self.flavor_id) new_flavor_size = new_flavor.ram old_flavor_size = old_flavor.ram - if new_flavor_size == old_flavor_size: - raise exception.CannotResizeToSameSize() + if CONF.reddwarf_volume_support: + if new_flavor.ephemeral != 0: + raise exception.LocalStorageNotSupported() + if new_flavor_size == old_flavor_size: + raise exception.CannotResizeToSameSize() + elif CONF.device_path is not None: + # ephemeral support enabled + if new_flavor.ephemeral == 0: + raise exception.LocalStorageNotSpecified(flavor=new_flavor_id) + if (new_flavor_size == old_flavor_size and + new_flavor.ephemeral == new_flavor.ephemeral): + raise exception.CannotResizeToSameSize() # Set the task to RESIZING and begin the async call before returning. self.update_db(task_status=InstanceTasks.RESIZING) diff --git a/reddwarf/instance/service.py b/reddwarf/instance/service.py index 48c0247f94..83b3030dda 100644 --- a/reddwarf/instance/service.py +++ b/reddwarf/instance/service.py @@ -196,7 +196,7 @@ class InstanceController(wsgi.Controller): users = populate_users(body['instance'].get('users', [])) except ValueError as ve: raise exception.BadRequest(msg=ve) - if body['instance'].get('volume', None) is not None: + if 'volume' in body['instance']: try: volume_size = int(body['instance']['volume']['size']) except ValueError as e: diff --git a/reddwarf/limits/views.py b/reddwarf/limits/views.py index 6423809c89..28dd63144a 100644 --- a/reddwarf/limits/views.py +++ b/reddwarf/limits/views.py @@ -50,8 +50,8 @@ class LimitViews(object): data = [] abs_view = dict() abs_view["verb"] = "ABSOLUTE" - abs_view["maxTotalInstances"] = self.abs_limits.get("instances", 0) - abs_view["maxTotalVolumes"] = self.abs_limits.get("volumes", 0) + for resource_name, abs_limit in self.abs_limits.items(): + abs_view["max_" + resource_name] = abs_limit data.append(abs_view) for l in self.rate_limits: diff --git a/reddwarf/quota/quota.py b/reddwarf/quota/quota.py index 1c666759dd..edd0c568a4 100644 --- a/reddwarf/quota/quota.py +++ b/reddwarf/quota/quota.py @@ -310,8 +310,9 @@ QUOTAS = QuotaEngine() ''' Define all kind of resources here ''' resources = [Resource(Resource.INSTANCES, 'max_instances_per_user'), - Resource(Resource.VOLUMES, 'max_volumes_per_user'), Resource(Resource.BACKUPS, 'max_backups_per_user')] +if CONF.reddwarf_volume_support: + resources.append(Resource(Resource.VOLUMES, 'max_volumes_per_user')) QUOTAS.register_resources(resources) diff --git a/reddwarf/taskmanager/models.py b/reddwarf/taskmanager/models.py index d499450f13..f0b3136916 100644 --- a/reddwarf/taskmanager/models.py +++ b/reddwarf/taskmanager/models.py @@ -219,17 +219,9 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin): def _create_server_volume_individually(self, flavor_id, image_id, security_groups, service_type, volume_size): - volume_info = None - block_device_mapping = None server = None - try: - volume_info = self._create_volume(volume_size) - block_device_mapping = volume_info['block_device'] - except Exception as e: - msg = "Error provisioning volume for instance." - err = inst_models.InstanceTasks.BUILDING_ERROR_VOLUME - self._log_and_raise(e, msg, err) - + volume_info = self._build_volume_info(volume_size) + block_device_mapping = volume_info['block_device'] try: server = self._create_server(flavor_id, image_id, security_groups, service_type, block_device_mapping) @@ -242,6 +234,28 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin): self._log_and_raise(e, msg, err) return server, volume_info + def _build_volume_info(self, volume_size=None): + volume_info = None + volume_support = CONF.reddwarf_volume_support + LOG.debug(_("reddwarf volume support = %s") % volume_support) + if volume_support: + try: + volume_info = self._create_volume(volume_size) + except Exception as e: + msg = "Error provisioning volume for instance." + err = inst_models.InstanceTasks.BUILDING_ERROR_VOLUME + self._log_and_raise(e, msg, err) + else: + LOG.debug(_("device_path = %s") % CONF.device_path) + LOG.debug(_("mount_point = %s") % CONF.mount_point) + volume_info = { + 'block_device': None, + 'device_path': CONF.device_path, + 'mount_point': CONF.mount_point, + 'volumes': None, + } + return volume_info + def _log_and_raise(self, exc, message, task_status): LOG.error(message) LOG.error(exc) @@ -253,18 +267,6 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin): LOG.info("Entering create_volume") LOG.debug(_("Starting to create the volume for the instance")) - volume_support = CONF.reddwarf_volume_support - LOG.debug(_("reddwarf volume support = %s") % volume_support) - if (volume_size is None or - volume_support is False): - volume_info = { - 'block_device': None, - 'device_path': None, - 'mount_point': None, - 'volumes': None, - } - return volume_info - volume_client = create_nova_volume_client(self.context) volume_desc = ("mysql volume for %s" % self.id) volume_ref = volume_client.volumes.create( diff --git a/reddwarf/tests/api/backups.py b/reddwarf/tests/api/backups.py index cc4a039329..f3e56ff2fc 100644 --- a/reddwarf/tests/api/backups.py +++ b/reddwarf/tests/api/backups.py @@ -67,7 +67,7 @@ class AfterBackupCreation(object): @test def test_instance_action_right_after_backup_create(self): """test any instance action while backup is running""" - assert_unprocessable(instance_info.dbaas.instances.resize_volume, + assert_unprocessable(instance_info.dbaas.instances.resize_instance, instance_info.id, 1) @test diff --git a/reddwarf/tests/api/instances.py b/reddwarf/tests/api/instances.py index 4ca34fb40b..3c3cdc5860 100644 --- a/reddwarf/tests/api/instances.py +++ b/reddwarf/tests/api/instances.py @@ -114,6 +114,9 @@ class InstanceTestInfo(object): instance_info = InstanceTestInfo() dbaas = None # Rich client used throughout this test. dbaas_admin = None # Same as above, with admin privs. +VOLUME_SUPPORT = CONFIG.get('reddwarf_volume_support', False) +EPHEMERAL_SUPPORT = not VOLUME_SUPPORT and CONFIG.get('device_path', + '/dev/vdb') is not None # This is like a cheat code which allows the tests to skip creating a new @@ -186,7 +189,11 @@ class InstanceSetup(object): @test def test_find_flavor(self): - flavor_name = CONFIG.values.get('instance_flavor_name', 'm1.tiny') + if EPHEMERAL_SUPPORT: + flavor_name = CONFIG.values.get('instance_eph_flavor_name', + 'eph.rd-tiny') + else: + flavor_name = CONFIG.values.get('instance_flavor_name', 'm1.tiny') flavors = dbaas.find_flavors_by_name(flavor_name) assert_equal(len(flavors), 1, "Number of flavors with name '%s' " "found was '%d'." % (flavor_name, len(flavors))) @@ -245,14 +252,15 @@ class CreateInstanceQuotaTest(unittest.TestCase): self.test_info = copy.deepcopy(instance_info) def tearDown(self): - quota_dict = {'instances': CONFIG.reddwarf_max_instances_per_user, - 'volumes': CONFIG.reddwarf_max_volumes_per_user} + quota_dict = {'instances': CONFIG.reddwarf_max_instances_per_user} + if VOLUME_SUPPORT: + quota_dict['volumes'] = CONFIG.reddwarf_max_volumes_per_user dbaas_admin.quota.update(self.test_info.user.tenant_id, quota_dict) def test_instance_size_too_big(self): - vol_ok = CONFIG.get('reddwarf_volume_support', False) - if 'reddwarf_max_accepted_volume_size' in CONFIG.values and vol_ok: + if ('reddwarf_max_accepted_volume_size' in CONFIG.values and + VOLUME_SUPPORT): too_big = CONFIG.reddwarf_max_accepted_volume_size self.test_info.volume = {'size': too_big + 1} @@ -263,6 +271,18 @@ class CreateInstanceQuotaTest(unittest.TestCase): self.test_info.dbaas_flavor_href, self.test_info.volume) + def test_update_quota_invalid_resource_should_fail(self): + quota_dict = {'invalid_resource': 100} + assert_raises(exceptions.NotFound, dbaas_admin.quota.update, + self.test_info.user.tenant_id, quota_dict) + + def test_update_quota_volume_should_fail_volume_not_supported(self): + if VOLUME_SUPPORT: + raise SkipTest("Volume support needs to be disabled") + quota_dict = {'volumes': 100} + assert_raises(exceptions.NotFound, dbaas_admin.quota.update, + self.test_info.user.tenant_id, quota_dict) + def test_create_too_many_instances(self): instance_quota = 0 quota_dict = {'instances': instance_quota} @@ -273,10 +293,13 @@ class CreateInstanceQuotaTest(unittest.TestCase): assert_equal(new_quotas['instances'], quota_dict['instances']) assert_equal(0, verify_quota['instances']) - assert_equal(CONFIG.reddwarf_max_volumes_per_user, - verify_quota['volumes']) + self.test_info.volume = None + + if VOLUME_SUPPORT: + assert_equal(CONFIG.reddwarf_max_volumes_per_user, + verify_quota['volumes']) + self.test_info.volume = {'size': 1} - self.test_info.volume = {'size': 1} self.test_info.name = "too_many_instances" assert_raises(exceptions.OverLimit, dbaas.instances.create, @@ -287,6 +310,8 @@ class CreateInstanceQuotaTest(unittest.TestCase): assert_equal(413, dbaas.last_http_code) def test_create_instances_total_volume_exceeded(self): + if not VOLUME_SUPPORT: + raise SkipTest("Volume support not enabled") volume_quota = 3 quota_dict = {'volumes': volume_quota} self.test_info.volume = {'size': volume_quota + 1} @@ -307,13 +332,14 @@ class CreateInstanceQuotaTest(unittest.TestCase): @test(depends_on_classes=[InstanceSetup], groups=[GROUP, GROUP_START, GROUP_START_SIMPLE, tests.INSTANCES], runs_after_groups=[tests.PRE_INSTANCES, 'dbaas_quotas']) -class CreateInstance(unittest.TestCase): +class CreateInstance(object): """Test to create a Database Instance If the call returns without raising an exception this test passes. """ + @test def test_create(self): databases = [] databases.append({"name": "firstdb", "character_set": "latin2", @@ -324,7 +350,7 @@ class CreateInstance(unittest.TestCase): users.append({"name": "lite", "password": "litepass", "databases": [{"name": "firstdb"}]}) instance_info.users = users - if CONFIG.values['reddwarf_volume_support']: + if VOLUME_SUPPORT: instance_info.volume = {'size': 1} else: instance_info.volume = None @@ -361,7 +387,7 @@ class CreateInstance(unittest.TestCase): # Check these attrs only are returned in create response expected_attrs = ['created', 'flavor', 'addresses', 'id', 'links', 'name', 'status', 'updated'] - if CONFIG.values['reddwarf_volume_support']: + if VOLUME_SUPPORT: expected_attrs.append('volume') if CONFIG.reddwarf_dns_support: expected_attrs.append('hostname') @@ -373,31 +399,52 @@ class CreateInstance(unittest.TestCase): # Don't CheckInstance if the instance already exists. check.flavor() check.links(result._info['links']) - if CONFIG.values['reddwarf_volume_support']: + if VOLUME_SUPPORT: check.volume() + @test(enabled=VOLUME_SUPPORT) def test_create_failure_with_empty_volume(self): - if CONFIG.values['reddwarf_volume_support']: - instance_name = "instance-failure-with-no-volume-size" - databases = [] - volume = {} - assert_raises(exceptions.BadRequest, dbaas.instances.create, - instance_name, instance_info.dbaas_flavor_href, - volume, databases) - assert_equal(400, dbaas.last_http_code) + instance_name = "instance-failure-with-no-volume-size" + databases = [] + volume = {} + assert_raises(exceptions.BadRequest, dbaas.instances.create, + instance_name, instance_info.dbaas_flavor_href, + volume, databases) + assert_equal(400, dbaas.last_http_code) + @test(enabled=VOLUME_SUPPORT) def test_create_failure_with_no_volume_size(self): - if CONFIG.values['reddwarf_volume_support']: - instance_name = "instance-failure-with-no-volume-size" - databases = [] - volume = {'size': None} - assert_raises(exceptions.BadRequest, dbaas.instances.create, - instance_name, instance_info.dbaas_flavor_href, - volume, databases) - assert_equal(400, dbaas.last_http_code) + instance_name = "instance-failure-with-no-volume-size" + databases = [] + volume = {'size': None} + assert_raises(exceptions.BadRequest, dbaas.instances.create, + instance_name, instance_info.dbaas_flavor_href, + volume, databases) + assert_equal(400, dbaas.last_http_code) + @test(enabled=not VOLUME_SUPPORT) + def test_create_failure_with_volume_size_and_volume_disabled(self): + instance_name = "instance-failure-volume-size_and_volume_disabled" + databases = [] + volume = {'size': 2} + assert_raises(exceptions.HTTPNotImplemented, dbaas.instances.create, + instance_name, instance_info.dbaas_flavor_href, + volume, databases) + assert_equal(501, dbaas.last_http_code) + + @test(enabled=EPHEMERAL_SUPPORT) + def test_create_failure_with_no_ephemeral_flavor(self): + instance_name = "instance-failure-with-no-ephemeral-flavor" + databases = [] + flavor_name = CONFIG.values.get('instance_flavor_name', 'm1.tiny') + flavors = dbaas.find_flavors_by_name(flavor_name) + assert_raises(exceptions.BadRequest, dbaas.instances.create, + instance_name, flavors[0].id, None, databases) + assert_equal(400, dbaas.last_http_code) + + @test def test_create_failure_with_no_name(self): - if CONFIG.values['reddwarf_volume_support']: + if VOLUME_SUPPORT: volume = {'size': 1} else: volume = None @@ -408,8 +455,9 @@ class CreateInstance(unittest.TestCase): volume, databases) assert_equal(400, dbaas.last_http_code) + @test def test_create_failure_with_spaces_for_name(self): - if CONFIG.values['reddwarf_volume_support']: + if VOLUME_SUPPORT: volume = {'size': 1} else: volume = None @@ -420,6 +468,7 @@ class CreateInstance(unittest.TestCase): volume, databases) assert_equal(400, dbaas.last_http_code) + @test def test_mgmt_get_instance_on_create(self): if CONFIG.test_mgmt: result = dbaas_admin.management.show(instance_info.id) @@ -777,7 +826,9 @@ class TestInstanceListing(object): @test def test_index_list(self): - expected_attrs = ['id', 'links', 'name', 'status', 'flavor', 'volume'] + expected_attrs = ['id', 'links', 'name', 'status', 'flavor'] + if VOLUME_SUPPORT: + expected_attrs.append('volume') instances = dbaas.instances.list() assert_equal(200, dbaas.last_http_code) for instance in instances: @@ -793,7 +844,9 @@ class TestInstanceListing(object): @test def test_get_instance(self): expected_attrs = ['created', 'databases', 'flavor', 'hostname', 'id', - 'links', 'name', 'status', 'updated', 'volume', 'ip'] + 'links', 'name', 'status', 'updated', 'ip'] + if VOLUME_SUPPORT: + expected_attrs.append('volume') instance = dbaas.instances.get(instance_info.id) assert_equal(200, dbaas.last_http_code) instance_dict = instance._info @@ -829,7 +882,7 @@ class TestInstanceListing(object): def test_get_legacy_status_notfound(self): assert_raises(exceptions.NotFound, dbaas.instances.get, -2) - @test(enabled=CONFIG.values["reddwarf_volume_support"]) + @test(enabled=VOLUME_SUPPORT) def test_volume_found(self): instance = dbaas.instances.get(instance_info.id) if create_new_instance(): @@ -960,7 +1013,7 @@ class DeleteInstance(object): " time: %s" % (str(instance_info.id), attempts, str(ex))) @time_out(30) - @test(enabled=CONFIG.values["reddwarf_volume_support"], + @test(enabled=VOLUME_SUPPORT, depends_on=[test_delete]) def test_volume_is_deleted(self): raise SkipTest("Cannot test volume is deleted from db.") @@ -1105,14 +1158,13 @@ class CheckInstance(AttrCheck): self.links(self.instance['flavor']['links']) def volume_key_exists(self): - if CONFIG.values['reddwarf_volume_support']: - if 'volume' not in self.instance: - self.fail("'volume' not found in instance.") - return False - return True + if 'volume' not in self.instance: + self.fail("'volume' not found in instance.") + return False + return True def volume(self): - if not CONFIG.values["reddwarf_volume_support"]: + if not VOLUME_SUPPORT: return if self.volume_key_exists(): expected_attrs = ['size'] @@ -1122,7 +1174,7 @@ class CheckInstance(AttrCheck): msg="Volumes") def used_volume(self): - if not CONFIG.values["reddwarf_volume_support"]: + if not VOLUME_SUPPORT: return if self.volume_key_exists(): expected_attrs = ['size', 'used'] @@ -1131,6 +1183,8 @@ class CheckInstance(AttrCheck): msg="Volumes") def volume_mgmt(self): + if not VOLUME_SUPPORT: + return if self.volume_key_exists(): expected_attrs = ['description', 'id', 'name', 'size'] self.attrs_exist(self.instance['volume'], expected_attrs, @@ -1152,6 +1206,8 @@ class CheckInstance(AttrCheck): msg="Guest status") def mgmt_volume(self): + if not VOLUME_SUPPORT: + return expected_attrs = ['description', 'id', 'name', 'size'] self.attrs_exist(self.instance['volume'], expected_attrs, msg="Volume") @@ -1183,7 +1239,13 @@ class BadInstanceStatusBug(): # can be used as another case. This all boils back to the same # piece of code so I'm not sure if it's relevant or not but could # be done. - result = self.client.instances.create('testbox', 1, {'size': 5}) + size = None + if VOLUME_SUPPORT: + size = {'size': 5} + + result = self.client.instances.create('testbox', + instance_info.dbaas_flavor_href, + size) id = result.id self.instances.append(id) diff --git a/reddwarf/tests/api/instances_actions.py b/reddwarf/tests/api/instances_actions.py index 21edcef2fd..d9aa20654f 100644 --- a/reddwarf/tests/api/instances_actions.py +++ b/reddwarf/tests/api/instances_actions.py @@ -25,11 +25,14 @@ from proboscis import SkipTest from reddwarf import tests from reddwarf.tests.util.check import Checker from reddwarfclient.exceptions import BadRequest +from reddwarfclient.exceptions import HTTPNotImplemented from reddwarfclient.exceptions import UnprocessableEntity from reddwarf.tests.api.instances import GROUP as INSTANCE_GROUP from reddwarf.tests.api.instances import GROUP_START from reddwarf.tests.api.instances import instance_info from reddwarf.tests.api.instances import assert_unprocessable +from reddwarf.tests.api.instances import VOLUME_SUPPORT +from reddwarf.tests.api.instances import EPHEMERAL_SUPPORT from reddwarf.tests import util from reddwarf.tests.util.server_connection import create_server_connection from reddwarf.tests.util import poll_until @@ -326,7 +329,7 @@ class StopTests(RebootTestBase): Confirms the get call behaves appropriately while an instance is down. """ - if not CONFIG.reddwarf_volume_support: + if not VOLUME_SUPPORT: raise SkipTest("Not testing volumes.") instance = self.dbaas.instances.get(self.instance_id) with TypeCheck("instance", instance) as check: @@ -416,14 +419,33 @@ class ResizeInstanceTest(ActionTestBase): assert_raises(BadRequest, self.dbaas.instances.resize_instance, self.instance_id, self.flavor_id) + @test(enabled=VOLUME_SUPPORT) + def test_instance_resize_to_ephemeral_in_volume_support_should_fail(self): + flavor_name = CONFIG.values.get('instance_bigger_eph_flavor_name', + 'eph.rd-smaller') + flavors = self.dbaas.find_flavors_by_name(flavor_name) + assert_raises(HTTPNotImplemented, self.dbaas.instances.resize_instance, + self.instance_id, flavors[0].id) + + @test(enabled=EPHEMERAL_SUPPORT) + def test_instance_resize_to_non_ephemeral_flavor_should_fail(self): + flavor_name = CONFIG.values.get('instance_bigger_flavor_name', + 'm1-small') + flavors = self.dbaas.find_flavors_by_name(flavor_name) + assert_raises(BadRequest, self.dbaas.instances.resize_instance, + self.instance_id, flavors[0].id) + def obtain_flavor_ids(self): old_id = self.instance.flavor['id'] self.expected_old_flavor_id = old_id res = instance_info.dbaas.find_flavor_and_self_href(old_id) self.expected_dbaas_flavor, _dontcare_ = res - - flavor_name = CONFIG.values.get('instance_bigger_flavor_name', - 'm1.small') + if EPHEMERAL_SUPPORT: + flavor_name = CONFIG.values.get('instance_bigger_eph_flavor_name', + 'eph.rd-smaller') + else: + flavor_name = CONFIG.values.get('instance_bigger_flavor_name', + 'm1.small') flavors = self.dbaas.find_flavors_by_name(flavor_name) assert_equal(len(flavors), 1, "Number of flavors with name '%s' " "found was '%d'." % (flavor_name, len(flavors))) @@ -548,7 +570,7 @@ def resize_should_not_delete_users(): @test(runs_after=[ResizeInstanceTest], depends_on=[create_user], groups=[GROUP, tests.INSTANCES], - enabled=CONFIG.reddwarf_volume_support) + enabled=VOLUME_SUPPORT) class ResizeInstanceVolume(object): """ Resize the volume of the instance """ diff --git a/reddwarf/tests/api/instances_delete.py b/reddwarf/tests/api/instances_delete.py index 9bef213d56..acc8ad628c 100644 --- a/reddwarf/tests/api/instances_delete.py +++ b/reddwarf/tests/api/instances_delete.py @@ -1,6 +1,5 @@ import time -from proboscis import after_class from proboscis import before_class from proboscis import test from proboscis.asserts import * @@ -11,6 +10,8 @@ from reddwarf.tests.util import create_dbaas_client from reddwarf.tests.util import poll_until from reddwarf.tests.util import test_config from reddwarf.tests.util.users import Requirements +from reddwarf.tests.api.instances import instance_info +from reddwarf.tests.api.instances import VOLUME_SUPPORT class TestBase(object): @@ -21,7 +22,12 @@ class TestBase(object): self.dbaas = create_dbaas_client(self.user) def create_instance(self, name, size=1): - result = self.dbaas.instances.create(name, 1, {'size': size}, [], []) + volume = None + if VOLUME_SUPPORT: + volume = {'size': size} + result = self.dbaas.instances.create(name, + instance_info.dbaas_flavor_href, + volume, [], []) return result.id def wait_for_instance_status(self, instance_id, status="ACTIVE"): @@ -73,8 +79,12 @@ class ErroredInstanceDelete(TestBase): super(ErroredInstanceDelete, self).set_up() # Create an instance that fails during server prov. self.server_error = self.create_instance('test_SERVER_ERROR') - # Create an instance that fails during volume prov. - self.volume_error = self.create_instance('test_VOLUME_ERROR', size=9) + if VOLUME_SUPPORT: + # Create an instance that fails during volume prov. + self.volume_error = self.create_instance('test_VOLUME_ERROR', + size=9) + else: + self.volume_error = None # Create an instance that fails during DNS prov. #self.dns_error = self.create_instance('test_DNS_ERROR') # Create an instance that fails while it's been deleted the first time. @@ -85,7 +95,7 @@ class ErroredInstanceDelete(TestBase): def delete_server_error(self): self.delete_errored_instance(self.server_error) - @test + @test(enabled=VOLUME_SUPPORT) @time_out(20) def delete_volume_error(self): self.delete_errored_instance(self.volume_error) diff --git a/reddwarf/tests/api/instances_mysql_down.py b/reddwarf/tests/api/instances_mysql_down.py index f7db4fbc2f..186cbd3161 100644 --- a/reddwarf/tests/api/instances_mysql_down.py +++ b/reddwarf/tests/api/instances_mysql_down.py @@ -30,6 +30,8 @@ from reddwarf.tests import util from reddwarf.tests.util import create_client from reddwarf.tests.util import poll_until from reddwarf.tests.util import test_config +from reddwarf.tests.api.instances import VOLUME_SUPPORT +from reddwarf.tests.api.instances import EPHEMERAL_SUPPORT @test(groups=["dbaas.api.instances.down"]) @@ -40,13 +42,21 @@ class TestBase(object): def set_up(self): self.client = create_client(is_admin=False) self.mgmt_client = create_client(is_admin=True) - flavor_name = test_config.values.get('instance_flavor_name', 'm1.tiny') + + if EPHEMERAL_SUPPORT: + flavor_name = test_config.values.get('instance_eph_flavor_name', + 'eph.rd-tiny') + flavor2_name = test_config.values.get( + 'instance_bigger_eph_flavor_name', 'eph.rd-smaller') + else: + flavor_name = test_config.values.get('instance_flavor_name', + 'm1.tiny') + flavor2_name = test_config.values.get( + 'instance_bigger_flavor_name', 'm1.small') flavors = self.client.find_flavors_by_name(flavor_name) self.flavor_id = flavors[0].id self.name = "TEST_" + str(datetime.now()) # Get the resize to flavor. - flavor2_name = test_config.values.get('instance_bigger_flavor_name', - 'm1.small') flavors2 = self.client.find_flavors_by_name(flavor2_name) self.new_flavor_id = flavors2[0].id assert_not_equal(self.flavor_id, self.new_flavor_id) @@ -58,8 +68,11 @@ class TestBase(object): @test def create_instance(self): + volume = None + if VOLUME_SUPPORT: + volume = {'size': 1} initial = self.client.instances.create(self.name, self.flavor_id, - {'size': 1}, [], []) + volume, [], []) self.id = initial.id self._wait_for_active() @@ -82,7 +95,8 @@ class TestBase(object): def put_into_shutdown_state_2(self): self._shutdown_instance() - @test(depends_on=[put_into_shutdown_state_2]) + @test(depends_on=[put_into_shutdown_state_2], + enabled=VOLUME_SUPPORT) @time_out(60 * 5) def resize_volume_in_shutdown_state(self): self.client.instances.resize_volume(self.id, 2) diff --git a/reddwarf/tests/api/limits.py b/reddwarf/tests/api/limits.py index 4d64adfc13..dea1ba2378 100644 --- a/reddwarf/tests/api/limits.py +++ b/reddwarf/tests/api/limits.py @@ -10,11 +10,13 @@ from reddwarf.tests.util import create_dbaas_client from reddwarfclient import exceptions from datetime import datetime from reddwarf.tests.util.users import Users +from reddwarf.tests.config import CONFIG GROUP = "dbaas.api.limits" DEFAULT_RATE = 200 DEFAULT_MAX_VOLUMES = 100 DEFAULT_MAX_INSTANCES = 55 +DEFAULT_MAX_BACKUPS = 5 @test(groups=[GROUP]) @@ -72,8 +74,10 @@ class Limits(object): # remove the abs_limits from the rate limits abs_limits = d.pop("ABSOLUTE", None) assert_equal(abs_limits.verb, "ABSOLUTE") - assert_equal(int(abs_limits.maxTotalInstances), DEFAULT_MAX_INSTANCES) - assert_equal(int(abs_limits.maxTotalVolumes), DEFAULT_MAX_VOLUMES) + assert_equal(int(abs_limits.max_instances), DEFAULT_MAX_INSTANCES) + assert_equal(int(abs_limits.max_backups), DEFAULT_MAX_BACKUPS) + if CONFIG.reddwarf_volume_support: + assert_equal(int(abs_limits.max_volumes), DEFAULT_MAX_VOLUMES) for k in d: assert_equal(d[k].verb, k) @@ -93,8 +97,10 @@ class Limits(object): abs_limits = d["ABSOLUTE"] get = d["GET"] - assert_equal(int(abs_limits.maxTotalInstances), DEFAULT_MAX_INSTANCES) - assert_equal(int(abs_limits.maxTotalVolumes), DEFAULT_MAX_VOLUMES) + assert_equal(int(abs_limits.max_instances), DEFAULT_MAX_INSTANCES) + assert_equal(int(abs_limits.max_backups), DEFAULT_MAX_BACKUPS) + if CONFIG.reddwarf_volume_support: + assert_equal(int(abs_limits.max_volumes), DEFAULT_MAX_VOLUMES) assert_equal(get.verb, "GET") assert_equal(get.unit, "MINUTE") assert_true(int(get.remaining) <= DEFAULT_RATE - 5) @@ -119,10 +125,13 @@ class Limits(object): assert_equal(get.verb, "GET") assert_equal(get.unit, "MINUTE") - assert_equal(int(abs_limits.maxTotalInstances), + assert_equal(int(abs_limits.max_instances), DEFAULT_MAX_INSTANCES) - assert_equal(int(abs_limits.maxTotalVolumes), - DEFAULT_MAX_VOLUMES) + assert_equal(int(abs_limits.max_backups), + DEFAULT_MAX_BACKUPS) + if CONFIG.reddwarf_volume_support: + assert_equal(int(abs_limits.max_volumes), + DEFAULT_MAX_VOLUMES) except exceptions.OverLimit: encountered = True diff --git a/reddwarf/tests/api/mgmt/accounts.py b/reddwarf/tests/api/mgmt/accounts.py index 703a3b9b59..b08bee4652 100644 --- a/reddwarf/tests/api/mgmt/accounts.py +++ b/reddwarf/tests/api/mgmt/accounts.py @@ -27,6 +27,7 @@ from reddwarf.tests.api.instances import instance_info from reddwarf.tests.util import test_config from reddwarf.tests.util import create_dbaas_client from reddwarf.tests.util import poll_until +from reddwarf.tests.config import CONFIG from reddwarf.tests.util.users import Requirements from reddwarf.tests.api.instances import existing_instance @@ -188,8 +189,14 @@ class AccountWithBrokenInstance(object): self.client = create_dbaas_client(self.user) self.name = 'test_SERVER_ERROR' # Create an instance with a broken compute instance. - self.response = self.client.instances.create(self.name, 1, - {'size': 1}, []) + volume = None + if CONFIG.reddwarf_volume_support: + volume = {'size': 1} + self.response = self.client.instances.create( + self.name, + instance_info.dbaas_flavor_href, + volume, + []) poll_until(lambda: self.client.instances.get(self.response.id), lambda instance: instance.status == 'ERROR', time_out=10) diff --git a/reddwarf/tests/api/mgmt/instances.py b/reddwarf/tests/api/mgmt/instances.py index 4f74155431..a4f4a146f1 100644 --- a/reddwarf/tests/api/mgmt/instances.py +++ b/reddwarf/tests/api/mgmt/instances.py @@ -110,7 +110,8 @@ def mgmt_instance_get(): server.has_element("name", basestring) server.has_element("status", basestring) server.has_element("tenant_id", basestring) - if CONFIG.reddwarf_main_instance_has_volume: + if (CONFIG.reddwarf_volume_support and + CONFIG.reddwarf_main_instance_has_volume): with CollectionCheck("volume", api_instance.volume) as volume: volume.has_element("attachments", list) volume.has_element("availability_zone", basestring) @@ -134,8 +135,14 @@ class WhenMgmtInstanceGetIsCalledButServerIsNotReady(object): # Fake volume will fail if the size is 13. # TODO(tim.simpson): This would be a lot nicer looking if we used a # traditional mock framework. - response = self.client.instances.create('test_SERVER_ERROR', 1, - {'size': 13}, []) + body = None + if CONFIG.reddwarf_volume_support: + body = {'size': 13} + response = self.client.instances.create( + 'test_SERVER_ERROR', + instance_info.dbaas_flavor_href, + body, + []) poll_until(lambda: self.client.instances.get(response.id), lambda instance: instance.status == 'ERROR', time_out=10) @@ -201,8 +208,10 @@ class MgmtInstancesIndex(object): 'task_description', 'tenant_id', 'updated', - 'volume', ] + if CONFIG.reddwarf_volume_support: + expected_fields.append('volume') + index = self.client.management.index() for instance in index: with Check() as check: diff --git a/reddwarf/tests/api/mgmt/malformed_json.py b/reddwarf/tests/api/mgmt/malformed_json.py index e1d92ce56d..b28f76f8de 100644 --- a/reddwarf/tests/api/mgmt/malformed_json.py +++ b/reddwarf/tests/api/mgmt/malformed_json.py @@ -4,6 +4,8 @@ from proboscis import after_class from proboscis import before_class from proboscis.asserts import Check from reddwarf.tests.config import CONFIG +from reddwarf.tests.api.instances import instance_info +from reddwarf.tests.api.instances import VOLUME_SUPPORT from reddwarfclient import exceptions import json import requests @@ -22,10 +24,13 @@ class MalformedJson(object): self.reqs = Requirements(is_admin=False) self.user = CONFIG.users.find_user(self.reqs) self.dbaas = create_dbaas_client(self.user) + volume = None + if VOLUME_SUPPORT: + volume = {"size": 1} self.instance = self.dbaas.instances.create( name="qe_instance", - flavor_id=1, - volume={"size": 1}, + flavor_id=instance_info.dbaas_flavor_href, + volume=volume, databases=[{"name": "firstdb", "character_set": "latin2", "collate": "latin2_general_ci"}]) diff --git a/reddwarf/tests/fakes/nova.py b/reddwarf/tests/fakes/nova.py index 62f7b5144c..dcd5d699aa 100644 --- a/reddwarf/tests/fakes/nova.py +++ b/reddwarf/tests/fakes/nova.py @@ -32,13 +32,13 @@ FAKE_HOSTS = ["fake_host_1", "fake_host_2"] class FakeFlavor(object): - def __init__(self, id, disk, name, ram): + def __init__(self, id, disk, name, ram, ephemeral=0, vcpus=10): self.id = id self.disk = disk self.name = name self.ram = ram - self.vcpus = 10 - self.ephemeral = 0 + self.vcpus = vcpus + self.ephemeral = ephemeral @property def links(self): @@ -69,6 +69,8 @@ class FakeFlavors(object): self._add(8, 2, "m1.rd-smaller", 768) self._add(9, 10, "tinier", 506) self._add(10, 2, "m1.rd-tiny", 512) + self._add(11, 0, "eph.rd-tiny", 512, 1) + self._add(12, 20, "eph.rd-smaller", 768, 2) def _add(self, *args, **kwargs): new_flavor = FakeFlavor(*args, **kwargs) diff --git a/reddwarf/tests/unittests/api/common/test_limits.py b/reddwarf/tests/unittests/api/common/test_limits.py index cc4bc51db9..1da953bea7 100644 --- a/reddwarf/tests/unittests/api/common/test_limits.py +++ b/reddwarf/tests/unittests/api/common/test_limits.py @@ -44,8 +44,9 @@ class BaseLimitTestSuite(testtools.TestCase): def setUp(self): super(BaseLimitTestSuite, self).setUp() - self.absolute_limits = {"maxTotalInstances": 55, - "maxTotalVolumes": 100} + self.absolute_limits = {"max_instances": 55, + "max_volumes": 100, + "max_backups": 40} class LimitsControllerTest(BaseLimitTestSuite): @@ -60,8 +61,7 @@ class LimitsControllerTest(BaseLimitTestSuite): when(QUOTAS).get_all_quotas_by_tenant(any()).thenReturn({}) view = limit_controller.index(req, "test_tenant_id") - expected = {'limits': [{'maxTotalInstances': 0, - 'verb': 'ABSOLUTE', 'maxTotalVolumes': 0}]} + expected = {'limits': [{'verb': 'ABSOLUTE'}]} self.assertEqual(expected, view._data) def test_limit_index(self): @@ -111,6 +111,10 @@ class LimitsControllerTest(BaseLimitTestSuite): resource="instances", hard_limit=100), + "backups": Quota(tenant_id=tenant_id, + resource="backups", + hard_limit=40), + "volumes": Quota(tenant_id=tenant_id, resource="volumes", hard_limit=55)} @@ -124,9 +128,10 @@ class LimitsControllerTest(BaseLimitTestSuite): expected = { 'limits': [ { - 'maxTotalInstances': 100, + 'max_instances': 100, + 'max_backups': 40, 'verb': 'ABSOLUTE', - 'maxTotalVolumes': 55 + 'max_volumes': 55 }, { 'regex': '.*', @@ -752,9 +757,7 @@ class LimitsViewsTest(testtools.TestCase): self.assertIsNotNone(view_data) data = view_data.data() - expected = {'limits': [{'maxTotalInstances': 0, - 'verb': 'ABSOLUTE', - 'maxTotalVolumes': 0}]} + expected = {'limits': [{'verb': 'ABSOLUTE'}]} self.assertEqual(expected, data) @@ -797,15 +800,16 @@ class LimitsViewsTest(testtools.TestCase): "resetTime": 1311272226 } ] - abs_view = {"instances": 55, "volumes": 100} + abs_view = {"instances": 55, "volumes": 100, "backups": 40} view_data = views.LimitViews(abs_view, rate_limits) self.assertIsNotNone(view_data) data = view_data.data() - expected = {'limits': [{'maxTotalInstances': 55, + expected = {'limits': [{'max_instances': 55, + 'max_backups': 40, 'verb': 'ABSOLUTE', - 'maxTotalVolumes': 100}, + 'max_volumes': 100}, {'regex': '.*', 'nextAvailable': '2011-07-21T18:17:06Z', 'uri': '*', diff --git a/reddwarf/tests/unittests/quota/test_quota.py b/reddwarf/tests/unittests/quota/test_quota.py index 8449f4cfeb..3168633bd0 100644 --- a/reddwarf/tests/unittests/quota/test_quota.py +++ b/reddwarf/tests/unittests/quota/test_quota.py @@ -110,7 +110,22 @@ class QuotaControllerTest(testtools.TestCase): verify(quota, never).save() self.assertEquals(200, result.status) - def test_update_resource_(self): + def test_update_resource_instance(self): + instance_quota = mock(Quota) + when(DatabaseModelBase).find_by( + tenant_id=FAKE_TENANT2, + resource='instances').thenReturn(instance_quota) + body = {'quotas': {'instances': 2}} + result = self.controller.update(self.req, body, FAKE_TENANT1, + FAKE_TENANT2) + verify(instance_quota, times=1).save() + self.assertTrue('instances' in result._data['quotas']) + self.assertEquals(200, result.status) + self.assertEquals(2, result._data['quotas']['instances']) + + @testtools.skipIf(not CONF.reddwarf_volume_support, + 'Volume support is not enabled') + def test_update_resource_volume(self): instance_quota = mock(Quota) when(DatabaseModelBase).find_by( tenant_id=FAKE_TENANT2, @@ -123,7 +138,7 @@ class QuotaControllerTest(testtools.TestCase): result = self.controller.update(self.req, body, FAKE_TENANT1, FAKE_TENANT2) verify(instance_quota, never).save() - self.assertFalse('instances' in result._data) + self.assertFalse('instances' in result._data['quotas']) verify(volume_quota, times=1).save() self.assertEquals(200, result.status) self.assertEquals(10, result._data['quotas']['volumes'])