From cf34a32820cc21dd9b9075d5476e050ecd8b34ac Mon Sep 17 00:00:00 2001 From: Andrea Rosa Date: Mon, 11 Jan 2016 15:50:09 +0000 Subject: [PATCH] Enable volume operations for shelved instances This change enables the attach and detach volume operations for instances which are in shelve and shelved_offloaded state. The code to manage the actual attach and detach is implemented by this change: https://review.openstack.org/259528 New tempest tests are going to be written for testing the new feature. APIImpact DocImpact by this change we are allowing operations which were not allowed before, we need to track this change in the documentation. Partially implements blueprint: volume-ops-when-shelved Change-Id: I43b67a50d998d43a6ff78c917093c513231b6ff2 --- .../versions/v21-version-get-resp.json | 2 +- .../versions/versions-get-resp.json | 2 +- nova/api/openstack/api_version_request.py | 5 +- nova/api/openstack/compute/volumes.py | 26 +++++- .../openstack/rest_api_version_history.rst | 6 ++ nova/compute/api.py | 6 +- .../versions/v21-version-get-resp.json.tpl | 2 +- .../versions/versions-get-resp.json.tpl | 2 +- .../api/openstack/compute/test_versions.py | 4 +- .../api/openstack/compute/test_volumes.py | 81 +++++++++++++++++++ ...-offloaded-instances-93f70cfd49299f05.yaml | 8 ++ 11 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/attach-detach-vol-for-shelved-and-shelved-offloaded-instances-93f70cfd49299f05.yaml diff --git a/doc/api_samples/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json index 0178080e07fb..1366d699779e 100644 --- a/doc/api_samples/versions/v21-version-get-resp.json +++ b/doc/api_samples/versions/v21-version-get-resp.json @@ -19,7 +19,7 @@ } ], "status": "CURRENT", - "version": "2.19", + "version": "2.20", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/doc/api_samples/versions/versions-get-resp.json b/doc/api_samples/versions/versions-get-resp.json index 34646aa170bc..4a33f304cdb3 100644 --- a/doc/api_samples/versions/versions-get-resp.json +++ b/doc/api_samples/versions/versions-get-resp.json @@ -22,7 +22,7 @@ } ], "status": "CURRENT", - "version": "2.19", + "version": "2.20", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/api/openstack/api_version_request.py b/nova/api/openstack/api_version_request.py index 2f83e4763aa3..9f3cda6a7421 100644 --- a/nova/api/openstack/api_version_request.py +++ b/nova/api/openstack/api_version_request.py @@ -61,6 +61,9 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 2.17 - Add trigger_crash_dump to server actions * 2.18 - Makes project_id optional in v2.1 * 2.19 - Allow user to set and get the server description + * 2.20 - Add attach and detach volume operations for instances in shelved + and shelved_offloaded state + """ # The minimum and maximum versions of the API supported @@ -69,7 +72,7 @@ REST_API_VERSION_HISTORY = """REST API Version History: # Note(cyeoh): This only applies for the v2.1 API once microversions # support is fully merged. It does not affect the V2 API. _MIN_API_VERSION = "2.1" -_MAX_API_VERSION = "2.19" +_MAX_API_VERSION = "2.20" DEFAULT_API_VERSION = _MIN_API_VERSION diff --git a/nova/api/openstack/compute/volumes.py b/nova/api/openstack/compute/volumes.py index 2d5e41774d6d..7dc2caaebed8 100644 --- a/nova/api/openstack/compute/volumes.py +++ b/nova/api/openstack/compute/volumes.py @@ -18,12 +18,14 @@ from oslo_utils import strutils from webob import exc +from nova.api.openstack import api_version_request from nova.api.openstack import common from nova.api.openstack.compute.schemas import volumes as volumes_schema from nova.api.openstack import extensions from nova.api.openstack import wsgi from nova.api import validation from nova import compute +from nova.compute import vm_states from nova import exception from nova.i18n import _ from nova import objects @@ -224,6 +226,19 @@ def _translate_attachment_summary_view(volume_id, instance_uuid, mountpoint): return d +def _check_request_version(req, min_version, method, server_id, server_state): + if not api_version_request.is_supported(req, min_version=min_version): + exc_inv = exception.InstanceInvalidState( + attr='vm_state', + instance_uuid=server_id, + state=server_state, + method=method) + common.raise_http_conflict_for_instance_invalid_state( + exc_inv, + method, + server_id) + + class VolumeAttachmentController(wsgi.Controller): """The volume attachment API controller for the OpenStack API. @@ -290,6 +305,12 @@ class VolumeAttachmentController(wsgi.Controller): device = body['volumeAttachment'].get('device') instance = common.get_instance(self.compute_api, context, server_id) + + if instance.vm_state in (vm_states.SHELVED, + vm_states.SHELVED_OFFLOADED): + _check_request_version(req, '2.20', 'attach_volume', + server_id, instance.vm_state) + try: device = self.compute_api.attach_volume(context, instance, volume_id, device) @@ -383,7 +404,10 @@ class VolumeAttachmentController(wsgi.Controller): volume_id = id instance = common.get_instance(self.compute_api, context, server_id) - + if instance.vm_state in (vm_states.SHELVED, + vm_states.SHELVED_OFFLOADED): + _check_request_version(req, '2.20', 'detach_volume', + server_id, instance.vm_state) try: volume = self.volume_api.get(context, volume_id) except exception.VolumeNotFound as e: diff --git a/nova/api/openstack/rest_api_version_history.rst b/nova/api/openstack/rest_api_version_history.rst index 7b4fdb70c685..4c386a1034d3 100644 --- a/nova/api/openstack/rest_api_version_history.rst +++ b/nova/api/openstack/rest_api_version_history.rst @@ -173,3 +173,9 @@ user documentation. Allow the user to set and get the server description. The user will be able to set the description when creating, rebuilding, or updating a server, and get the description as part of the server details. + +2.20 +---- + + From this version of the API user can call detach and attach volumes for + instances which are in shelved and shelved_offloaded state. diff --git a/nova/compute/api.py b/nova/compute/api.py index 63ea333cb8c4..87f089df57e7 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -3046,7 +3046,8 @@ class API(base.Base): @check_instance_lock @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED, vm_states.STOPPED, vm_states.RESIZED, - vm_states.SOFT_DELETED]) + vm_states.SOFT_DELETED, vm_states.SHELVED, + vm_states.SHELVED_OFFLOADED]) def attach_volume(self, context, instance, volume_id, device=None, disk_bus=None, device_type=None): """Attach an existing volume to an existing instance.""" @@ -3105,7 +3106,8 @@ class API(base.Base): @check_instance_lock @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.PAUSED, vm_states.STOPPED, vm_states.RESIZED, - vm_states.SOFT_DELETED]) + vm_states.SOFT_DELETED, vm_states.SHELVED, + vm_states.SHELVED_OFFLOADED]) def detach_volume(self, context, instance, volume): """Detach a volume from an instance.""" if instance.vm_state == vm_states.SHELVED_OFFLOADED: diff --git a/nova/tests/functional/api_sample_tests/api_samples/versions/v21-version-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/versions/v21-version-get-resp.json.tpl index 09aeb0facdda..c73b4f873cda 100644 --- a/nova/tests/functional/api_sample_tests/api_samples/versions/v21-version-get-resp.json.tpl +++ b/nova/tests/functional/api_sample_tests/api_samples/versions/v21-version-get-resp.json.tpl @@ -19,7 +19,7 @@ } ], "status": "CURRENT", - "version": "2.19", + "version": "2.20", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/tests/functional/api_sample_tests/api_samples/versions/versions-get-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/versions/versions-get-resp.json.tpl index b6d70f4ce810..8a69527a551c 100644 --- a/nova/tests/functional/api_sample_tests/api_samples/versions/versions-get-resp.json.tpl +++ b/nova/tests/functional/api_sample_tests/api_samples/versions/versions-get-resp.json.tpl @@ -22,7 +22,7 @@ } ], "status": "CURRENT", - "version": "2.19", + "version": "2.20", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/tests/unit/api/openstack/compute/test_versions.py b/nova/tests/unit/api/openstack/compute/test_versions.py index 44cd8de5d191..f94e4e003fca 100644 --- a/nova/tests/unit/api/openstack/compute/test_versions.py +++ b/nova/tests/unit/api/openstack/compute/test_versions.py @@ -66,7 +66,7 @@ EXP_VERSIONS = { "v2.1": { "id": "v2.1", "status": "CURRENT", - "version": "2.19", + "version": "2.20", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z", "links": [ @@ -128,7 +128,7 @@ class VersionsTestV20(test.NoDBTestCase): { "id": "v2.1", "status": "CURRENT", - "version": "2.19", + "version": "2.20", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z", "links": [ diff --git a/nova/tests/unit/api/openstack/compute/test_volumes.py b/nova/tests/unit/api/openstack/compute/test_volumes.py index 0672e88991a7..3829d615c2f6 100644 --- a/nova/tests/unit/api/openstack/compute/test_volumes.py +++ b/nova/tests/unit/api/openstack/compute/test_volumes.py @@ -23,6 +23,7 @@ from six.moves import urllib import webob from webob import exc +from nova.api.openstack import common from nova.api.openstack.compute import assisted_volume_snapshots \ as assisted_snaps_v21 from nova.api.openstack.compute.legacy_v2.contrib import \ @@ -32,6 +33,7 @@ from nova.api.openstack.compute import volumes as volumes_v21 from nova.api.openstack import extensions from nova.compute import api as compute_api from nova.compute import flavors +from nova.compute import vm_states from nova import context from nova import db from nova import exception @@ -461,6 +463,40 @@ class VolumeAttachTestsV21(test.NoDBTestCase): status_int = result.status_int self.assertEqual(202, status_int) + @mock.patch.object(common, 'get_instance') + def test_detach_vol_shelved_not_supported(self, mock_get_instance): + inst = fake_instance.fake_instance_obj(self.context, + **{'uuid': FAKE_UUID}) + inst.vm_state = vm_states.SHELVED + mock_get_instance.return_value = inst + req = fakes.HTTPRequest.blank( + '/v2/servers/id/os-volume_attachments/uuid', version='2.19') + req.method = 'DELETE' + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + self.assertRaises(webob.exc.HTTPConflict, + self.attachments.delete, + req, + FAKE_UUID, + FAKE_UUID_A) + + @mock.patch.object(compute_api.API, 'detach_volume') + @mock.patch.object(common, 'get_instance') + def test_detach_vol_shelved_supported(self, + mock_get_instance, + mock_detach): + inst = fake_instance.fake_instance_obj(self.context, + **{'uuid': FAKE_UUID}) + inst.vm_state = vm_states.SHELVED + mock_get_instance.return_value = inst + req = fakes.HTTPRequest.blank( + '/v2/servers/id/os-volume_attachments/uuid', version='2.20') + req.method = 'DELETE' + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + self.attachments.delete(req, FAKE_UUID, FAKE_UUID_A) + self.assertTrue(mock_detach.called) + def test_detach_vol_not_found(self): self.stubs.Set(compute_api.API, 'detach_volume', @@ -524,6 +560,51 @@ class VolumeAttachTestsV21(test.NoDBTestCase): self.assertEqual('00000000-aaaa-aaaa-aaaa-000000000000', result['volumeAttachment']['id']) + @mock.patch.object(common, 'get_instance') + def test_attach_vol_shelved_not_supported(self, mock_get_instance): + body = {'volumeAttachment': {'volumeId': FAKE_UUID_A, + 'device': '/dev/fake'}} + + inst = fake_instance.fake_instance_obj(self.context, + **{'uuid': FAKE_UUID}) + inst.vm_state = vm_states.SHELVED + mock_get_instance.return_value = inst + req = fakes.HTTPRequest.blank('/v2/servers/id/os-volume_attachments', + version='2.19') + req.method = 'POST' + req.body = jsonutils.dump_as_bytes({}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + self.assertRaises(webob.exc.HTTPConflict, + self.attachments.create, + req, + FAKE_UUID, + body=body) + + @mock.patch.object(compute_api.API, 'attach_volume', + return_value='/dev/myfake') + @mock.patch.object(common, 'get_instance') + def test_attach_vol_shelved_supported(self, + mock_get_instance, + mock_attach): + body = {'volumeAttachment': {'volumeId': FAKE_UUID_A, + 'device': '/dev/fake'}} + + inst = fake_instance.fake_instance_obj(self.context, + **{'uuid': FAKE_UUID}) + inst.vm_state = vm_states.SHELVED + mock_get_instance.return_value = inst + req = fakes.HTTPRequest.blank('/v2/servers/id/os-volume_attachments', + version='2.20') + req.method = 'POST' + req.body = jsonutils.dump_as_bytes({}) + req.headers['content-type'] = 'application/json' + req.environ['nova.context'] = self.context + result = self.attachments.create(req, FAKE_UUID, body=body) + self.assertEqual('00000000-aaaa-aaaa-aaaa-000000000000', + result['volumeAttachment']['id']) + self.assertEqual('/dev/myfake', result['volumeAttachment']['device']) + @mock.patch.object(compute_api.API, 'attach_volume', return_value='/dev/myfake') def test_attach_volume_with_auto_device(self, mock_attach): diff --git a/releasenotes/notes/attach-detach-vol-for-shelved-and-shelved-offloaded-instances-93f70cfd49299f05.yaml b/releasenotes/notes/attach-detach-vol-for-shelved-and-shelved-offloaded-instances-93f70cfd49299f05.yaml new file mode 100644 index 000000000000..e59cb6f5cb55 --- /dev/null +++ b/releasenotes/notes/attach-detach-vol-for-shelved-and-shelved-offloaded-instances-93f70cfd49299f05.yaml @@ -0,0 +1,8 @@ +--- +features: + - It is possible to call attach and detach volume API operations for + instances which are in shelved and shelved_offloaded state. + For an instance in shelved_offloaded state Nova will set to None the value + for the device_name field, the right value for that field will be set once + the instance will be unshelved as it will be managed by a specific compute + manager.