diff --git a/nova/api/openstack/compute/plugins/v3/servers.py b/nova/api/openstack/compute/plugins/v3/servers.py index 70c0523be5ba..b324a4c1f76c 100644 --- a/nova/api/openstack/compute/plugins/v3/servers.py +++ b/nova/api/openstack/compute/plugins/v3/servers.py @@ -903,13 +903,11 @@ class ServersController(wsgi.Controller): img = instance['image_ref'] if not img: props = bdms.root_metadata( - context, self.compute_api.image_service, + context, self.compute_api.image_api, self.compute_api.volume_api) image_meta = {'properties': props} else: - src_image = self.compute_api.\ - image_service.show(context, img) - image_meta = dict(src_image) + image_meta = self.compute_api.image_api.get(context, img) image = self.compute_api.snapshot_volume_backed( context, diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index b11db3b0bd19..831eb9ff9b80 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -1443,13 +1443,11 @@ class Controller(wsgi.Controller): img = instance['image_ref'] if not img: props = bdms.root_metadata( - context, self.compute_api.image_service, + context, self.compute_api.image_api, self.compute_api.volume_api) image_meta = {'properties': props} else: - src_image = self.compute_api.image_service.\ - show(context, img) - image_meta = dict(src_image) + image_meta = self.compute_api.image_api.get(context, img) image = self.compute_api.snapshot_volume_backed( context, diff --git a/nova/compute/api.py b/nova/compute/api.py index 978272986168..cfdeba394737 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -43,7 +43,7 @@ from nova import crypto from nova.db import base from nova import exception from nova import hooks -from nova.image import glance +from nova import image from nova import network from nova.network import model as network_model from nova.network.security_group import openstack_driver @@ -239,11 +239,9 @@ def _diff_dict(orig, new): class API(base.Base): """API for interacting with the compute manager.""" - def __init__(self, image_service=None, network_api=None, volume_api=None, + def __init__(self, image_api=None, network_api=None, volume_api=None, security_group_api=None, **kwargs): - self.image_service = (image_service or - glance.get_default_image_service()) - + self.image_api = image_api or image.API() self.network_api = network_api or network.API() self.volume_api = volume_api or volume.API() self.security_group_api = (security_group_api or @@ -461,8 +459,8 @@ class API(base.Base): return self.network_api.validate_networks(context, requested_networks, max_count) - @staticmethod - def _handle_kernel_and_ramdisk(context, kernel_id, ramdisk_id, image): + def _handle_kernel_and_ramdisk(self, context, kernel_id, ramdisk_id, + image): """Choose kernel and ramdisk appropriate for the instance. The kernel and ramdisk can be chosen in one of three ways: @@ -489,14 +487,19 @@ class API(base.Base): # Verify kernel and ramdisk exist (fail-fast) if kernel_id is not None: - image_service, kernel_id = glance.get_remote_image_service( - context, kernel_id) - image_service.show(context, kernel_id) + kernel_image = self.image_api.get(context, kernel_id) + # kernel_id could have been a URI, not a UUID, so to keep behaviour + # from before, which leaked that implementation detail out to the + # caller, we return the image UUID of the kernel image and ramdisk + # image (below) and not any image URIs that might have been + # supplied. + # TODO(jaypipes): Get rid of this silliness once we move to a real + # Image object and hide all of that stuff within nova.image.api. + kernel_id = kernel_image['id'] if ramdisk_id is not None: - image_service, ramdisk_id = glance.get_remote_image_service( - context, ramdisk_id) - image_service.show(context, ramdisk_id) + ramdisk_image = self.image_api.get(context, ramdisk_id) + ramdisk_id = ramdisk_image['id'] return kernel_id, ramdisk_id @@ -681,10 +684,8 @@ class API(base.Base): if not image_href: return None, {} - (image_service, image_id) = glance.get_remote_image_service( - context, image_href) - image = image_service.show(context, image_id) - return image_id, image + image = self.image_api.get(context, image_href) + return image['id'], image def _checks_for_create_and_rebuild(self, context, image_id, image, instance_type, metadata, @@ -868,7 +869,7 @@ class API(base.Base): if bdm.get('image_id'): try: image_id = bdm['image_id'] - image_meta = self.image_service.show(context, image_id) + image_meta = self.image_api.get(context, image_id) return image_meta.get('properties', {}) except Exception: raise exception.InvalidBDMImage(id=image_id) @@ -1409,7 +1410,7 @@ class API(base.Base): "from shelved instance..."), snapshot_id, instance=instance) try: - self.image_service.delete(context, snapshot_id) + self.image_api.delete(context, snapshot_id) except (exception.ImageNotFound, exception.ImageNotAuthorized) as exc: LOG.warning(_("Failed to delete snapshot " @@ -1988,7 +1989,7 @@ class API(base.Base): } image_ref = instance.image_ref sent_meta = compute_utils.get_image_metadata( - context, self.image_service, image_ref, instance) + context, self.image_api, image_ref, instance) sent_meta['name'] = name sent_meta['is_public'] = False @@ -1997,7 +1998,7 @@ class API(base.Base): properties.update(extra_properties or {}) sent_meta['properties'].update(properties) - return self.image_service.create(context, sent_meta) + return self.image_api.create(context, sent_meta) @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED]) def snapshot_volume_backed(self, context, instance, image_meta, name, @@ -2062,7 +2063,7 @@ class API(base.Base): # hence the zero size image_meta['size'] = 0 - return self.image_service.create(context, image_meta, data='') + return self.image_api.create(context, image_meta) @wrap_check_policy @check_instance_lock diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8434a37231d0..4b5c955173da 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -56,6 +56,7 @@ from nova import consoleauth import nova.context from nova import exception from nova import hooks +from nova import image from nova.image import glance from nova import manager from nova import network @@ -354,8 +355,7 @@ def delete_image_on_error(function): LOG.debug("Cleaning up image %s" % image_id, exc_info=True, instance=instance) try: - image_service = glance.get_default_image_service() - image_service.delete(context, image_id) + self.image_api.delete(context, image_id) except Exception: LOG.exception(_("Error while trying to clean up image %s") % image_id, instance=instance) @@ -419,12 +419,6 @@ def aggregate_object_compat(function): return decorated_function -def _get_image_meta(context, image_ref): - image_service, image_id = glance.get_remote_image_service(context, - image_ref) - return image_service.show(context, image_id) - - class InstanceEvents(object): def __init__(self): self._events = {} @@ -575,6 +569,7 @@ class ComputeManager(manager.Manager): self.virtapi = ComputeVirtAPI(self) self.network_api = network.API() self.volume_api = volume.API() + self.image_api = image.API() self._last_host_check = 0 self._last_bw_usage_poll = 0 self._bw_usage_supported = True @@ -2502,13 +2497,14 @@ class ComputeManager(manager.Manager): instance.save() if image_ref: - image_meta = _get_image_meta(context, image_ref) + image_meta = self.image_api.get(context, image_ref) else: image_meta = {} # This instance.exists message should contain the original # image_ref, not the new one. Since the DB has been updated # to point to the new one... we have to override it. + #TODO(jaypipes): Move generate_image_url() into the nova.image.api orig_image_ref_url = glance.generate_image_url(orig_image_ref) extra_usage_info = {'image_ref_url': orig_image_ref_url} self.conductor_api.notify_usage_exists(context, @@ -2837,13 +2833,12 @@ class ComputeManager(manager.Manager): :param rotation: int representing how many backups to keep around; None if rotation shouldn't be used (as in the case of snapshots) """ - image_service = glance.get_default_image_service() filters = {'property-image_type': 'backup', 'property-backup_type': backup_type, 'property-instance_uuid': instance.uuid} - images = image_service.detail(context, filters=filters, - sort_key='created_at', sort_dir='desc') + images = self.image_api.get_all(context, filters=filters, + sort_key='created_at', sort_dir='desc') num_images = len(images) LOG.debug("Found %(num_images)d images (rotation: %(rotation)d)", {'num_images': num_images, 'rotation': rotation}, @@ -2860,7 +2855,7 @@ class ComputeManager(manager.Manager): image_id = image['id'] LOG.debug("Deleting image %s", image_id, instance=instance) - image_service.delete(context, image_id) + self.image_api.delete(context, image_id) @object_compat @wrap_exception() @@ -2960,9 +2955,7 @@ class ComputeManager(manager.Manager): ' using instance\'s current image')) rescue_image_ref = instance['image_ref'] - image_service, image_id = glance.get_remote_image_service( - context, rescue_image_ref) - image_meta = compute_utils.get_image_metadata(context, image_service, + image_meta = compute_utils.get_image_metadata(context, self.image_api, rescue_image_ref, instance) # NOTE(belliott) bug #1227350 - xenapi needs the actual image id @@ -3941,8 +3934,7 @@ class ComputeManager(manager.Manager): if image: instance.image_ref = shelved_image_ref - image_service = glance.get_default_image_service() - image_service.delete(context, image['id']) + self.image_api.delete(context, image['id']) self._unshelve_instance_key_restore(instance, scrubbed_keys) instance.power_state = self._get_power_state(context, instance) @@ -4400,10 +4392,8 @@ class ComputeManager(manager.Manager): % dict(ports=len(network_info))) raise exception.InterfaceAttachFailed(instance=instance) image_ref = instance.get('image_ref') - image_service, image_id = glance.get_remote_image_service( - context, image_ref) image_meta = compute_utils.get_image_metadata( - context, image_service, image_ref, instance) + context, self.image_api, image_ref, instance) self.driver.attach_interface(instance, image_meta, network_info[0]) return network_info[0] diff --git a/nova/compute/utils.py b/nova/compute/utils.py index f29563987b99..1eea08449b24 100644 --- a/nova/compute/utils.py +++ b/nova/compute/utils.py @@ -199,13 +199,14 @@ def _get_unused_letter(used_letters): return letters[0] -def get_image_metadata(context, image_service, image_id, instance): +def get_image_metadata(context, image_api, image_id_or_uri, instance): # If the base image is still available, get its metadata try: - image = image_service.show(context, image_id) + image = image_api.get(context, image_id_or_uri) except Exception as e: LOG.warning(_("Can't access image %(image_id)s: %(error)s"), - {"image_id": image_id, "error": e}, instance=instance) + {"image_id": image_id_or_uri, "error": e}, + instance=instance) image_system_meta = {} else: flavor = flavors.extract_flavor(instance) diff --git a/nova/image/__init__.py b/nova/image/__init__.py index e69de29bb2d1..21cd55feb62e 100644 --- a/nova/image/__init__.py +++ b/nova/image/__init__.py @@ -0,0 +1,17 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +def API(): + # Needed to prevent circular import... + import nova.image.api + return nova.image.api.API() diff --git a/nova/image/api.py b/nova/image/api.py new file mode 100644 index 000000000000..36ab6870aef8 --- /dev/null +++ b/nova/image/api.py @@ -0,0 +1,130 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Main abstraction layer for retrieving and storing information about disk +images used by the compute layer. +""" + +from nova.image import glance + + +class API(object): + + """Responsible for exposing a relatively stable internal API for other + modules in Nova to retrieve information about disk images. This API + attempts to match the nova.volume.api and nova.network.api calling + interface. + """ + + def _get_session_and_image_id(self, context, id_or_uri): + """Returns a tuple of (session, image_id). If the supplied `id_or_uri` + is an image ID, then the default client session will be returned + for the context's user, along with the image ID. If the supplied + `id_or_uri` parameter is a URI, then a client session connecting to + the URI's image service endpoint will be returned along with a + parsed image ID from that URI. + + :param context: The `nova.context.Context` object for the request + :param id_or_uri: A UUID identifier or an image URI to look up image + information for. + """ + return glance.get_remote_image_service(context, id_or_uri) + + def _get_session(self, _context): + """Returns a client session that can be used to query for image + information. + + :param context: The `nova.context.Context` object for the request + """ + #TODO(jaypipes): Refactor glance.get_remote_image_service and + # glance.get_default_image_service into a single + # method that takes a context and actually respects + # it, returning a real session object that keeps + # the context alive... + return glance.get_default_image_service() + + def get_all(self, context, **kwargs): + """Retrieves all information records about all disk images available + to show to the requesting user. If the requesting user is an admin, + all images in an ACTIVE status are returned. If the requesting user + is not an admin, the all public images and all private images that + are owned by the requesting user in the ACTIVE status are returned. + + :param context: The `nova.context.Context` object for the request + :param **kwargs: A dictionary of filter and pagination values that + may be passed to the underlying image info driver. + """ + session = self._get_session(context) + return session.detail(context, **kwargs) + + def get(self, context, id_or_uri): + """Retrieves the information record for a single disk image. If the + supplied identifier parameter is a UUID, the default driver will + be used to return information about the image. If the supplied + identifier is a URI, then the driver that matches that URI endpoint + will be used to query for image information. + + :param context: The `nova.context.Context` object for the request + :param id_or_uri: A UUID identifier or an image URI to look up image + information for. + """ + session, image_id = self._get_session_and_image_id(context, id_or_uri) + return session.show(context, image_id) + + def create(self, context, image_info, data=None): + """Creates a new image record, optionally passing the image bits to + backend storage. + + :param context: The `nova.context.Context` object for the request + :param image_info: A dict of information about the image that is + passed to the image registry. + :param data: Optional file handle or bytestream iterator that is + passed to backend storage. + """ + session = self._get_session(context) + return session.create(context, image_info, data=data) + + def update(self, context, id_or_uri, image_info, + data=None, purge_props=False): + """Update the information about an image, optionally along with a file + handle or bytestream iterator for image bits. If the optional file + handle for updated image bits is supplied, the image may not have + already uploaded bits for the image. + + :param context: The `nova.context.Context` object for the request + :param id_or_uri: A UUID identifier or an image URI to look up image + information for. + :param image_info: A dict of information about the image that is + passed to the image registry. + :param data: Optional file handle or bytestream iterator that is + passed to backend storage. + :param purge_props: Optional, defaults to True. If set, the backend + image registry will clear all image properties + and replace them the image properties supplied + in the image_info dictionary's 'properties' + collection. + """ + session, image_id = self._get_session_and_image_id(context, id_or_uri) + return session.update(context, image_id, image_info, data=data, + purge_props=purge_props) + + def delete(self, context, id_or_uri): + """Delete the information about an image and mark the image bits for + deletion. + + :param context: The `nova.context.Context` object for the request + :param id_or_uri: A UUID identifier or an image URI to look up image + information for. + """ + session, image_id = self._get_session_and_image_id(context, id_or_uri) + return session.delete(context, image_id) diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index 2ce95d8f467b..f70c12aa93b3 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -549,7 +549,7 @@ class ComputeVolumeTestCase(BaseTestCase): self.test_boot_volume_metadata(metadata=False) def test_boot_image_metadata(self, metadata=True): - def image_api_show(*args, **kwargs): + def image_api_get(*args, **kwargs): if metadata: return { 'properties': {'img_test_key': 'img_test_value'} @@ -557,7 +557,7 @@ class ComputeVolumeTestCase(BaseTestCase): else: return {} - self.stubs.Set(self.compute_api.image_service, 'show', image_api_show) + self.stubs.Set(self.compute_api.image_api, 'get', image_api_get) block_device_mapping = [{ 'boot_index': 0, @@ -2007,10 +2007,9 @@ class ComputeTestCase(BaseTestCase): self.assertEqual('some_random_state', inst_obj['vm_state']) @mock.patch.object(nova.compute.utils, "get_image_metadata") - @mock.patch.object(nova.image.glance, "get_remote_image_service") @mock.patch.object(nova.virt.fake.FakeDriver, "rescue") def test_rescue_with_image_specified(self, mock_rescue, - mock_get_remote_image_service, mock_get_image_metadata): + mock_get_image_metadata): image_ref = "image-ref" rescue_image_meta = {} @@ -2023,16 +2022,12 @@ class ComputeTestCase(BaseTestCase): mock_context.elevated.return_value = ctxt mock_get_image_metadata.return_value = rescue_image_meta - mock_image_service = "image_service" - mock_get_remote_image_service.return_value = (mock_image_service, "id") self.compute.rescue_instance(mock_context, instance=instance, rescue_password="password", rescue_image_ref=image_ref) - mock_get_remote_image_service.assert_called_with(ctxt, - image_ref) mock_get_image_metadata.assert_called_with(ctxt, - mock_image_service, + self.compute.image_api, image_ref, instance) mock_rescue.assert_called_with(ctxt, instance, [], rescue_image_meta, 'password') @@ -2040,11 +2035,9 @@ class ComputeTestCase(BaseTestCase): self._objectify(instance), [], []) @mock.patch.object(nova.compute.utils, "get_image_metadata") - @mock.patch.object(nova.image.glance, "get_remote_image_service") @mock.patch.object(nova.virt.fake.FakeDriver, "rescue") def test_rescue_with_base_image_when_image_not_specified(self, - mock_rescue, mock_get_remote_image_service, - mock_get_image_metadata): + mock_rescue, mock_get_image_metadata): image_ref = "image-ref" system_meta = {"image_base_image_ref": image_ref} @@ -2059,16 +2052,12 @@ class ComputeTestCase(BaseTestCase): mock_context.elevated.return_value = ctxt mock_get_image_metadata.return_value = rescue_image_meta - mock_image_service = "image_service" - mock_get_remote_image_service.return_value = (mock_image_service, "id") self.compute.rescue_instance(mock_context, instance=instance, rescue_password="password") - mock_get_remote_image_service.assert_called_with(ctxt, - image_ref) mock_get_image_metadata.assert_called_with(ctxt, - mock_image_service, + self.compute.image_api, image_ref, instance) mock_rescue.assert_called_with(ctxt, instance, [], rescue_image_meta, 'password') diff --git a/nova/tests/compute/test_compute_api.py b/nova/tests/compute/test_compute_api.py index e60d2ee32e37..83cfcbb09e4f 100644 --- a/nova/tests/compute/test_compute_api.py +++ b/nova/tests/compute/test_compute_api.py @@ -457,20 +457,20 @@ class _ComputeAPIUnitTestMixIn(object): migration['source_compute'], fake_quotas.reservations, cast=False) def _test_delete_shelved_part(self, inst): - image_service = self.compute_api.image_service - self.mox.StubOutWithMock(image_service, 'delete') + image_api = self.compute_api.image_api + self.mox.StubOutWithMock(image_api, 'delete') snapshot_id = inst.system_metadata.get('shelved_image_id') if snapshot_id == SHELVED_IMAGE: - image_service.delete(self.context, snapshot_id).AndReturn(True) + image_api.delete(self.context, snapshot_id).AndReturn(True) elif snapshot_id == SHELVED_IMAGE_NOT_FOUND: - image_service.delete(self.context, snapshot_id).AndRaise( + image_api.delete(self.context, snapshot_id).AndRaise( exception.ImageNotFound(image_id=snapshot_id)) elif snapshot_id == SHELVED_IMAGE_NOT_AUTHORIZED: - image_service.delete(self.context, snapshot_id).AndRaise( + image_api.delete(self.context, snapshot_id).AndRaise( exception.ImageNotAuthorized(image_id=snapshot_id)) elif snapshot_id == SHELVED_IMAGE_EXCEPTION: - image_service.delete(self.context, snapshot_id).AndRaise( + image_api.delete(self.context, snapshot_id).AndRaise( test.TestingException("Unexpected error")) def _test_downed_host_part(self, inst, updates, delete_time, delete_type): @@ -1388,7 +1388,7 @@ class _ComputeAPIUnitTestMixIn(object): extra_props = dict(cow='moo', cat='meow') self.mox.StubOutWithMock(compute_utils, 'get_image_metadata') - self.mox.StubOutWithMock(self.compute_api.image_service, + self.mox.StubOutWithMock(self.compute_api.image_api, 'create') self.mox.StubOutWithMock(instance, 'save') self.mox.StubOutWithMock(self.compute_api.compute_rpcapi, @@ -1424,11 +1424,11 @@ class _ComputeAPIUnitTestMixIn(object): expected_props['backup_type'] = 'fake-backup-type' compute_utils.get_image_metadata( - self.context, self.compute_api.image_service, + self.context, self.compute_api.image_api, FAKE_IMAGE_REF, instance).AndReturn(expected_meta) fake_image = dict(id='fake-image-id') - mock_method = self.compute_api.image_service.create( + mock_method = self.compute_api.image_api.create( self.context, expected_meta) if create_fails: mock_method.AndRaise(test.TestingException()) @@ -1559,7 +1559,7 @@ class _ComputeAPIUnitTestMixIn(object): def fake_get_all_by_instance(context, instance, use_slave=False): return copy.deepcopy(instance_bdms) - def fake_image_create(context, image_meta, data): + def fake_image_create(context, image_meta, data=None): self.assertThat(image_meta, matchers.DictMatches(expect_meta)) def fake_volume_get(context, volume_id): @@ -1570,7 +1570,7 @@ class _ComputeAPIUnitTestMixIn(object): self.stubs.Set(db, 'block_device_mapping_get_all_by_instance', fake_get_all_by_instance) - self.stubs.Set(self.compute_api.image_service, 'create', + self.stubs.Set(self.compute_api.image_api, 'create', fake_image_create) self.stubs.Set(self.compute_api.volume_api, 'get', fake_volume_get) diff --git a/nova/tests/compute/test_compute_utils.py b/nova/tests/compute/test_compute_utils.py index bbf669b04406..42bf8d1b1c1a 100644 --- a/nova/tests/compute/test_compute_utils.py +++ b/nova/tests/compute/test_compute_utils.py @@ -628,8 +628,8 @@ class ComputeGetImageMetadataTestCase(test.TestCase): "properties": {}, } - self.image_service = nova.tests.image.fake._FakeImageService() - self.stubs.Set(self.image_service, 'show', self._fake_show) + self.mock_image_api = mock.Mock() + self.mock_image_api.get.return_value = self.image self.ctx = context.RequestContext('fake', 'fake') @@ -660,24 +660,19 @@ class ComputeGetImageMetadataTestCase(test.TestCase): self.ctx, objects.Instance(), self.instance, expected_attrs=instance_obj.INSTANCE_DEFAULT_FIELDS) - def _fake_show(self, ctx, image_id): - return self.image - def test_get_image_meta(self): image_meta = compute_utils.get_image_metadata( - self.ctx, self.image_service, 'fake-image', self.instance_obj) + self.ctx, self.mock_image_api, 'fake-image', self.instance_obj) self.image['properties'] = 'DONTCARE' self.assertThat(self.image, matchers.DictMatches(image_meta)) def test_get_image_meta_no_image(self): - def fake_show(ctx, image_id): - raise exception.ImageNotFound(image_id='fake-image') - - self.stubs.Set(self.image_service, 'show', fake_show) + e = exception.ImageNotFound(image_id='fake-image') + self.mock_image_api.get.side_effect = e image_meta = compute_utils.get_image_metadata( - self.ctx, self.image_service, 'fake-image', self.instance_obj) + self.ctx, self.mock_image_api, 'fake-image', self.instance_obj) self.image['properties'] = 'DONTCARE' # NOTE(danms): The trip through system_metadata will stringify things @@ -691,23 +686,21 @@ class ComputeGetImageMetadataTestCase(test.TestCase): del self.instance['system_metadata'][k] image_meta = compute_utils.get_image_metadata( - self.ctx, self.image_service, 'fake-image', self.instance_obj) + self.ctx, self.mock_image_api, 'fake-image', self.instance_obj) self.image['properties'] = 'DONTCARE' self.assertThat(self.image, matchers.DictMatches(image_meta)) def test_get_image_meta_no_image_no_image_system_meta(self): - def fake_show(ctx, image_id): - raise exception.ImageNotFound(image_id='fake-image') - - self.stubs.Set(self.image_service, 'show', fake_show) + e = exception.ImageNotFound(image_id='fake-image') + self.mock_image_api.get.side_effect = e for k in self.instance['system_metadata'].keys(): if k.startswith('image_'): del self.instance['system_metadata'][k] image_meta = compute_utils.get_image_metadata( - self.ctx, self.image_service, 'fake-image', self.instance_obj) + self.ctx, self.mock_image_api, 'fake-image', self.instance_obj) expected = {'properties': 'DONTCARE'} self.assertThat(expected, matchers.DictMatches(image_meta)) diff --git a/nova/tests/compute/test_shelve.py b/nova/tests/compute/test_shelve.py index 245c3d20c58c..bc3cfb3602a5 100644 --- a/nova/tests/compute/test_shelve.py +++ b/nova/tests/compute/test_shelve.py @@ -374,7 +374,7 @@ class ShelveComputeAPITestCase(test_compute.BaseTestCase): # sets a fake method. self2.images = {} - def fake_create(self2, ctxt, metadata): + def fake_create(self2, ctxt, metadata, data=None): self.assertEqual(metadata['name'], 'vm01-shelved') metadata['id'] = '8b24ed3f-ee57-43bc-bc2e-fb2e9482bc42' return metadata diff --git a/nova/tests/virt/libvirt/test_libvirt.py b/nova/tests/virt/libvirt/test_libvirt.py index e4a17884e0f2..66d74688cf98 100644 --- a/nova/tests/virt/libvirt/test_libvirt.py +++ b/nova/tests/virt/libvirt/test_libvirt.py @@ -461,7 +461,8 @@ class LibvirtConnTestCase(test.TestCase): flavor = db.flavor_get(self.context, 5) sys_meta = flavors.save_flavor_info({}, flavor) - nova.tests.image.fake.stub_out_image_service(self.stubs) + self.image_service = nova.tests.image.fake.stub_out_image_service( + self.stubs) self.test_instance = { 'uuid': '32dfcb37-5af1-552b-357c-be8c3aa38310', 'memory_kb': '1024000', @@ -2666,9 +2667,6 @@ class LibvirtConnTestCase(test.TestCase): self.flags(snapshots_directory='./', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assign different image_ref from nova/images/fakes for testing ami test_instance = copy.deepcopy(self.test_instance) test_instance["image_ref"] = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77' @@ -2682,7 +2680,7 @@ class LibvirtConnTestCase(test.TestCase): 'status': 'creating', 'properties': properties} # Create new image. It will be updated in snapshot method # To work with it from snapshot, the single image_service is needed - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -2696,7 +2694,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -2718,9 +2716,6 @@ class LibvirtConnTestCase(test.TestCase): virt_type='lxc', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assign different image_ref from nova/images/fakes for testing ami test_instance = copy.deepcopy(self.test_instance) test_instance["image_ref"] = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77' @@ -2734,7 +2729,7 @@ class LibvirtConnTestCase(test.TestCase): 'status': 'creating', 'properties': properties} # Create new image. It will be updated in snapshot method # To work with it from snapshot, the single image_service is needed - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -2748,7 +2743,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -2768,9 +2763,6 @@ class LibvirtConnTestCase(test.TestCase): self.flags(snapshots_directory='./', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assuming that base image already exists in image_service instance_ref = db.instance_create(self.context, self.test_instance) properties = {'instance_id': instance_ref['id'], @@ -2780,7 +2772,7 @@ class LibvirtConnTestCase(test.TestCase): 'status': 'creating', 'properties': properties} # Create new image. It will be updated in snapshot method # To work with it from snapshot, the single image_service is needed - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -2799,7 +2791,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -2821,9 +2813,6 @@ class LibvirtConnTestCase(test.TestCase): virt_type='lxc', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assuming that base image already exists in image_service instance_ref = db.instance_create(self.context, self.test_instance) properties = {'instance_id': instance_ref['id'], @@ -2833,7 +2822,7 @@ class LibvirtConnTestCase(test.TestCase): 'status': 'creating', 'properties': properties} # Create new image. It will be updated in snapshot method # To work with it from snapshot, the single image_service is needed - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -2852,7 +2841,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -2874,9 +2863,6 @@ class LibvirtConnTestCase(test.TestCase): snapshots_directory='./', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assuming that base image already exists in image_service instance_ref = db.instance_create(self.context, self.test_instance) properties = {'instance_id': instance_ref['id'], @@ -2886,7 +2872,7 @@ class LibvirtConnTestCase(test.TestCase): 'status': 'creating', 'properties': properties} # Create new image. It will be updated in snapshot method # To work with it from snapshot, the single image_service is needed - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -2900,7 +2886,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -2923,9 +2909,6 @@ class LibvirtConnTestCase(test.TestCase): virt_type='lxc', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assuming that base image already exists in image_service instance_ref = db.instance_create(self.context, self.test_instance) properties = {'instance_id': instance_ref['id'], @@ -2935,7 +2918,7 @@ class LibvirtConnTestCase(test.TestCase): 'status': 'creating', 'properties': properties} # Create new image. It will be updated in snapshot method # To work with it from snapshot, the single image_service is needed - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -2949,7 +2932,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -2970,9 +2953,6 @@ class LibvirtConnTestCase(test.TestCase): self.flags(snapshots_directory='./', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assign different image_ref from nova/images/fakes for # testing different base image test_instance = copy.deepcopy(self.test_instance) @@ -2987,7 +2967,7 @@ class LibvirtConnTestCase(test.TestCase): 'status': 'creating', 'properties': properties} # Create new image. It will be updated in snapshot method # To work with it from snapshot, the single image_service is needed - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -3000,7 +2980,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -3021,9 +3001,6 @@ class LibvirtConnTestCase(test.TestCase): virt_type='lxc', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assign different image_ref from nova/images/fakes for # testing different base image test_instance = copy.deepcopy(self.test_instance) @@ -3038,7 +3015,7 @@ class LibvirtConnTestCase(test.TestCase): 'status': 'creating', 'properties': properties} # Create new image. It will be updated in snapshot method # To work with it from snapshot, the single image_service is needed - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -3051,7 +3028,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -3071,9 +3048,6 @@ class LibvirtConnTestCase(test.TestCase): self.flags(snapshots_directory='./', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assign a non-existent image test_instance = copy.deepcopy(self.test_instance) test_instance["image_ref"] = '661122aa-1234-dede-fefe-babababababa' @@ -3084,7 +3058,7 @@ class LibvirtConnTestCase(test.TestCase): snapshot_name = 'test-snap' sent_meta = {'name': snapshot_name, 'is_public': False, 'status': 'creating', 'properties': properties} - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -3097,7 +3071,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -3118,9 +3092,6 @@ class LibvirtConnTestCase(test.TestCase): virt_type='lxc', group='libvirt') - # Start test - image_service = nova.tests.image.fake.FakeImageService() - # Assign a non-existent image test_instance = copy.deepcopy(self.test_instance) test_instance["image_ref"] = '661122aa-1234-dede-fefe-babababababa' @@ -3131,7 +3102,7 @@ class LibvirtConnTestCase(test.TestCase): snapshot_name = 'test-snap' sent_meta = {'name': snapshot_name, 'is_public': False, 'status': 'creating', 'properties': properties} - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -3144,7 +3115,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['status'], 'active') @@ -3164,8 +3135,6 @@ class LibvirtConnTestCase(test.TestCase): self.flags(snapshots_directory='./', group='libvirt') - image_service = nova.tests.image.fake.FakeImageService() - # Assign an image with an architecture defined (x86_64) test_instance = copy.deepcopy(self.test_instance) test_instance["image_ref"] = 'a440c04b-79fa-479c-bed1-0b816eaec379' @@ -3179,7 +3148,7 @@ class LibvirtConnTestCase(test.TestCase): snapshot_name = 'test-snap' sent_meta = {'name': snapshot_name, 'is_public': False, 'status': 'creating', 'properties': properties} - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -3192,7 +3161,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['properties']['architecture'], 'fake_arch') @@ -3215,8 +3184,6 @@ class LibvirtConnTestCase(test.TestCase): self.flags(snapshots_directory='./', group='libvirt') - image_service = nova.tests.image.fake.FakeImageService() - # Assign a non-existent image test_instance = copy.deepcopy(self.test_instance) test_instance["image_ref"] = '661122aa-1234-dede-fefe-babababababa' @@ -3229,7 +3196,7 @@ class LibvirtConnTestCase(test.TestCase): snapshot_name = 'test-snap' sent_meta = {'name': snapshot_name, 'is_public': False, 'status': 'creating', 'properties': properties} - recv_meta = image_service.create(context, sent_meta) + recv_meta = self.image_service.create(context, sent_meta) self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_conn') libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup @@ -3242,7 +3209,7 @@ class LibvirtConnTestCase(test.TestCase): conn.snapshot(self.context, instance_ref, recv_meta['id'], func_call_matcher.call) - snapshot = image_service.show(context, recv_meta['id']) + snapshot = self.image_service.show(context, recv_meta['id']) self.assertIsNone(func_call_matcher.match()) self.assertEqual(snapshot['properties']['image_state'], 'available') self.assertEqual(snapshot['properties']['os_type'], diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 661954f9e57a..c675c04f2140 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -69,7 +69,7 @@ from nova.compute import utils as compute_utils from nova.compute import vm_mode from nova import context as nova_context from nova import exception -from nova.image import glance +from nova import image from nova.objects import block_device as block_device_obj from nova.objects import flavor as flavor_obj from nova.objects import instance as instance_obj @@ -415,6 +415,7 @@ class LibvirtDriver(driver.ComputeDriver): self.disk_cachemodes[disk_type] = cache_mode self._volume_api = volume.API() + self._image_api = image.API() @property def disk_cachemode(self): @@ -1483,15 +1484,12 @@ class LibvirtDriver(driver.ComputeDriver): except exception.InstanceNotFound: raise exception.InstanceNotRunning(instance_id=instance['uuid']) - (image_service, image_id) = glance.get_remote_image_service( - context, instance['image_ref']) + base_image_ref = instance['image_ref'] base = compute_utils.get_image_metadata( - context, image_service, image_id, instance) + context, self._image_api, base_image_ref, instance) - _image_service = glance.get_remote_image_service(context, image_href) - snapshot_image_service, snapshot_image_id = _image_service - snapshot = snapshot_image_service.show(context, snapshot_image_id) + snapshot = self._image_api.get(context, image_href) disk_path = libvirt_utils.find_disk(virt_dom) source_format = libvirt_utils.get_disk_type(disk_path) @@ -1591,10 +1589,10 @@ class LibvirtDriver(driver.ComputeDriver): update_task_state(task_state=task_states.IMAGE_UPLOADING, expected_state=task_states.IMAGE_PENDING_UPLOAD) with libvirt_utils.file_open(out_path) as image_file: - image_service.update(context, - image_href, - metadata, - image_file) + self._image_api.update(context, + image_href, + metadata, + image_file) LOG.info(_("Snapshot image upload complete"), instance=instance) @@ -2088,11 +2086,9 @@ class LibvirtDriver(driver.ComputeDriver): image_meta = utils.get_image_from_system_metadata(system_meta) if not image_meta: image_ref = instance.get('image_ref') - service, image_id = glance.get_remote_image_service(context, - image_ref) image_meta = compute_utils.get_image_metadata(context, - service, - image_id, + self._image_api, + image_ref, instance) disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type, @@ -3456,10 +3452,9 @@ class LibvirtDriver(driver.ComputeDriver): block_device_info=None, write_to_disk=False): # We should get image metadata every time for generating xml if image_meta is None: - (image_service, image_id) = glance.get_remote_image_service( - context, instance['image_ref']) + image_ref = instance['image_ref'] image_meta = compute_utils.get_image_metadata( - context, image_service, image_id, instance) + context, self._image_api, image_ref, instance) # NOTE(danms): Stringifying a NetworkInfo will take a lock. Do # this ahead of time so that we don't acquire it while also # holding the logging lock.