diff --git a/cinderclient/tests/v1/fakes.py b/cinderclient/tests/v1/fakes.py index a71f502..54b5112 100644 --- a/cinderclient/tests/v1/fakes.py +++ b/cinderclient/tests/v1/fakes.py @@ -114,6 +114,44 @@ def _stub_restore(): return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'} +def _stub_transfer_full(id, base_uri, tenant_id): + return { + 'id': id, + 'name': 'transfer', + 'volume_id': '8c05f861-6052-4df6-b3e0-0aebfbe686cc', + 'created_at': '2013-04-12T08:16:37.000000', + 'auth_key': '123456', + 'links': [ + { + 'href': _self_href(base_uri, tenant_id, id), + 'rel': 'self' + }, + { + 'href': _bookmark_href(base_uri, tenant_id, id), + 'rel': 'bookmark' + } + ] + } + + +def _stub_transfer(id, base_uri, tenant_id): + return { + 'id': id, + 'name': 'transfer', + 'volume_id': '8c05f861-6052-4df6-b3e0-0aebfbe686cc', + 'links': [ + { + 'href': _self_href(base_uri, tenant_id, id), + 'rel': 'self' + }, + { + 'href': _bookmark_href(base_uri, tenant_id, id), + 'rel': 'bookmark' + } + ] + } + + class FakeClient(fakes.FakeClient, client.Client): def __init__(self, *args, **kwargs): @@ -405,3 +443,42 @@ class FakeHTTPClient(base_client.HTTPClient): def post_backups_76a17945_3c6f_435c_975b_b5685db10b62_restore(self, **kw): return (200, {}, {'restore': _stub_restore()}) + + # + # VolumeTransfers + # + + def get_os_volume_transfer_5678(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + transfer1 = '5678' + return (200, {}, + {'transfer': + _stub_transfer_full(transfer1, base_uri, tenant_id)}) + + def get_os_volume_transfer_detail(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + transfer1 = '5678' + transfer2 = 'f625ec3e-13dd-4498-a22a-50afd534cc41' + return (200, {}, + {'transfers': [ + _stub_transfer_full(transfer1, base_uri, tenant_id), + _stub_transfer_full(transfer2, base_uri, tenant_id)]}) + + def delete_os_volume_transfer_5678(self, **kw): + return (202, {}, None) + + def post_os_volume_transfer(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + transfer1 = '5678' + return (202, {}, + {'transfer': _stub_transfer(transfer1, base_uri, tenant_id)}) + + def post_os_volume_transfer_5678_accept(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + transfer1 = '5678' + return (200, {}, + {'transfer': _stub_transfer(transfer1, base_uri, tenant_id)}) diff --git a/cinderclient/tests/v1/test_volume_transfers.py b/cinderclient/tests/v1/test_volume_transfers.py new file mode 100644 index 0000000..40fb09b --- /dev/null +++ b/cinderclient/tests/v1/test_volume_transfers.py @@ -0,0 +1,51 @@ +# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cinderclient.tests import utils +from cinderclient.tests.v1 import fakes + + +cs = fakes.FakeClient() + + +class VolumeTRansfersTest(utils.TestCase): + + def test_create(self): + cs.transfers.create('1234') + cs.assert_called('POST', '/os-volume-transfer') + + def test_get(self): + transfer_id = '5678' + cs.transfers.get(transfer_id) + cs.assert_called('GET', '/os-volume-transfer/%s' % transfer_id) + + def test_list(self): + cs.transfers.list() + cs.assert_called('GET', '/os-volume-transfer/detail') + + def test_delete(self): + b = cs.transfers.list()[0] + b.delete() + cs.assert_called('DELETE', '/os-volume-transfer/5678') + cs.transfers.delete('5678') + cs.assert_called('DELETE', '/os-volume-transfer/5678') + cs.transfers.delete(b) + cs.assert_called('DELETE', '/os-volume-transfer/5678') + + def test_accept(self): + transfer_id = '5678' + auth_key = '12345' + cs.transfers.accept(transfer_id, auth_key) + cs.assert_called('POST', '/os-volume-transfer/%s/accept' % transfer_id) diff --git a/cinderclient/tests/v2/fakes.py b/cinderclient/tests/v2/fakes.py index 28cb20a..580c904 100644 --- a/cinderclient/tests/v2/fakes.py +++ b/cinderclient/tests/v2/fakes.py @@ -121,6 +121,44 @@ def _stub_restore(): return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'} +def _stub_transfer_full(id, base_uri, tenant_id): + return { + 'id': id, + 'name': 'transfer', + 'volume_id': '8c05f861-6052-4df6-b3e0-0aebfbe686cc', + 'created_at': '2013-04-12T08:16:37.000000', + 'auth_key': '123456', + 'links': [ + { + 'href': _self_href(base_uri, tenant_id, id), + 'rel': 'self' + }, + { + 'href': _bookmark_href(base_uri, tenant_id, id), + 'rel': 'bookmark' + } + ] + } + + +def _stub_transfer(id, base_uri, tenant_id): + return { + 'id': id, + 'name': 'transfer', + 'volume_id': '8c05f861-6052-4df6-b3e0-0aebfbe686cc', + 'links': [ + { + 'href': _self_href(base_uri, tenant_id, id), + 'rel': 'self' + }, + { + 'href': _bookmark_href(base_uri, tenant_id, id), + 'rel': 'bookmark' + } + ] + } + + class FakeClient(fakes.FakeClient, client.Client): def __init__(self, *args, **kwargs): @@ -412,3 +450,42 @@ class FakeHTTPClient(base_client.HTTPClient): def post_backups_76a17945_3c6f_435c_975b_b5685db10b62_restore(self, **kw): return (200, {}, {'restore': _stub_restore()}) + + # + # VolumeTransfers + # + + def get_os_volume_transfer_5678(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + transfer1 = '5678' + return (200, {}, + {'transfer': + _stub_transfer_full(transfer1, base_uri, tenant_id)}) + + def get_os_volume_transfer_detail(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + transfer1 = '5678' + transfer2 = 'f625ec3e-13dd-4498-a22a-50afd534cc41' + return (200, {}, + {'transfers': [ + _stub_transfer_full(transfer1, base_uri, tenant_id), + _stub_transfer_full(transfer2, base_uri, tenant_id)]}) + + def delete_os_volume_transfer_5678(self, **kw): + return (202, {}, None) + + def post_os_volume_transfer(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + transfer1 = '5678' + return (202, {}, + {'transfer': _stub_transfer(transfer1, base_uri, tenant_id)}) + + def post_os_volume_transfer_5678_accept(self, **kw): + base_uri = 'http://localhost:8776' + tenant_id = '0fa851f6668144cf9cd8c8419c1646c1' + transfer1 = '5678' + return (200, {}, + {'transfer': _stub_transfer(transfer1, base_uri, tenant_id)}) diff --git a/cinderclient/tests/v2/test_volume_transfers.py b/cinderclient/tests/v2/test_volume_transfers.py new file mode 100644 index 0000000..40fb09b --- /dev/null +++ b/cinderclient/tests/v2/test_volume_transfers.py @@ -0,0 +1,51 @@ +# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from cinderclient.tests import utils +from cinderclient.tests.v1 import fakes + + +cs = fakes.FakeClient() + + +class VolumeTRansfersTest(utils.TestCase): + + def test_create(self): + cs.transfers.create('1234') + cs.assert_called('POST', '/os-volume-transfer') + + def test_get(self): + transfer_id = '5678' + cs.transfers.get(transfer_id) + cs.assert_called('GET', '/os-volume-transfer/%s' % transfer_id) + + def test_list(self): + cs.transfers.list() + cs.assert_called('GET', '/os-volume-transfer/detail') + + def test_delete(self): + b = cs.transfers.list()[0] + b.delete() + cs.assert_called('DELETE', '/os-volume-transfer/5678') + cs.transfers.delete('5678') + cs.assert_called('DELETE', '/os-volume-transfer/5678') + cs.transfers.delete(b) + cs.assert_called('DELETE', '/os-volume-transfer/5678') + + def test_accept(self): + transfer_id = '5678' + auth_key = '12345' + cs.transfers.accept(transfer_id, auth_key) + cs.assert_called('POST', '/os-volume-transfer/%s/accept' % transfer_id) diff --git a/cinderclient/v1/client.py b/cinderclient/v1/client.py index a5b9b02..19e7e62 100644 --- a/cinderclient/v1/client.py +++ b/cinderclient/v1/client.py @@ -22,6 +22,7 @@ from cinderclient.v1 import volume_snapshots from cinderclient.v1 import volume_types from cinderclient.v1 import volume_backups from cinderclient.v1 import volume_backups_restore +from cinderclient.v1 import volume_transfers class Client(object): @@ -60,6 +61,7 @@ class Client(object): self.quotas = quotas.QuotaSetManager(self) self.backups = volume_backups.VolumeBackupManager(self) self.restores = volume_backups_restore.VolumeBackupRestoreManager(self) + self.transfers = volume_transfers.VolumeTransferManager(self) # Add in any extensions... if extensions: diff --git a/cinderclient/v1/shell.py b/cinderclient/v1/shell.py index 5c56f4a..7e6255a 100644 --- a/cinderclient/v1/shell.py +++ b/cinderclient/v1/shell.py @@ -73,6 +73,11 @@ def _find_backup(cs, backup): return utils.find_resource(cs.backups, backup) +def _find_transfer(cs, transfer): + """Get a transfer by ID.""" + return utils.find_resource(cs.transfers, transfer) + + def _print_volume(volume): utils.print_dict(volume._info) @@ -719,3 +724,71 @@ def do_backup_restore(cs, args): """Restore a backup.""" cs.restores.restore(args.backup, args.volume_id) + + +@utils.arg('volume', metavar='', + help='ID of the volume to transfer.') +@utils.arg('--display-name', metavar='', + help='Optional transfer name. (Default=None)', + default=None) +@utils.service_type('volume') +def do_transfer_create(cs, args): + """Creates a volume transfer.""" + transfer = cs.transfers.create(args.volume, + args.display_name) + info = dict() + info.update(transfer._info) + + if 'links' in info: + info.pop('links') + + utils.print_dict(info) + + +@utils.arg('transfer', metavar='', + help='ID of the transfer to delete.') +@utils.service_type('volume') +def do_transfer_delete(cs, args): + """Undo a transfer.""" + transfer = _find_transfer(cs, args.transfer) + transfer.delete() + + +@utils.arg('transfer', metavar='', + help='ID of the transfer to accept.') +@utils.arg('auth_key', metavar='', + help='Auth key of the transfer to accept.') +@utils.service_type('volume') +def do_transfer_accept(cs, args): + """Accepts a volume transfer.""" + transfer = cs.transfers.accept(args.transfer, args.auth_key) + info = dict() + info.update(transfer._info) + + if 'links' in info: + info.pop('links') + + utils.print_dict(info) + + +@utils.service_type('volume') +def do_transfer_list(cs, args): + """List all the transfers.""" + transfers = cs.transfers.list() + columns = ['ID', 'Volume ID', 'Name'] + utils.print_list(transfers, columns) + + +@utils.arg('transfer', metavar='', + help='ID of the transfer to accept.') +@utils.service_type('volume') +def do_transfer_show(cs, args): + """Show details about a transfer.""" + transfer = _find_transfer(cs, args.transfer) + info = dict() + info.update(transfer._info) + + if 'links' in info: + info.pop('links') + + utils.print_dict(info) diff --git a/cinderclient/v1/volume_transfers.py b/cinderclient/v1/volume_transfers.py new file mode 100644 index 0000000..8aace02 --- /dev/null +++ b/cinderclient/v1/volume_transfers.py @@ -0,0 +1,82 @@ +# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Volume transfer interface (1.1 extension). +""" + +from cinderclient import base + + +class VolumeTransfer(base.Resource): + """Transfer a volume from one tenant to another""" + def __repr__(self): + return "" % self.id + + def delete(self): + """Delete this volume transfer.""" + return self.manager.delete(self) + + +class VolumeTransferManager(base.ManagerWithFind): + """Manage :class:`VolumeTransfer` resources.""" + resource_class = VolumeTransfer + + def create(self, volume_id, name=None): + """Create a volume transfer. + + :param volume_id: The ID of the volume to transfer. + :param name: The name of the transfer. + :rtype: :class:`VolumeTransfer` + """ + body = {'transfer': {'volume_id': volume_id, + 'name': name}} + return self._create('/os-volume-transfer', body, 'transfer') + + def accept(self, transfer_id, auth_key): + """Accept a volume transfer. + + :param transfer_id: The ID of the trasnfer to accept. + :param auth_key: The auth_key of the transfer. + :rtype: :class:`VolumeTransfer` + """ + body = {'accept': {'auth_key': auth_key}} + return self._create('/os-volume-transfer/%s/accept' % transfer_id, + body, 'transfer') + + def get(self, transfer_id): + """Show details of a volume transfer. + + :param transfer_id: The ID of the volume transfer to display. + :rtype: :class:`VolumeTransfer` + """ + return self._get("/os-volume-transfer/%s" % transfer_id, "transfer") + + def list(self, detailed=True): + """Get a list of all volume transfer. + + :rtype: list of :class:`VolumeTransfer` + """ + if detailed is True: + return self._list("/os-volume-transfer/detail", "transfers") + else: + return self._list("/os-volume-transfer", "transfers") + + def delete(self, transfer_id): + """Delete a volume transfer. + + :param transfer_id: The :class:`VolumeTransfer` to delete. + """ + self._delete("/os-volume-transfer/%s" % base.getid(transfer_id)) diff --git a/cinderclient/v2/client.py b/cinderclient/v2/client.py index eb2760c..9079a52 100644 --- a/cinderclient/v2/client.py +++ b/cinderclient/v2/client.py @@ -22,6 +22,7 @@ from cinderclient.v2 import volume_snapshots from cinderclient.v2 import volume_types from cinderclient.v2 import volume_backups from cinderclient.v2 import volume_backups_restore +from cinderclient.v1 import volume_transfers class Client(object): @@ -58,6 +59,7 @@ class Client(object): self.quotas = quotas.QuotaSetManager(self) self.backups = volume_backups.VolumeBackupManager(self) self.restores = volume_backups_restore.VolumeBackupRestoreManager(self) + self.transfers = volume_transfers.VolumeTransferManager(self) # Add in any extensions... if extensions: diff --git a/cinderclient/v2/shell.py b/cinderclient/v2/shell.py index 4016810..8d7e776 100644 --- a/cinderclient/v2/shell.py +++ b/cinderclient/v2/shell.py @@ -69,6 +69,11 @@ def _find_backup(cs, backup): return utils.find_resource(cs.backups, backup) +def _find_transfer(cs, transfer): + """Get a transfer by ID.""" + return utils.find_resource(cs.transfers, transfer) + + def _print_volume_snapshot(snapshot): utils.print_dict(snapshot._info) @@ -785,3 +790,77 @@ def do_backup_restore(cs, args): """Restore a backup.""" cs.restores.restore(args.backup, args.volume_id) + + +@utils.arg('volume', metavar='', + help='ID of the volume to transfer.') +@utils.arg('--name', + metavar='', + default=None, + help='Optional transfer name. (Default=None)') +@utils.arg('--display-name', + help=argparse.SUPPRESS) +@utils.service_type('volume') +def do_transfer_create(cs, args): + """Creates a volume transfer.""" + if args.display_name is not None: + args.name = args.display_name + + transfer = cs.transfers.create(args.volume, + args.name) + info = dict() + info.update(transfer._info) + + if 'links' in info: + info.pop('links') + + utils.print_dict(info) + + +@utils.arg('transfer', metavar='', + help='ID of the transfer to delete.') +@utils.service_type('volume') +def do_transfer_delete(cs, args): + """Undo a transfer.""" + transfer = _find_transfer(cs, args.transfer) + transfer.delete() + + +@utils.arg('transfer', metavar='', + help='ID of the transfer to accept.') +@utils.arg('auth_key', metavar='', + help='Auth key of the transfer to accept.') +@utils.service_type('volume') +def do_transfer_accept(cs, args): + """Accepts a volume transfer.""" + transfer = cs.transfers.accept(args.transfer, args.auth_key) + info = dict() + info.update(transfer._info) + + if 'links' in info: + info.pop('links') + + utils.print_dict(info) + + +@utils.service_type('volume') +def do_transfer_list(cs, args): + """List all the transfers.""" + transfers = cs.transfers.list() + columns = ['ID', 'Volume ID', 'Name'] + utils.print_list(transfers, columns) + + +@utils.arg('transfer', metavar='', + help='ID of the transfer to accept.') +@utils.service_type('volume') +def do_transfer_show(cs, args): + """Show details about a transfer.""" + transfer = _find_transfer(cs, args.transfer) + info = dict() + info.update(transfer._info) + + if 'links' in info: + info.pop('links') + + utils.print_dict(info) diff --git a/cinderclient/v2/volume_transfers.py b/cinderclient/v2/volume_transfers.py new file mode 100644 index 0000000..8aace02 --- /dev/null +++ b/cinderclient/v2/volume_transfers.py @@ -0,0 +1,82 @@ +# Copyright (C) 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Volume transfer interface (1.1 extension). +""" + +from cinderclient import base + + +class VolumeTransfer(base.Resource): + """Transfer a volume from one tenant to another""" + def __repr__(self): + return "" % self.id + + def delete(self): + """Delete this volume transfer.""" + return self.manager.delete(self) + + +class VolumeTransferManager(base.ManagerWithFind): + """Manage :class:`VolumeTransfer` resources.""" + resource_class = VolumeTransfer + + def create(self, volume_id, name=None): + """Create a volume transfer. + + :param volume_id: The ID of the volume to transfer. + :param name: The name of the transfer. + :rtype: :class:`VolumeTransfer` + """ + body = {'transfer': {'volume_id': volume_id, + 'name': name}} + return self._create('/os-volume-transfer', body, 'transfer') + + def accept(self, transfer_id, auth_key): + """Accept a volume transfer. + + :param transfer_id: The ID of the trasnfer to accept. + :param auth_key: The auth_key of the transfer. + :rtype: :class:`VolumeTransfer` + """ + body = {'accept': {'auth_key': auth_key}} + return self._create('/os-volume-transfer/%s/accept' % transfer_id, + body, 'transfer') + + def get(self, transfer_id): + """Show details of a volume transfer. + + :param transfer_id: The ID of the volume transfer to display. + :rtype: :class:`VolumeTransfer` + """ + return self._get("/os-volume-transfer/%s" % transfer_id, "transfer") + + def list(self, detailed=True): + """Get a list of all volume transfer. + + :rtype: list of :class:`VolumeTransfer` + """ + if detailed is True: + return self._list("/os-volume-transfer/detail", "transfers") + else: + return self._list("/os-volume-transfer", "transfers") + + def delete(self, transfer_id): + """Delete a volume transfer. + + :param transfer_id: The :class:`VolumeTransfer` to delete. + """ + self._delete("/os-volume-transfer/%s" % base.getid(transfer_id))