diff options
-rw-r--r-- | nova/db/sqlalchemy/api.py | 6 | ||||
-rw-r--r-- | nova/tests/test_libvirt.py | 34 | ||||
-rw-r--r-- | nova/tests/test_quota.py | 24 | ||||
-rw-r--r-- | nova/tests/test_xenapi.py | 120 | ||||
-rw-r--r-- | nova/virt/libvirt/connection.py | 8 | ||||
-rw-r--r-- | nova/virt/xenapi/vmops.py | 53 |
6 files changed, 218 insertions, 27 deletions
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 2ce42e1cc..37239d016 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2241,6 +2241,9 @@ def quota_get_all_by_project(context, project_id): @require_admin_context def quota_create(context, project_id, resource, limit): + # NOTE: Treat -1 as unlimited for consistency w/ flags + if limit == -1: + limit = None quota_ref = models.Quota() quota_ref.project_id = project_id quota_ref.resource = resource @@ -2251,6 +2254,9 @@ def quota_create(context, project_id, resource, limit): @require_admin_context def quota_update(context, project_id, resource, limit): + # NOTE: Treat -1 as unlimited for consistency w/ flags + if limit == -1: + limit = None session = get_session() with session.begin(): quota_ref = quota_get(context, project_id, resource, session=session) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 4eea7b0b3..ef0307158 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -780,6 +780,40 @@ class LibvirtConnTestCase(test.TestCase): self.assertEquals(snapshot['status'], 'active') self.assertEquals(snapshot['name'], snapshot_name) + @test.skip_if(missing_libvirt(), "Test requires libvirt") + def test_snapshot_no_original_image(self): + self.flags(image_service='nova.image.fake.FakeImageService') + + # Start test + image_service = utils.import_object(FLAGS.image_service) + + # Assign a non-existent image + test_instance = copy.deepcopy(self.test_instance) + test_instance["image_ref"] = '661122aa-1234-dede-fefe-babababababa' + + instance_ref = db.instance_create(self.context, test_instance) + properties = {'instance_id': instance_ref['id'], + 'user_id': str(self.context.user_id)} + snapshot_name = 'test-snap' + sent_meta = {'name': snapshot_name, 'is_public': False, + 'status': 'creating', 'properties': properties} + recv_meta = image_service.create(context, sent_meta) + + self.mox.StubOutWithMock(connection.LibvirtConnection, '_conn') + connection.LibvirtConnection._conn.lookupByName = self.fake_lookup + self.mox.StubOutWithMock(connection.utils, 'execute') + connection.utils.execute = self.fake_execute + + self.mox.ReplayAll() + + conn = connection.LibvirtConnection(False) + conn.snapshot(self.context, instance_ref, recv_meta['id']) + + snapshot = image_service.show(context, recv_meta['id']) + self.assertEquals(snapshot['properties']['image_state'], 'available') + self.assertEquals(snapshot['status'], 'active') + self.assertEquals(snapshot['name'], snapshot_name) + def test_attach_invalid_volume_type(self): self.create_fake_libvirt_mock() connection.LibvirtConnection._conn.lookupByName = self.fake_lookup diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index ca4fd265c..46641d4d3 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -342,6 +342,10 @@ class QuotaTestCase(test.TestCase): num_instances = quota.allowed_instances(self.context, 100, instance_type) self.assertEqual(num_instances, 100) + db.quota_create(self.context, self.project_id, 'instances', -1) + num_instances = quota.allowed_instances(self.context, 100, + instance_type) + self.assertEqual(num_instances, 100) num_instances = quota.allowed_instances(self.context, 101, instance_type) self.assertEqual(num_instances, 101) @@ -356,6 +360,10 @@ class QuotaTestCase(test.TestCase): num_instances = quota.allowed_instances(self.context, 100, instance_type) self.assertEqual(num_instances, 100) + db.quota_create(self.context, self.project_id, 'ram', -1) + num_instances = quota.allowed_instances(self.context, 100, + instance_type) + self.assertEqual(num_instances, 100) num_instances = quota.allowed_instances(self.context, 101, instance_type) self.assertEqual(num_instances, 101) @@ -370,6 +378,10 @@ class QuotaTestCase(test.TestCase): num_instances = quota.allowed_instances(self.context, 100, instance_type) self.assertEqual(num_instances, 100) + db.quota_create(self.context, self.project_id, 'cores', -1) + num_instances = quota.allowed_instances(self.context, 100, + instance_type) + self.assertEqual(num_instances, 100) num_instances = quota.allowed_instances(self.context, 101, instance_type) self.assertEqual(num_instances, 101) @@ -381,6 +393,9 @@ class QuotaTestCase(test.TestCase): db.quota_create(self.context, self.project_id, 'volumes', None) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 100) + db.quota_create(self.context, self.project_id, 'volumes', -1) + volumes = quota.allowed_volumes(self.context, 100, 1) + self.assertEqual(volumes, 100) volumes = quota.allowed_volumes(self.context, 101, 1) self.assertEqual(volumes, 101) @@ -391,6 +406,9 @@ class QuotaTestCase(test.TestCase): db.quota_create(self.context, self.project_id, 'gigabytes', None) volumes = quota.allowed_volumes(self.context, 100, 1) self.assertEqual(volumes, 100) + db.quota_create(self.context, self.project_id, 'gigabytes', -1) + volumes = quota.allowed_volumes(self.context, 100, 1) + self.assertEqual(volumes, 100) volumes = quota.allowed_volumes(self.context, 101, 1) self.assertEqual(volumes, 101) @@ -401,6 +419,9 @@ class QuotaTestCase(test.TestCase): db.quota_create(self.context, self.project_id, 'floating_ips', None) floating_ips = quota.allowed_floating_ips(self.context, 100) self.assertEqual(floating_ips, 100) + db.quota_create(self.context, self.project_id, 'floating_ips', -1) + floating_ips = quota.allowed_floating_ips(self.context, 100) + self.assertEqual(floating_ips, 100) floating_ips = quota.allowed_floating_ips(self.context, 101) self.assertEqual(floating_ips, 101) @@ -411,6 +432,9 @@ class QuotaTestCase(test.TestCase): db.quota_create(self.context, self.project_id, 'metadata_items', None) items = quota.allowed_metadata_items(self.context, 100) self.assertEqual(items, 100) + db.quota_create(self.context, self.project_id, 'metadata_items', -1) + items = quota.allowed_metadata_items(self.context, 100) + self.assertEqual(items, 100) items = quota.allowed_metadata_items(self.context, 101) self.assertEqual(items, 101) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index bc8a4c8c1..581f833fc 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -23,27 +23,31 @@ import functools import os import re -from nova import db -from nova import context -from nova import flags -from nova import log as logging -from nova import test -from nova import utils +import mox + from nova.compute import aggregate_states from nova.compute import instance_types from nova.compute import power_state +from nova.compute import task_states from nova.compute import utils as compute_utils +from nova.compute import vm_states +from nova import context +from nova import db from nova import exception -from nova.virt import xenapi_conn -from nova.virt.xenapi import fake as xenapi_fake -from nova.virt.xenapi import volume_utils -from nova.virt.xenapi import vmops -from nova.virt.xenapi import vm_utils +from nova import flags +from nova import log as logging +from nova import test from nova.tests.db import fakes as db_fakes from nova.tests.xenapi import stubs from nova.tests.glance import stubs as glance_stubs from nova.tests import fake_network from nova.tests import fake_utils +from nova import utils +from nova.virt import xenapi_conn +from nova.virt.xenapi import fake as xenapi_fake +from nova.virt.xenapi import volume_utils +from nova.virt.xenapi import vmops +from nova.virt.xenapi import vm_utils LOG = logging.getLogger(__name__) @@ -851,6 +855,100 @@ class XenAPIMigrateInstance(test.TestCase): stubs.stubout_get_this_vm_uuid(self.stubs) glance_stubs.stubout_glance_client(self.stubs) + def test_poll_unconfirmed_resizes(self): + """Test all migrations are checked despite errors when + autoconfirming resizes. + """ + stubs.stubout_session(self.stubs, + stubs.FakeSessionForMigrationTests) + conn = xenapi_conn.get_connection(False) + + self.mox.StubOutWithMock(context, 'get_admin_context') + self.mox.StubOutWithMock(db, 'migration_get_all_unconfirmed') + self.mox.StubOutWithMock(db, 'instance_get_by_uuid') + self.mox.StubOutWithMock(db, 'migration_update') + self.mox.StubOutWithMock(vmops.LOG, 'info') + self.mox.StubOutWithMock(vmops.LOG, 'warn') + self.mox.StubOutWithMock(vmops.LOG, 'error') + self.mox.StubOutWithMock(conn._vmops.compute_api, 'confirm_resize') + + fake_context = 'fake-context' + instances = [{'uuid': 'fake_uuid1', 'vm_state': vm_states.ACTIVE, + 'task_state': task_states.RESIZE_VERIFY}, + {'uuid': 'noexist'}, + {'uuid': 'fake_uuid2', 'vm_state': vm_states.ERROR, + 'task_state': task_states.RESIZE_VERIFY}, + {'uuid': 'fake_uuid3', 'vm_state': vm_states.ACTIVE, + 'task_state': task_states.REBOOTING}, + {'uuid': 'fake_uuid4', 'vm_state': vm_states.ACTIVE, + 'task_state': task_states.RESIZE_VERIFY}, + {'uuid': 'fake_uuid5', 'vm_state': vm_states.ACTIVE, + 'task_state': task_states.RESIZE_VERIFY}] + + migrations = [] + for i, instance in enumerate(instances, start=1): + migrations.append({'id': i, 'instance_uuid': instance['uuid']}) + resize_confirm_window = 60 + + context.get_admin_context().AndReturn(fake_context) + db.migration_get_all_unconfirmed(fake_context, + resize_confirm_window).AndReturn(migrations) + # Found unconfirmed migrations message + vmops.LOG.info(mox.IgnoreArg()) + + # test success (ACTIVE/RESIZE_VERIFY) + instance = instances.pop(0) + vmops.LOG.info(mox.IgnoreArg()) + db.instance_get_by_uuid(fake_context, + instance['uuid']).AndReturn(instance) + conn._vmops.compute_api.confirm_resize(fake_context, + instance) + + # test instance that doesn't exist anymore sets migration to + # error + instance = instances.pop(0) + vmops.LOG.info(mox.IgnoreArg()) + db.instance_get_by_uuid(fake_context, + instance['uuid']).AndRaise(exception.InstanceNotFound) + vmops.LOG.warn(mox.IgnoreArg()) + db.migration_update(fake_context, 2, {'status': 'error'}) + + # test instance in ERROR/RESIZE_VERIFY sets migration to error + instance = instances.pop(0) + vmops.LOG.info(mox.IgnoreArg()) + db.instance_get_by_uuid(fake_context, + instance['uuid']).AndReturn(instance) + vmops.LOG.warn(mox.IgnoreArg()) + db.migration_update(fake_context, 3, {'status': 'error'}) + + # test instance in ACTIVE/REBOOTING sets migration to error + instance = instances.pop(0) + vmops.LOG.info(mox.IgnoreArg()) + db.instance_get_by_uuid(fake_context, + instance['uuid']).AndReturn(instance) + vmops.LOG.warn(mox.IgnoreArg()) + db.migration_update(fake_context, 4, {'status': 'error'}) + + # test confirm_resize raises and doesn't set migration to error + instance = instances.pop(0) + vmops.LOG.info(mox.IgnoreArg()) + db.instance_get_by_uuid(fake_context, + instance['uuid']).AndReturn(instance) + conn._vmops.compute_api.confirm_resize(fake_context, + instance).AndRaise(test.TestingException) + vmops.LOG.error(mox.IgnoreArg()) + + # test succeeds again (ACTIVE/RESIZE_VERIFY) + instance = instances.pop(0) + vmops.LOG.info(mox.IgnoreArg()) + db.instance_get_by_uuid(fake_context, + instance['uuid']).AndReturn(instance) + conn._vmops.compute_api.confirm_resize(fake_context, + instance) + + self.mox.ReplayAll() + conn._vmops.poll_unconfirmed_resizes(resize_confirm_window) + def test_resize_xenserver_6(self): instance = db.instance_create(self.context, self.instance_values) called = {'resize': False} diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index ac286a768..5d27c6f5a 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -592,7 +592,11 @@ class LibvirtConnection(driver.ComputeDriver): (image_service, image_id) = nova.image.get_image_service( context, instance['image_ref']) - base = image_service.show(context, image_id) + try: + base = image_service.show(context, image_id) + except exception.ImageNotFound: + base = {} + _image_service = nova.image.get_image_service(context, image_href) snapshot_image_service, snapshot_image_id = _image_service snapshot = snapshot_image_service.show(context, snapshot_image_id) @@ -608,7 +612,7 @@ class LibvirtConnection(driver.ComputeDriver): 'ramdisk_id': instance['ramdisk_id'], } } - if 'architecture' in base['properties']: + if 'architecture' in base.get('properties', {}): arch = base['properties']['architecture'] metadata['properties']['architecture'] = arch diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 324ec6ca4..d2177f902 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -32,6 +32,8 @@ from eventlet import greenthread from nova.compute import api as compute from nova.compute import power_state +from nova.compute import task_states +from nova.compute import vm_states from nova import context as nova_context from nova import db from nova import exception @@ -1358,36 +1360,59 @@ class VMOps(object): """Poll for unconfirmed resizes. Look for any unconfirmed resizes that are older than - `resize_confirm_window` and automatically confirm them. + `resize_confirm_window` and automatically confirm them. Check + all migrations despite exceptions when trying to confirm and + yield to other greenthreads on each iteration. """ ctxt = nova_context.get_admin_context() migrations = db.migration_get_all_unconfirmed(ctxt, resize_confirm_window) migrations_info = dict(migration_count=len(migrations), - confirm_window=FLAGS.resize_confirm_window) + confirm_window=resize_confirm_window) if migrations_info["migration_count"] > 0: LOG.info(_("Found %(migration_count)d unconfirmed migrations " "older than %(confirm_window)d seconds") % migrations_info) + def _set_migration_to_error(migration_id, reason): + msg = _("Setting migration %(migration_id)s to error: " + "%(reason)s") % locals() + LOG.warn(msg) + db.migration_update( + ctxt, migration_id, {'status': 'error'}) + for migration in migrations: - LOG.info(_("Automatically confirming migration %d"), - migration['id']) + # NOTE(comstud): Yield to other greenthreads. Putting this + # at the top so we make sure to do it on each iteration. + greenthread.sleep(0) + migration_id = migration['id'] + instance_uuid = migration['instance_uuid'] + msg = _("Automatically confirming migration %(migration_id)s " + "for instance %(instance_uuid)s") + LOG.info(msg % locals()) try: - instance = self.compute_api.get(ctxt, migration.instance_uuid) + instance = db.instance_get_by_uuid(ctxt, instance_uuid) except exception.InstanceNotFound: - LOG.warn(_("Instance for migration %d not found, skipping"), - migration.id) - - # NOTE(sirp): setting to error so we don't keep trying to auto - # confirm this resize - db.migration_update( - ctxt, migration['id'], {'status': 'error'}) - + reason = _("Instance %(instance_uuid)s not found") + _set_migration_to_error(migration_id, reason % locals()) continue - else: + if instance['vm_state'] == vm_states.ERROR: + reason = _("Instance %(instance_uuid)s in ERROR state") + _set_migration_to_error(migration_id, reason % locals()) + continue + if instance['task_state'] != task_states.RESIZE_VERIFY: + task_state = instance['task_state'] + reason = _("Instance %(instance_uuid)s in %(task_state)s " + "task_state, not RESIZE_VERIFY.") + _set_migration_to_error(migration_id, reason % locals()) + continue + try: self.compute_api.confirm_resize(ctxt, instance) + except Exception, e: + msg = _("Error auto-confirming resize for instance " + "%(instance_uuid)s: %(e)s. Will retry later.") + LOG.error(msg % locals()) def get_info(self, instance): """Return data about VM instance.""" |