Add request_ids attribute to resource objects

Added request_ids attribute to resource object for all the volume,
volume_types, volume_type_access and volume_snapshots APIs by
updating following APIs:

volumes: delete, update, force_delete, reset_state, extend,
migrate_volume, retype, update_readonly_flag, manage, unmanage,
promote, reenable, get_pools, initialize_connection,
terminate_connection, get_encryption_metadata

volume_types: delete
volume_type_access: add_project_access remove_project_access
volume_snapshots: delete and update

Returning list with request_ids as attribute in case of
'delete_metadata' and 'delete_image_metadata' APIs.

These changes are required to return 'request_id' from client to
log request_id mappings of cross projects.

For more details on how request_id will be returned to the caller,
please refer to the approved blueprint [1] discussed with the
cross-project team.
[1] http://specs.openstack.org/openstack/openstack-specs/specs/return-request-id.html

DocImpact
'request-ids' will be returned as an attribute with response object.
User can access it using 'res.request_ids' where 'res' is a
response object.

Change-Id: Icc4565291220278a65e6910a840fba623b750cc4
Partial-Implements: blueprint return-request-id-to-caller
This commit is contained in:
Ankit Agrawal 2015-12-13 23:43:16 -08:00
parent b2fc77f731
commit b560b1777c
10 changed files with 211 additions and 97 deletions

View File

@ -15,6 +15,9 @@ import json
from cinderclient.tests.unit.fixture_data import base
REQUEST_ID = 'req-test-request-id'
def _stub_snapshot(**kwargs):
snapshot = {
"created_at": "2012-08-28T16:30:31.000000",
@ -37,8 +40,11 @@ class Fixture(base.Fixture):
super(Fixture, self).setUp()
snapshot_1234 = _stub_snapshot(id='1234')
self.requests.register_uri('GET', self.url('1234'),
json={'snapshot': snapshot_1234})
self.requests.register_uri(
'GET', self.url('1234'),
json={'snapshot': snapshot_1234},
headers={'x-openstack-request-id': REQUEST_ID}
)
def action_1234(request, context):
return ''
@ -53,13 +59,20 @@ class Fixture(base.Fixture):
raise AssertionError("Unexpected action: %s" % action)
return ''
self.requests.register_uri('POST', self.url('1234', 'action'),
text=action_1234, status_code=202)
self.requests.register_uri(
'POST', self.url('1234', 'action'),
text=action_1234, status_code=202,
headers={'x-openstack-request-id': REQUEST_ID}
)
self.requests.register_uri('GET',
self.url('detail?limit=2&marker=1234'),
status_code=200, json={'snapshots': []})
self.requests.register_uri(
'GET', self.url('detail?limit=2&marker=1234'),
status_code=200, json={'snapshots': []},
headers={'x-openstack-request-id': REQUEST_ID}
)
self.requests.register_uri('GET',
self.url('detail?sort=id'),
status_code=200, json={'snapshots': []})
self.requests.register_uri(
'GET', self.url('detail?sort=id'),
status_code=200, json={'snapshots': []},
headers={'x-openstack-request-id': REQUEST_ID}
)

View File

@ -433,7 +433,7 @@ class FakeHTTPClient(base_client.HTTPClient):
assert body[action] is None
elif action == 'os-initialize_connection':
assert list(body[action]) == ['connector']
return (202, {}, {'connection_info': 'foos'})
return (202, {}, {'connection_info': {'foos': 'bars'}})
elif action == 'os-terminate_connection':
assert list(body[action]) == ['connector']
elif action == 'os-begin_detaching':

View File

@ -24,27 +24,35 @@ class SnapshotActionsTest(utils.FixturedTestCase):
data_fixture_class = snapshots.Fixture
def test_update_snapshot_status(self):
s = self.cs.volume_snapshots.get('1234')
snap = self.cs.volume_snapshots.get('1234')
self._assert_request_id(snap)
stat = {'status': 'available'}
self.cs.volume_snapshots.update_snapshot_status(s, stat)
stats = self.cs.volume_snapshots.update_snapshot_status(snap, stat)
self.assert_called('POST', '/snapshots/1234/action')
self._assert_request_id(stats)
def test_update_snapshot_status_with_progress(self):
s = self.cs.volume_snapshots.get('1234')
self._assert_request_id(s)
stat = {'status': 'available', 'progress': '73%'}
self.cs.volume_snapshots.update_snapshot_status(s, stat)
stats = self.cs.volume_snapshots.update_snapshot_status(s, stat)
self.assert_called('POST', '/snapshots/1234/action')
self._assert_request_id(stats)
def test_list_snapshots_with_marker_limit(self):
self.cs.volume_snapshots.list(marker=1234, limit=2)
lst = self.cs.volume_snapshots.list(marker=1234, limit=2)
self.assert_called('GET', '/snapshots/detail?limit=2&marker=1234')
self._assert_request_id(lst)
def test_list_snapshots_with_sort(self):
self.cs.volume_snapshots.list(sort="id")
lst = self.cs.volume_snapshots.list(sort="id")
self.assert_called('GET', '/snapshots/detail?sort=id')
self._assert_request_id(lst)
def test_snapshot_unmanage(self):
s = self.cs.volume_snapshots.get('1234')
self.cs.volume_snapshots.unmanage(s)
self._assert_request_id(s)
snap = self.cs.volume_snapshots.unmanage(s)
self.assert_called('POST', '/snapshots/1234/action',
{'os-unmanage': None})
self._assert_request_id(snap)

View File

@ -28,15 +28,18 @@ class TypeAccessTest(utils.TestCase):
def test_list(self):
access = cs.volume_type_access.list(volume_type='3')
cs.assert_called('GET', '/types/3/os-volume-type-access')
self._assert_request_id(access)
for a in access:
self.assertTrue(isinstance(a, volume_type_access.VolumeTypeAccess))
def test_add_project_access(self):
cs.volume_type_access.add_project_access('3', PROJECT_UUID)
access = cs.volume_type_access.add_project_access('3', PROJECT_UUID)
cs.assert_called('POST', '/types/3/action',
{'addProjectAccess': {'project': PROJECT_UUID}})
self._assert_request_id(access)
def test_remove_project_access(self):
cs.volume_type_access.remove_project_access('3', PROJECT_UUID)
access = cs.volume_type_access.remove_project_access('3', PROJECT_UUID)
cs.assert_called('POST', '/types/3/action',
{'removeProjectAccess': {'project': PROJECT_UUID}})
self._assert_request_id(access)

View File

@ -26,12 +26,14 @@ class TypesTest(utils.TestCase):
def test_list_types(self):
tl = cs.volume_types.list()
cs.assert_called('GET', '/types?is_public=None')
self._assert_request_id(tl)
for t in tl:
self.assertIsInstance(t, volume_types.VolumeType)
def test_list_types_not_public(self):
cs.volume_types.list(is_public=None)
t1 = cs.volume_types.list(is_public=None)
cs.assert_called('GET', '/types?is_public=None')
self._assert_request_id(t1)
def test_create(self):
t = cs.volume_types.create('test-type-3', 'test-type-3-desc')
@ -42,6 +44,7 @@ class TypesTest(utils.TestCase):
'os-volume-type-access:is_public': True
}})
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_create_non_public(self):
t = cs.volume_types.create('test-type-3', 'test-type-3-desc', False)
@ -52,6 +55,7 @@ class TypesTest(utils.TestCase):
'os-volume-type-access:is_public': False
}})
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_update(self):
t = cs.volume_types.update('1', 'test_type_1', 'test_desc_1', False)
@ -61,16 +65,19 @@ class TypesTest(utils.TestCase):
'description': 'test_desc_1',
'is_public': False}})
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_get(self):
t = cs.volume_types.get('1')
cs.assert_called('GET', '/types/1')
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_default(self):
t = cs.volume_types.default()
cs.assert_called('GET', '/types/default')
self.assertIsInstance(t, volume_types.VolumeType)
self._assert_request_id(t)
def test_set_key(self):
t = cs.volume_types.get(1)
@ -78,12 +85,15 @@ class TypesTest(utils.TestCase):
cs.assert_called('POST',
'/types/1/extra_specs',
{'extra_specs': {'k': 'v'}})
self._assert_request_id(t)
def test_unsset_keys(self):
t = cs.volume_types.get(1)
t.unset_keys(['k'])
cs.assert_called('DELETE', '/types/1/extra_specs/k')
self._assert_request_id(t)
def test_delete(self):
cs.volume_types.delete(1)
t = cs.volume_types.delete(1)
cs.assert_called('DELETE', '/types/1')
self._assert_request_id(t)

View File

@ -24,12 +24,14 @@ cs = fakes.FakeClient()
class VolumesTest(utils.TestCase):
def test_list_volumes_with_marker_limit(self):
cs.volumes.list(marker=1234, limit=2)
lst = cs.volumes.list(marker=1234, limit=2)
cs.assert_called('GET', '/volumes/detail?limit=2&marker=1234')
self._assert_request_id(lst)
def test_list_volumes_with_sort_key_dir(self):
cs.volumes.list(sort_key='id', sort_dir='asc')
lst = cs.volumes.list(sort_key='id', sort_dir='asc')
cs.assert_called('GET', '/volumes/detail?sort_dir=asc&sort_key=id')
self._assert_request_id(lst)
def test_list_volumes_with_invalid_sort_key(self):
self.assertRaises(ValueError,
@ -54,6 +56,7 @@ class VolumesTest(utils.TestCase):
# osapi_max_limit is 1000 by default. If limit is less than
# osapi_max_limit, we can get 2 volumes back.
volumes = cs.volumes._list(url, response_key, limit=limit)
self._assert_request_id(volumes)
cs.assert_called('GET', url)
self.assertEqual(fake_volumes, volumes)
@ -64,23 +67,28 @@ class VolumesTest(utils.TestCase):
cs.client.osapi_max_limit = 1
volumes = cs.volumes._list(url, response_key, limit=limit)
self.assertEqual(fake_volumes, volumes)
self._assert_request_id(volumes)
cs.client.osapi_max_limit = 1000
def test_delete_volume(self):
v = cs.volumes.list()[0]
v.delete()
del_v = v.delete()
cs.assert_called('DELETE', '/volumes/1234')
cs.volumes.delete('1234')
self._assert_request_id(del_v)
del_v = cs.volumes.delete('1234')
cs.assert_called('DELETE', '/volumes/1234')
cs.volumes.delete(v)
self._assert_request_id(del_v)
del_v = cs.volumes.delete(v)
cs.assert_called('DELETE', '/volumes/1234')
self._assert_request_id(del_v)
def test_create_volume(self):
cs.volumes.create(1)
vol = cs.volumes.create(1)
cs.assert_called('POST', '/volumes')
self._assert_request_id(vol)
def test_create_volume_with_hint(self):
cs.volumes.create(1, scheduler_hints='uuid')
vol = cs.volumes.create(1, scheduler_hints='uuid')
expected = {'volume': {'status': 'creating',
'description': None,
'availability_zone': None,
@ -99,31 +107,42 @@ class VolumesTest(utils.TestCase):
'multiattach': False},
'OS-SCH-HNT:scheduler_hints': 'uuid'}
cs.assert_called('POST', '/volumes', body=expected)
self._assert_request_id(vol)
def test_attach(self):
v = cs.volumes.get('1234')
cs.volumes.attach(v, 1, '/dev/vdc', mode='ro')
self._assert_request_id(v)
vol = cs.volumes.attach(v, 1, '/dev/vdc', mode='ro')
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_attach_to_host(self):
v = cs.volumes.get('1234')
cs.volumes.attach(v, None, None, host_name='test', mode='rw')
self._assert_request_id(v)
vol = cs.volumes.attach(v, None, None, host_name='test', mode='rw')
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_detach(self):
v = cs.volumes.get('1234')
cs.volumes.detach(v, 'abc123')
self._assert_request_id(v)
vol = cs.volumes.detach(v, 'abc123')
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_reserve(self):
v = cs.volumes.get('1234')
cs.volumes.reserve(v)
self._assert_request_id(v)
vol = cs.volumes.reserve(v)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_unreserve(self):
v = cs.volumes.get('1234')
cs.volumes.unreserve(v)
self._assert_request_id(v)
vol = cs.volumes.unreserve(v)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_begin_detaching(self):
v = cs.volumes.get('1234')
@ -132,126 +151,161 @@ class VolumesTest(utils.TestCase):
def test_roll_detaching(self):
v = cs.volumes.get('1234')
cs.volumes.roll_detaching(v)
self._assert_request_id(v)
vol = cs.volumes.roll_detaching(v)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_initialize_connection(self):
v = cs.volumes.get('1234')
cs.volumes.initialize_connection(v, {})
self._assert_request_id(v)
vol = cs.volumes.initialize_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_terminate_connection(self):
v = cs.volumes.get('1234')
cs.volumes.terminate_connection(v, {})
self._assert_request_id(v)
vol = cs.volumes.terminate_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_set_metadata(self):
cs.volumes.set_metadata(1234, {'k1': 'v2'})
vol = cs.volumes.set_metadata(1234, {'k1': 'v2'})
cs.assert_called('POST', '/volumes/1234/metadata',
{'metadata': {'k1': 'v2'}})
self._assert_request_id(vol)
def test_delete_metadata(self):
keys = ['key1']
cs.volumes.delete_metadata(1234, keys)
vol = cs.volumes.delete_metadata(1234, keys)
cs.assert_called('DELETE', '/volumes/1234/metadata/key1')
self._assert_request_id(vol)
def test_extend(self):
v = cs.volumes.get('1234')
cs.volumes.extend(v, 2)
self._assert_request_id(v)
vol = cs.volumes.extend(v, 2)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_reset_state(self):
v = cs.volumes.get('1234')
cs.volumes.reset_state(v, 'in-use', attach_status='detached',
migration_status='none')
self._assert_request_id(v)
vol = cs.volumes.reset_state(v, 'in-use', attach_status='detached',
migration_status='none')
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_get_encryption_metadata(self):
cs.volumes.get_encryption_metadata('1234')
vol = cs.volumes.get_encryption_metadata('1234')
cs.assert_called('GET', '/volumes/1234/encryption')
self._assert_request_id(vol)
def test_migrate(self):
v = cs.volumes.get('1234')
cs.volumes.migrate_volume(v, 'dest', False, False)
self._assert_request_id(v)
vol = cs.volumes.migrate_volume(v, 'dest', False, False)
cs.assert_called('POST', '/volumes/1234/action',
{'os-migrate_volume': {'host': 'dest',
'force_host_copy': False,
'lock_volume': False}})
self._assert_request_id(vol)
def test_migrate_with_lock_volume(self):
v = cs.volumes.get('1234')
cs.volumes.migrate_volume(v, 'dest', False, True)
self._assert_request_id(v)
vol = cs.volumes.migrate_volume(v, 'dest', False, True)
cs.assert_called('POST', '/volumes/1234/action',
{'os-migrate_volume': {'host': 'dest',
'force_host_copy': False,
'lock_volume': True}})
self._assert_request_id(vol)
def test_metadata_update_all(self):
cs.volumes.update_all_metadata(1234, {'k1': 'v1'})
vol = cs.volumes.update_all_metadata(1234, {'k1': 'v1'})
cs.assert_called('PUT', '/volumes/1234/metadata',
{'metadata': {'k1': 'v1'}})
self._assert_request_id(vol)
def test_readonly_mode_update(self):
v = cs.volumes.get('1234')
cs.volumes.update_readonly_flag(v, True)
self._assert_request_id(v)
vol = cs.volumes.update_readonly_flag(v, True)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_retype(self):
v = cs.volumes.get('1234')
cs.volumes.retype(v, 'foo', 'on-demand')
self._assert_request_id(v)
vol = cs.volumes.retype(v, 'foo', 'on-demand')
cs.assert_called('POST', '/volumes/1234/action',
{'os-retype': {'new_type': 'foo',
'migration_policy': 'on-demand'}})
self._assert_request_id(vol)
def test_set_bootable(self):
v = cs.volumes.get('1234')
cs.volumes.set_bootable(v, True)
self._assert_request_id(v)
vol = cs.volumes.set_bootable(v, True)
cs.assert_called('POST', '/volumes/1234/action')
self._assert_request_id(vol)
def test_volume_manage(self):
cs.volumes.manage('host1', {'k': 'v'})
vol = cs.volumes.manage('host1', {'k': 'v'})
expected = {'host': 'host1', 'name': None, 'availability_zone': None,
'description': None, 'metadata': None, 'ref': {'k': 'v'},
'volume_type': None, 'bootable': False}
cs.assert_called('POST', '/os-volume-manage', {'volume': expected})
self._assert_request_id(vol)
def test_volume_manage_bootable(self):
cs.volumes.manage('host1', {'k': 'v'}, bootable=True)
vol = cs.volumes.manage('host1', {'k': 'v'}, bootable=True)
expected = {'host': 'host1', 'name': None, 'availability_zone': None,
'description': None, 'metadata': None, 'ref': {'k': 'v'},
'volume_type': None, 'bootable': True}
cs.assert_called('POST', '/os-volume-manage', {'volume': expected})
self._assert_request_id(vol)
def test_volume_unmanage(self):
v = cs.volumes.get('1234')
cs.volumes.unmanage(v)
self._assert_request_id(v)
vol = cs.volumes.unmanage(v)
cs.assert_called('POST', '/volumes/1234/action', {'os-unmanage': None})
self._assert_request_id(vol)
def test_snapshot_manage(self):
cs.volume_snapshots.manage('volume_id1', {'k': 'v'})
vol = cs.volume_snapshots.manage('volume_id1', {'k': 'v'})
expected = {'volume_id': 'volume_id1', 'name': None,
'description': None, 'metadata': None, 'ref': {'k': 'v'}}
cs.assert_called('POST', '/os-snapshot-manage', {'snapshot': expected})
self._assert_request_id(vol)
def test_replication_promote(self):
v = cs.volumes.get('1234')
cs.volumes.promote(v)
self._assert_request_id(v)
vol = cs.volumes.promote(v)
cs.assert_called('POST', '/volumes/1234/action',
{'os-promote-replica': None})
self._assert_request_id(vol)
def test_replication_reenable(self):
v = cs.volumes.get('1234')
cs.volumes.reenable(v)
self._assert_request_id(v)
vol = cs.volumes.reenable(v)
cs.assert_called('POST', '/volumes/1234/action',
{'os-reenable-replica': None})
self._assert_request_id(vol)
def test_get_pools(self):
cs.volumes.get_pools('')
vol = cs.volumes.get_pools('')
cs.assert_called('GET', '/scheduler-stats/get_pools')
self._assert_request_id(vol)
def test_get_pools_detail(self):
cs.volumes.get_pools('--detail')
vol = cs.volumes.get_pools('--detail')
cs.assert_called('GET', '/scheduler-stats/get_pools?detail=True')
self._assert_request_id(vol)
class FormatSortParamTestCase(utils.TestCase):

View File

@ -16,6 +16,7 @@
"""Volume snapshot interface (1.1 extension)."""
from cinderclient import base
from cinderclient.openstack.common.apiclient import base as common_base
class Snapshot(base.Resource):
@ -26,11 +27,11 @@ class Snapshot(base.Resource):
def delete(self):
"""Delete this snapshot."""
self.manager.delete(self)
return self.manager.delete(self)
def update(self, **kwargs):
"""Update the name or description for this snapshot."""
self.manager.update(self, **kwargs)
return self.manager.update(self, **kwargs)
@property
def progress(self):
@ -42,7 +43,7 @@ class Snapshot(base.Resource):
def reset_state(self, state):
"""Update the snapshot with the provided state."""
self.manager.reset_state(self, state)
return self.manager.reset_state(self, state)
def set_metadata(self, metadata):
"""Set metadata of this snapshot."""
@ -122,7 +123,7 @@ class SnapshotManager(base.ManagerWithFind):
:param snapshot: The :class:`Snapshot` to delete.
"""
self._delete("/snapshots/%s" % base.getid(snapshot))
return self._delete("/snapshots/%s" % base.getid(snapshot))
def update(self, snapshot, **kwargs):
"""Update the name or description for a snapshot.
@ -134,7 +135,7 @@ class SnapshotManager(base.ManagerWithFind):
body = {"snapshot": kwargs}
self._update("/snapshots/%s" % base.getid(snapshot), body)
return self._update("/snapshots/%s" % base.getid(snapshot), body)
def reset_state(self, snapshot, state):
"""Update the specified snapshot with the provided state."""
@ -145,7 +146,8 @@ class SnapshotManager(base.ManagerWithFind):
body = {action: info}
self.run_hooks('modify_body_for_action', body, **kwargs)
url = '/snapshots/%s/action' % base.getid(snapshot)
return self.api.client.post(url, body=body)
resp, body = self.api.client.post(url, body=body)
return common_base.TupleWithMeta((resp, body), resp)
def update_snapshot_status(self, snapshot, update_dict):
return self._action('os-update_snapshot_status',
@ -167,9 +169,14 @@ class SnapshotManager(base.ManagerWithFind):
:param snapshot: The :class:`Snapshot`.
:param keys: A list of keys to be removed.
"""
response_list = []
snapshot_id = base.getid(snapshot)
for k in keys:
self._delete("/snapshots/%s/metadata/%s" % (snapshot_id, k))
resp, body = self._delete("/snapshots/%s/metadata/%s" %
(snapshot_id, k))
response_list.append(resp)
return common_base.ListWithMeta([], response_list)
def update_all_metadata(self, snapshot, metadata):
"""Update_all snapshot metadata.

View File

@ -15,6 +15,7 @@
"""Volume type access interface."""
from cinderclient import base
from cinderclient.openstack.common.apiclient import base as common_base
class VolumeTypeAccess(base.Resource):
@ -36,16 +37,17 @@ class VolumeTypeAccessManager(base.ManagerWithFind):
def add_project_access(self, volume_type, project):
"""Add a project to the given volume type access list."""
info = {'project': project}
self._action('addProjectAccess', volume_type, info)
return self._action('addProjectAccess', volume_type, info)
def remove_project_access(self, volume_type, project):
"""Remove a project from the given volume type access list."""
info = {'project': project}
self._action('removeProjectAccess', volume_type, info)
return self._action('removeProjectAccess', volume_type, info)
def _action(self, action, volume_type, info, **kwargs):
"""Perform a volume type action."""
body = {action: info}
self.run_hooks('modify_body_for_action', body, **kwargs)
url = '/types/%s/action' % base.getid(volume_type)
return self.api.client.post(url, body=body)
resp, body = self.api.client.post(url, body=body)
return common_base.TupleWithMeta((resp, body), resp)

View File

@ -108,7 +108,7 @@ class VolumeTypeManager(base.ManagerWithFind):
:param volume_type: The name or ID of the :class:`VolumeType` to get.
"""
self._delete("/types/%s" % base.getid(volume_type))
return self._delete("/types/%s" % base.getid(volume_type))
def create(self, name, description=None, is_public=True):
"""Creates a volume type.

View File

@ -16,6 +16,7 @@
"""Volume interface (v2 extension)."""
from cinderclient import base
from cinderclient.openstack.common.apiclient import base as common_base
class Volume(base.Resource):
@ -25,11 +26,11 @@ class Volume(base.Resource):
def delete(self):
"""Delete this volume."""
self.manager.delete(self)
return self.manager.delete(self)
def update(self, **kwargs):
"""Update the name or description for this volume."""
self.manager.update(self, **kwargs)
return self.manager.update(self, **kwargs)
def attach(self, instance_uuid, mountpoint, mode='rw', host_name=None):
"""Set attachment metadata.
@ -119,7 +120,7 @@ class Volume(base.Resource):
:param volume: The UUID of the volume to force-delete.
"""
self.manager.force_delete(self)
return self.manager.force_delete(self)
def reset_state(self, state, attach_status=None, migration_status=None):
"""Update the volume with the provided state.
@ -130,7 +131,8 @@ class Volume(base.Resource):
:param migration_status: The migration_status of the volume to be set,
or None to keep the current status.
"""
self.manager.reset_state(self, state, attach_status, migration_status)
return self.manager.reset_state(self, state, attach_status,
migration_status)
def extend(self, volume, new_size):
"""Extend the size of the specified volume.
@ -138,11 +140,12 @@ class Volume(base.Resource):
:param volume: The UUID of the volume to extend
:param new_size: The desired size to extend volume to.
"""
self.manager.extend(self, new_size)
return self.manager.extend(self, new_size)
def migrate_volume(self, host, force_host_copy, lock_volume):
"""Migrate the volume to a new host."""
self.manager.migrate_volume(self, host, force_host_copy, lock_volume)
return self.manager.migrate_volume(self, host, force_host_copy,
lock_volume)
def replication_enable(self, volume):
"""Enables volume replication on a given volume."""
@ -162,7 +165,7 @@ class Volume(base.Resource):
def retype(self, volume_type, policy):
"""Change a volume's type."""
self.manager.retype(self, volume_type, policy)
return self.manager.retype(self, volume_type, policy)
def update_all_metadata(self, metadata):
"""Update all metadata of this volume."""
@ -175,32 +178,33 @@ class Volume(base.Resource):
:param read_only: The value to indicate whether to update volume to
read-only access mode.
"""
self.manager.update_readonly_flag(self, read_only)
return self.manager.update_readonly_flag(self, read_only)
def manage(self, host, ref, name=None, description=None,
volume_type=None, availability_zone=None, metadata=None,
bootable=False):
"""Manage an existing volume."""
self.manager.manage(host=host, ref=ref, name=name,
description=description, volume_type=volume_type,
availability_zone=availability_zone,
metadata=metadata, bootable=bootable)
return self.manager.manage(host=host, ref=ref, name=name,
description=description,
volume_type=volume_type,
availability_zone=availability_zone,
metadata=metadata, bootable=bootable)
def unmanage(self, volume):
"""Unmanage a volume."""
self.manager.unmanage(volume)
return self.manager.unmanage(volume)
def promote(self, volume):
"""Promote secondary to be primary in relationship."""
self.manager.promote(volume)
return self.manager.promote(volume)
def reenable(self, volume):
"""Sync the secondary volume with primary for a relationship."""
self.manager.reenable(volume)
return self.manager.reenable(volume)
def get_pools(self, detail):
"""Show pool information for backends."""
self.manager.get_pools(detail)
return self.manager.get_pools(detail)
class VolumeManager(base.ManagerWithFind):
@ -298,7 +302,7 @@ class VolumeManager(base.ManagerWithFind):
:param volume: The :class:`Volume` to delete.
"""
self._delete("/volumes/%s" % base.getid(volume))
return self._delete("/volumes/%s" % base.getid(volume))
def update(self, volume, **kwargs):
"""Update the name or description for a volume.
@ -310,7 +314,7 @@ class VolumeManager(base.ManagerWithFind):
body = {"volume": kwargs}
self._update("/volumes/%s" % base.getid(volume), body)
return self._update("/volumes/%s" % base.getid(volume), body)
def _action(self, action, volume, info=None, **kwargs):
"""Perform a volume "action."
@ -318,7 +322,8 @@ class VolumeManager(base.ManagerWithFind):
body = {action: info}
self.run_hooks('modify_body_for_action', body, **kwargs)
url = '/volumes/%s/action' % base.getid(volume)
return self.api.client.post(url, body=body)
resp, body = self.api.client.post(url, body=body)
return common_base.TupleWithMeta((resp, body), resp)
def attach(self, volume, instance_uuid, mountpoint, mode='rw',
host_name=None):
@ -386,8 +391,9 @@ class VolumeManager(base.ManagerWithFind):
:param volume: The :class:`Volume` (or its ID).
:param connector: connector dict from nova.
"""
return self._action('os-initialize_connection', volume,
{'connector': connector})[1]['connection_info']
resp, body = self._action('os-initialize_connection', volume,
{'connector': connector})
return common_base.DictWithMeta(body['connection_info'], resp)
def terminate_connection(self, volume, connector):
"""Terminate a volume connection.
@ -395,8 +401,8 @@ class VolumeManager(base.ManagerWithFind):
:param volume: The :class:`Volume` (or its ID).
:param connector: connector dict from nova.
"""
self._action('os-terminate_connection', volume,
{'connector': connector})
return self._action('os-terminate_connection', volume,
{'connector': connector})
def set_metadata(self, volume, metadata):
"""Update/Set a volumes metadata.
@ -414,8 +420,13 @@ class VolumeManager(base.ManagerWithFind):
:param volume: The :class:`Volume`.
:param keys: A list of keys to be removed.
"""
response_list = []
for k in keys:
self._delete("/volumes/%s/metadata/%s" % (base.getid(volume), k))
resp, body = self._delete("/volumes/%s/metadata/%s" %
(base.getid(volume), k))
response_list.append(resp)
return common_base.ListWithMeta([], response_list)
def set_image_metadata(self, volume, metadata):
"""Set a volume's image metadata.
@ -433,9 +444,13 @@ class VolumeManager(base.ManagerWithFind):
:param volume: The :class:`Volume`.
:param keys: A list of keys to be removed.
"""
response_list = []
for key in keys:
self._action("os-unset_image_metadata", volume,
{'key': key})
resp, body = self._action("os-unset_image_metadata", volume,
{'key': key})
response_list.append(resp)
return common_base.ListWithMeta([], response_list)
def show_image_metadata(self, volume):
"""Show a volume's image metadata.
@ -500,7 +515,8 @@ class VolumeManager(base.ManagerWithFind):
:param volume_id: the id of the volume to query
:return: a dictionary of volume encryption metadata
"""
return self._get("/volumes/%s/encryption" % volume_id)._info
metadata = self._get("/volumes/%s/encryption" % volume_id)
return common_base.DictWithMeta(metadata._info, metadata.request_ids)
def migrate_volume(self, volume, host, force_host_copy, lock_volume):
"""Migrate volume to new host.
@ -524,9 +540,10 @@ class VolumeManager(base.ManagerWithFind):
:param error: Inform of an error to cause migration cleanup
"""
new_volume_id = base.getid(new_volume)
return self._action('os-migrate_volume_completion',
old_volume,
{'new_volume': new_volume_id, 'error': error})[1]
resp, body = self._action('os-migrate_volume_completion', old_volume,
{'new_volume': new_volume_id,
'error': error})
return common_base.DictWithMeta(body, resp)
def replication_enable(self, volume_id):
"""