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
This commit is contained in:
Andrea Rosa 2016-01-11 15:50:09 +00:00
parent ffe88d721f
commit cf34a32820
11 changed files with 134 additions and 10 deletions

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.19",
"version": "2.20",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.19",
"version": "2.20",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -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

View File

@ -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:

View File

@ -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.

View File

@ -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:

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.19",
"version": "2.20",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.19",
"version": "2.20",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -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": [

View File

@ -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):

View File

@ -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.