Fail fast if changing image on a volume-backed server rebuild

We don't support changing the image in the root disk of a volume-backed
server during a rebuild. The API will change the instance.image_ref
attribute to the newly supplied image_href to the rebuild API but the
actual image used by the server after the rebuild will be the original
image, which is wrong.

We need to just fail fast in this case in the API since the compute
service doesn't support it. We also need to ensure that instance.image_ref
doesn't get modified since a missing value here is used by novaclient and
probably other HTTP API users as an indication of a volume-backed server.

See the related mailing list discussion for more details:
http://lists.openstack.org/pipermail/openstack-dev/2017-October/123255.html

Co-Authored-By: Chris Friesen <chris.friesen@windriver.com>
Change-Id: If4c5fb782bb7e7714fb44f8ca9875121e066bc10
Closes-Bug: #1482040
This commit is contained in:
Matt Riedemann 2017-11-16 12:18:34 -05:00
parent 54407afef3
commit 132636dd61
5 changed files with 78 additions and 5 deletions

View File

@ -942,6 +942,7 @@ class ServersController(wsgi.Controller):
except exception.QuotaError as error:
raise exc.HTTPForbidden(explanation=error.format_message())
except (exception.ImageNotActive,
exception.ImageUnacceptable,
exception.FlavorDiskTooSmall,
exception.FlavorMemoryTooSmall,
exception.InvalidMetadata,

View File

@ -2878,7 +2878,8 @@ class API(base.Base):
root_bdm = compute_utils.get_root_bdm(context, instance, bdms)
# Check to see if the image is changing and we have a volume-backed
# server.
# server. The compute doesn't support changing the image in the
# root disk of a volume-backed server, so we need to just fail fast.
is_volume_backed = compute_utils.is_volume_backed_instance(
context, instance, bdms)
if is_volume_backed:
@ -2896,6 +2897,17 @@ class API(base.Base):
volume = self.volume_api.get(context, root_bdm.volume_id)
volume_image_metadata = volume.get('volume_image_metadata', {})
orig_image_ref = volume_image_metadata.get('image_id')
if orig_image_ref != image_href:
# Leave a breadcrumb.
LOG.debug('Requested to rebuild instance with a new image %s '
'for a volume-backed server with image %s in its '
'root volume which is not supported.', image_href,
orig_image_ref, instance=instance)
msg = _('Unable to rebuild with a different image for a '
'volume-backed server.')
raise exception.ImageUnacceptable(
image_id=image_href, reason=msg)
else:
orig_image_ref = instance.image_ref
@ -2938,7 +2950,10 @@ class API(base.Base):
instance.update(options_from_image)
instance.task_state = task_states.REBUILDING
instance.image_ref = image_href
# An empty instance.image_ref is currently used as an indication
# of BFV. Preserve that over a rebuild to not break users.
if not is_volume_backed:
instance.image_ref = image_href
instance.kernel_id = kernel_id or ""
instance.ramdisk_id = ramdisk_id or ""
instance.progress = 0

View File

@ -83,6 +83,6 @@ class RebuildVolumeBackedSameImage(integrated_helpers._IntegratedTestBase,
}
server = self.api.api_post('/servers/%s/action' % server['id'],
rebuild_req_body).body['server']
# FIXME(mriedem): Once bug 1482040 is fixed, the server image ref
# should still be blank for a volume-backed server after the rebuild.
self.assertNotEqual('', server['image'])
# The server image ref should still be blank for a volume-backed server
# after the rebuild.
self.assertEqual('', server['image'])

View File

@ -21,6 +21,7 @@ import mock
from oslo_log import log as logging
from oslo_serialization import base64
from oslo_utils import timeutils
import six
from nova.compute import api as compute_api
from nova.compute import instance_actions
@ -1237,6 +1238,51 @@ class ServerRebuildTestCase(integrated_helpers._IntegratedTestBase,
# assertFlavorMatchesAllocation(flavor, allocs)
assertFlavorsMatchAllocation(flavor, flavor, allocs)
def test_volume_backed_rebuild_different_image(self):
"""Tests that trying to rebuild a volume-backed instance with a
different image than what is in the root disk of the root volume
will result in a 400 BadRequest error.
"""
self.useFixture(nova_fixtures.CinderFixture(self))
# First create our server as normal.
server_req_body = {
# There is no imageRef because this is boot from volume.
'server': {
'flavorRef': '1', # m1.tiny from DefaultFlavorsFixture,
'name': 'test_volume_backed_rebuild_different_image',
# We don't care about networking for this test. This requires
# microversion >= 2.37.
'networks': 'none',
'block_device_mapping_v2': [{
'boot_index': 0,
'uuid': nova_fixtures.CinderFixture.IMAGE_BACKED_VOL,
'source_type': 'volume',
'destination_type': 'volume'
}]
}
}
server = self.api.post_server(server_req_body)
server = self._wait_for_state_change(self.api, server, 'ACTIVE')
# For a volume-backed server, the image ref will be an empty string
# in the server response.
self.assertEqual('', server['image'])
# Now rebuild the server with a different image than was used to create
# our fake volume.
rebuild_image_ref = (
nova.tests.unit.image.fake.AUTO_DISK_CONFIG_ENABLED_IMAGE_UUID)
rebuild_req_body = {
'rebuild': {
'imageRef': rebuild_image_ref
}
}
resp = self.api.api_post('/servers/%s/action' % server['id'],
rebuild_req_body, check_response_status=[400])
# Assert that we failed because of the image change and not something
# else.
self.assertIn('Unable to rebuild with a different image for a '
'volume-backed server', six.text_type(resp))
class ProviderUsageBaseTestCase(test.TestCase,
integrated_helpers.InstanceHelperMixin):

View File

@ -0,0 +1,11 @@
---
fixes:
- |
A fix is made for `bug 1482040`_ where a request to rebuild a volume-backed
server with a new image which is different than what is in the root volume
will now fail with a `400 Bad Request` response. The compute API would
previously return a `202 Accepted` response but the backend compute service
does not replace the image in the root disk so the API behavior was always
wrong and is now explicit about the failure.
.. _bug 1482040: https://bugs.launchpad.net/nova/+bug/1482040