Add "volume migrate" command
Add "volume migrate" command in volume v1 and v2 to support migrating volume to a new host Change-Id: Ie4e6037171a31a872006a13f9fd1e15eaa627c26 Implements: bp cinder-command-support
This commit is contained in:
parent
5e3ec1b42f
commit
d7c8bb88e4
@ -197,6 +197,48 @@ List volumes
|
|||||||
|
|
||||||
*Volume version 2 only*
|
*Volume version 2 only*
|
||||||
|
|
||||||
|
volume migrate
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Migrate volume to a new host
|
||||||
|
|
||||||
|
.. program:: volume migrate
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
os volume migrate
|
||||||
|
--host <host>
|
||||||
|
[--force-host-copy]
|
||||||
|
[--lock-volume | --unlock-volume]
|
||||||
|
<volume>
|
||||||
|
|
||||||
|
.. option:: --host <host>
|
||||||
|
|
||||||
|
Destination host (takes the form: host@backend-name#pool) (required)
|
||||||
|
|
||||||
|
.. option:: --force-host-copy
|
||||||
|
|
||||||
|
Enable generic host-based force-migration,
|
||||||
|
which bypasses driver optimizations
|
||||||
|
|
||||||
|
.. option:: --lock-volume
|
||||||
|
|
||||||
|
If specified, the volume state will be locked and will not allow
|
||||||
|
a migration to be aborted (possibly by another operation)
|
||||||
|
|
||||||
|
*Volume version 2 only*
|
||||||
|
|
||||||
|
.. option:: --unlock-volume
|
||||||
|
|
||||||
|
If specified, the volume state will not be locked and the a
|
||||||
|
migration can be aborted (default) (possibly by another operation)
|
||||||
|
|
||||||
|
*Volume version 2 only*
|
||||||
|
|
||||||
|
.. _volume_migrate-volume:
|
||||||
|
.. describe:: <volume>
|
||||||
|
|
||||||
|
Volume to migrate (name or ID)
|
||||||
|
|
||||||
volume set
|
volume set
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
@ -230,8 +230,8 @@ Those actions with an opposite action are noted in parens if applicable.
|
|||||||
* ``issue`` (``revoke``) - issue a token
|
* ``issue`` (``revoke``) - issue a token
|
||||||
* ``list`` - display summary information about multiple objects
|
* ``list`` - display summary information about multiple objects
|
||||||
* ``lock`` (``unlock``) - lock one or more servers so that non-admin user won't be able to execute actions
|
* ``lock`` (``unlock``) - lock one or more servers so that non-admin user won't be able to execute actions
|
||||||
* ``migrate`` - move a server to a different host; ``--live`` performs a
|
* ``migrate`` - move a server or a volume to a different host; ``--live`` performs a
|
||||||
live migration if possible
|
live server migration if possible
|
||||||
* ``pause`` (``unpause``) - stop one or more servers and leave them in memory
|
* ``pause`` (``unpause``) - stop one or more servers and leave them in memory
|
||||||
* ``reboot`` - forcibly reboot a server
|
* ``reboot`` - forcibly reboot a server
|
||||||
* ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create
|
* ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create
|
||||||
|
@ -739,6 +739,68 @@ class TestVolumeList(TestVolume):
|
|||||||
self.cmd, arglist, verifylist)
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVolumeMigrate(TestVolume):
|
||||||
|
|
||||||
|
_volume = volume_fakes.FakeVolume.create_one_volume()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestVolumeMigrate, self).setUp()
|
||||||
|
|
||||||
|
self.volumes_mock.get.return_value = self._volume
|
||||||
|
self.volumes_mock.migrate_volume.return_value = None
|
||||||
|
# Get the command object to test
|
||||||
|
self.cmd = volume.MigrateVolume(self.app, None)
|
||||||
|
|
||||||
|
def test_volume_migrate(self):
|
||||||
|
arglist = [
|
||||||
|
"--host", "host@backend-name#pool",
|
||||||
|
self._volume.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("force_host_copy", False),
|
||||||
|
("host", "host@backend-name#pool"),
|
||||||
|
("volume", self._volume.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.volumes_mock.get.assert_called_once_with(self._volume.id)
|
||||||
|
self.volumes_mock.migrate_volume.assert_called_once_with(
|
||||||
|
self._volume.id, "host@backend-name#pool", False)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_volume_migrate_with_option(self):
|
||||||
|
arglist = [
|
||||||
|
"--force-host-copy",
|
||||||
|
"--host", "host@backend-name#pool",
|
||||||
|
self._volume.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("force_host_copy", True),
|
||||||
|
("host", "host@backend-name#pool"),
|
||||||
|
("volume", self._volume.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.volumes_mock.get.assert_called_once_with(self._volume.id)
|
||||||
|
self.volumes_mock.migrate_volume.assert_called_once_with(
|
||||||
|
self._volume.id, "host@backend-name#pool", True)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_volume_migrate_without_host(self):
|
||||||
|
arglist = [
|
||||||
|
self._volume.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("force_host_copy", False),
|
||||||
|
("volume", self._volume.id),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRaises(tests_utils.ParserException, self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
class TestVolumeSet(TestVolume):
|
class TestVolumeSet(TestVolume):
|
||||||
|
|
||||||
_volume = volume_fakes.FakeVolume.create_one_volume()
|
_volume = volume_fakes.FakeVolume.create_one_volume()
|
||||||
|
@ -996,6 +996,96 @@ class TestVolumeList(TestVolume):
|
|||||||
self.cmd, arglist, verifylist)
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
|
class TestVolumeMigrate(TestVolume):
|
||||||
|
|
||||||
|
_volume = volume_fakes.FakeVolume.create_one_volume()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestVolumeMigrate, self).setUp()
|
||||||
|
|
||||||
|
self.volumes_mock.get.return_value = self._volume
|
||||||
|
self.volumes_mock.migrate_volume.return_value = None
|
||||||
|
# Get the command object to test
|
||||||
|
self.cmd = volume.MigrateVolume(self.app, None)
|
||||||
|
|
||||||
|
def test_volume_migrate(self):
|
||||||
|
arglist = [
|
||||||
|
"--host", "host@backend-name#pool",
|
||||||
|
self._volume.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("force_host_copy", False),
|
||||||
|
("lock_volume", False),
|
||||||
|
("unlock_volume", False),
|
||||||
|
("host", "host@backend-name#pool"),
|
||||||
|
("volume", self._volume.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.volumes_mock.get.assert_called_once_with(self._volume.id)
|
||||||
|
self.volumes_mock.migrate_volume.assert_called_once_with(
|
||||||
|
self._volume.id, "host@backend-name#pool", False, False)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_volume_migrate_with_option(self):
|
||||||
|
arglist = [
|
||||||
|
"--force-host-copy",
|
||||||
|
"--lock-volume",
|
||||||
|
"--host", "host@backend-name#pool",
|
||||||
|
self._volume.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("force_host_copy", True),
|
||||||
|
("lock_volume", True),
|
||||||
|
("unlock_volume", False),
|
||||||
|
("host", "host@backend-name#pool"),
|
||||||
|
("volume", self._volume.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.volumes_mock.get.assert_called_once_with(self._volume.id)
|
||||||
|
self.volumes_mock.migrate_volume.assert_called_once_with(
|
||||||
|
self._volume.id, "host@backend-name#pool", True, True)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_volume_migrate_with_unlock_volume(self):
|
||||||
|
arglist = [
|
||||||
|
"--unlock-volume",
|
||||||
|
"--host", "host@backend-name#pool",
|
||||||
|
self._volume.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("force_host_copy", False),
|
||||||
|
("lock_volume", False),
|
||||||
|
("unlock_volume", True),
|
||||||
|
("host", "host@backend-name#pool"),
|
||||||
|
("volume", self._volume.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.volumes_mock.get.assert_called_once_with(self._volume.id)
|
||||||
|
self.volumes_mock.migrate_volume.assert_called_once_with(
|
||||||
|
self._volume.id, "host@backend-name#pool", False, False)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_volume_migrate_without_host(self):
|
||||||
|
arglist = [
|
||||||
|
self._volume.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("force_host_copy", False),
|
||||||
|
("lock_volume", False),
|
||||||
|
("unlock_volume", False),
|
||||||
|
("volume", self._volume.id),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRaises(tests_utils.ParserException, self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
class TestVolumeSet(TestVolume):
|
class TestVolumeSet(TestVolume):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -344,6 +344,37 @@ class ListVolume(command.Lister):
|
|||||||
) for s in data))
|
) for s in data))
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateVolume(command.Command):
|
||||||
|
"""Migrate volume to a new host"""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(MigrateVolume, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'volume',
|
||||||
|
metavar="<volume>",
|
||||||
|
help=_("Volume to migrate (name or ID)")
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--host',
|
||||||
|
metavar="<host>",
|
||||||
|
required=True,
|
||||||
|
help=_("Destination host (takes the form: host@backend-name#pool)")
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--force-host-copy',
|
||||||
|
action="store_true",
|
||||||
|
help=_("Enable generic host-based force-migration, "
|
||||||
|
"which bypasses driver optimizations")
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
volume_client = self.app.client_manager.volume
|
||||||
|
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
|
||||||
|
volume_client.volumes.migrate_volume(volume.id, parsed_args.host,
|
||||||
|
parsed_args.force_host_copy,)
|
||||||
|
|
||||||
|
|
||||||
class SetVolume(command.Command):
|
class SetVolume(command.Command):
|
||||||
"""Set volume properties"""
|
"""Set volume properties"""
|
||||||
|
|
||||||
|
@ -409,6 +409,53 @@ class ListVolume(command.Lister):
|
|||||||
) for s in data))
|
) for s in data))
|
||||||
|
|
||||||
|
|
||||||
|
class MigrateVolume(command.Command):
|
||||||
|
"""Migrate volume to a new host"""
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(MigrateVolume, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'volume',
|
||||||
|
metavar="<volume>",
|
||||||
|
help=_("Volume to migrate (name or ID)")
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--host',
|
||||||
|
metavar="<host>",
|
||||||
|
required=True,
|
||||||
|
help=_("Destination host (takes the form: host@backend-name#pool)")
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--force-host-copy',
|
||||||
|
action="store_true",
|
||||||
|
help=_("Enable generic host-based force-migration, "
|
||||||
|
"which bypasses driver optimizations")
|
||||||
|
)
|
||||||
|
lock_group = parser.add_mutually_exclusive_group()
|
||||||
|
lock_group.add_argument(
|
||||||
|
'--lock-volume',
|
||||||
|
action="store_true",
|
||||||
|
help=_("If specified, the volume state will be locked "
|
||||||
|
"and will not allow a migration to be aborted "
|
||||||
|
"(possibly by another operation)")
|
||||||
|
)
|
||||||
|
lock_group.add_argument(
|
||||||
|
'--unlock-volume',
|
||||||
|
action="store_true",
|
||||||
|
help=_("If specified, the volume state will not be "
|
||||||
|
"locked and the a migration can be aborted "
|
||||||
|
"(default) (possibly by another operation)")
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
volume_client = self.app.client_manager.volume
|
||||||
|
volume = utils.find_resource(volume_client.volumes, parsed_args.volume)
|
||||||
|
volume_client.volumes.migrate_volume(volume.id, parsed_args.host,
|
||||||
|
parsed_args.force_host_copy,
|
||||||
|
parsed_args.lock_volume,)
|
||||||
|
|
||||||
|
|
||||||
class SetVolume(command.Command):
|
class SetVolume(command.Command):
|
||||||
"""Set volume properties"""
|
"""Set volume properties"""
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Add ``volume migrate`` command.
|
||||||
|
[Blueprint `cinder-command-support <https://blueprints.launchpad.net/python-openstackclient/+spec/cinder-command-support>`_]
|
@ -463,6 +463,7 @@ openstack.volume.v1 =
|
|||||||
volume_create = openstackclient.volume.v1.volume:CreateVolume
|
volume_create = openstackclient.volume.v1.volume:CreateVolume
|
||||||
volume_delete = openstackclient.volume.v1.volume:DeleteVolume
|
volume_delete = openstackclient.volume.v1.volume:DeleteVolume
|
||||||
volume_list = openstackclient.volume.v1.volume:ListVolume
|
volume_list = openstackclient.volume.v1.volume:ListVolume
|
||||||
|
volume_migrate = openstackclient.volume.v1.volume:MigrateVolume
|
||||||
volume_set = openstackclient.volume.v1.volume:SetVolume
|
volume_set = openstackclient.volume.v1.volume:SetVolume
|
||||||
volume_show = openstackclient.volume.v1.volume:ShowVolume
|
volume_show = openstackclient.volume.v1.volume:ShowVolume
|
||||||
volume_unset = openstackclient.volume.v1.volume:UnsetVolume
|
volume_unset = openstackclient.volume.v1.volume:UnsetVolume
|
||||||
@ -517,6 +518,7 @@ openstack.volume.v2 =
|
|||||||
volume_create = openstackclient.volume.v2.volume:CreateVolume
|
volume_create = openstackclient.volume.v2.volume:CreateVolume
|
||||||
volume_delete = openstackclient.volume.v2.volume:DeleteVolume
|
volume_delete = openstackclient.volume.v2.volume:DeleteVolume
|
||||||
volume_list = openstackclient.volume.v2.volume:ListVolume
|
volume_list = openstackclient.volume.v2.volume:ListVolume
|
||||||
|
volume_migrate = openstackclient.volume.v2.volume:MigrateVolume
|
||||||
volume_set = openstackclient.volume.v2.volume:SetVolume
|
volume_set = openstackclient.volume.v2.volume:SetVolume
|
||||||
volume_show = openstackclient.volume.v2.volume:ShowVolume
|
volume_show = openstackclient.volume.v2.volume:ShowVolume
|
||||||
volume_unset = openstackclient.volume.v2.volume:UnsetVolume
|
volume_unset = openstackclient.volume.v2.volume:UnsetVolume
|
||||||
|
Loading…
Reference in New Issue
Block a user