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:
parent
ffe88d721f
commit
cf34a32820
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.19",
|
||||
"version": "2.20",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.19",
|
||||
"version": "2.20",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.19",
|
||||
"version": "2.20",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.19",
|
||||
"version": "2.20",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -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": [
|
||||
|
@ -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):
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user