Deprecate openstack server migrate --host option
Per the discussion at the Train Forum [1] this deprecates the problematic --live option on the server migrate command which, depending on the compute API version used, forcefully bypasses the scheduler and also does not allow you to live migrate a server and let the scheduler pick a host. The --live option is replaced here with two new options: * --live-migration: this simply tells the command you want to perform a live rather than cold migration; if specified with --live the --live-migration option takes priority. * --host: when specified, this will request a target host for the live migration and will be validated by the scheduler; if not specified, the scheduler will pick a host. This option is mutually exclusive with --live. We can build on the --host option by supporting cold migrations with a specified host when using compute API version 2.56 or greater but that will come in a separate change. If the --live option is ever used we log a warning. Note there are several related changes for this issue: - https://review.openstack.org/#/c/628334/ - https://review.openstack.org/#/c/626949/ - https://review.openstack.org/#/c/627801/ - https://review.openstack.org/#/c/589012/ - https://review.openstack.org/#/c/460059/ This change allows us to deprecate the --live option and provide a replacement which is backward compatible without having to use something potentially error-prone like nargs='?'. Closes-Bug: #1411190 [1] https://etherpad.openstack.org/p/DEN-osc-compute-api-gaps Change-Id: I95d3d588e4abeb6848bdccf6915f7b5da40b5d4f
This commit is contained in:
parent
1bc44fcdc6
commit
3057989714
@ -1407,9 +1407,38 @@ class MigrateServer(command.Command):
|
|||||||
help=_('Server (name or ID)'),
|
help=_('Server (name or ID)'),
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
'--live-migration',
|
||||||
|
dest='live_migration',
|
||||||
|
action='store_true',
|
||||||
|
help=_('Live migrate the server. Use the ``--host`` option to '
|
||||||
|
'specify a target host for the migration which will be '
|
||||||
|
'validated by the scheduler.'),
|
||||||
|
)
|
||||||
|
# The --live and --host options are mutually exclusive ways of asking
|
||||||
|
# for a target host during a live migration.
|
||||||
|
host_group = parser.add_mutually_exclusive_group()
|
||||||
|
# TODO(mriedem): Remove --live in the next major version bump after
|
||||||
|
# the Train release.
|
||||||
|
host_group.add_argument(
|
||||||
'--live',
|
'--live',
|
||||||
metavar='<hostname>',
|
metavar='<hostname>',
|
||||||
help=_('Target hostname'),
|
help=_('**Deprecated** This option is problematic in that it '
|
||||||
|
'requires a host and prior to compute API version 2.30, '
|
||||||
|
'specifying a host during live migration will bypass '
|
||||||
|
'validation by the scheduler which could result in '
|
||||||
|
'failures to actually migrate the server to the specified '
|
||||||
|
'host or over-subscribe the host. Use the '
|
||||||
|
'``--live-migration`` option instead. If both this option '
|
||||||
|
'and ``--live-migration`` are used, ``--live-migration`` '
|
||||||
|
'takes priority.'),
|
||||||
|
)
|
||||||
|
# TODO(mriedem): Add support for --os-compute-api-version >= 2.56 where
|
||||||
|
# you can cold migrate to a specified target host.
|
||||||
|
host_group.add_argument(
|
||||||
|
'--host',
|
||||||
|
metavar='<hostname>',
|
||||||
|
help=_('Live migrate the server to the specified host. Requires '
|
||||||
|
'``--os-compute-api-version`` 2.30 or greater.'),
|
||||||
)
|
)
|
||||||
migration_group = parser.add_mutually_exclusive_group()
|
migration_group = parser.add_mutually_exclusive_group()
|
||||||
migration_group.add_argument(
|
migration_group.add_argument(
|
||||||
@ -1447,6 +1476,15 @@ class MigrateServer(command.Command):
|
|||||||
)
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
def _log_warning_for_live(self, parsed_args):
|
||||||
|
if parsed_args.live:
|
||||||
|
# NOTE(mriedem): The --live option requires a host and if
|
||||||
|
# --os-compute-api-version is less than 2.30 it will forcefully
|
||||||
|
# bypass the scheduler which is dangerous.
|
||||||
|
self.log.warning(_(
|
||||||
|
'The --live option has been deprecated. Please use the '
|
||||||
|
'--live-migration option instead.'))
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
|
|
||||||
def _show_progress(progress):
|
def _show_progress(progress):
|
||||||
@ -1460,19 +1498,45 @@ class MigrateServer(command.Command):
|
|||||||
compute_client.servers,
|
compute_client.servers,
|
||||||
parsed_args.server,
|
parsed_args.server,
|
||||||
)
|
)
|
||||||
if parsed_args.live:
|
# Check for live migration.
|
||||||
|
if parsed_args.live or parsed_args.live_migration:
|
||||||
|
# Always log a warning if --live is used.
|
||||||
|
self._log_warning_for_live(parsed_args)
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'host': parsed_args.live,
|
|
||||||
'block_migration': parsed_args.block_migration
|
'block_migration': parsed_args.block_migration
|
||||||
}
|
}
|
||||||
|
# Prefer --live-migration over --live if both are specified.
|
||||||
|
if parsed_args.live_migration:
|
||||||
|
# Technically we could pass a non-None host with
|
||||||
|
# --os-compute-api-version < 2.30 but that is the same thing
|
||||||
|
# as the --live option bypassing the scheduler which we don't
|
||||||
|
# want to support, so if the user is using --live-migration
|
||||||
|
# and --host, we want to enforce that they are using version
|
||||||
|
# 2.30 or greater.
|
||||||
|
if (parsed_args.host and
|
||||||
|
compute_client.api_version <
|
||||||
|
api_versions.APIVersion('2.30')):
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
'--os-compute-api-version 2.30 or greater is required '
|
||||||
|
'when using --host')
|
||||||
|
# The host parameter is required in the API even if None.
|
||||||
|
kwargs['host'] = parsed_args.host
|
||||||
|
else:
|
||||||
|
kwargs['host'] = parsed_args.live
|
||||||
|
|
||||||
if compute_client.api_version < api_versions.APIVersion('2.25'):
|
if compute_client.api_version < api_versions.APIVersion('2.25'):
|
||||||
kwargs['disk_over_commit'] = parsed_args.disk_overcommit
|
kwargs['disk_over_commit'] = parsed_args.disk_overcommit
|
||||||
server.live_migrate(**kwargs)
|
server.live_migrate(**kwargs)
|
||||||
else:
|
else:
|
||||||
if parsed_args.block_migration or parsed_args.disk_overcommit:
|
if (parsed_args.block_migration or parsed_args.disk_overcommit or
|
||||||
raise exceptions.CommandError("--live must be specified if "
|
parsed_args.host):
|
||||||
"--block-migration or "
|
# TODO(mriedem): Allow --host for cold migration if
|
||||||
"--disk-overcommit is specified")
|
# --os-compute-api-version >= 2.56.
|
||||||
|
raise exceptions.CommandError(
|
||||||
|
"--live-migration must be specified if "
|
||||||
|
"--block-migration, --disk-overcommit or --host is "
|
||||||
|
"specified")
|
||||||
|
|
||||||
server.migrate()
|
server.migrate()
|
||||||
|
|
||||||
if parsed_args.wait:
|
if parsed_args.wait:
|
||||||
|
@ -23,6 +23,7 @@ from openstack import exceptions as sdk_exceptions
|
|||||||
from osc_lib import exceptions
|
from osc_lib import exceptions
|
||||||
from osc_lib import utils as common_utils
|
from osc_lib import utils as common_utils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
|
import six
|
||||||
|
|
||||||
from openstackclient.compute.v2 import server
|
from openstackclient.compute.v2 import server
|
||||||
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
|
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
|
||||||
@ -2415,12 +2416,40 @@ class TestServerMigrate(TestServer):
|
|||||||
self.assertNotCalled(self.servers_mock.live_migrate)
|
self.assertNotCalled(self.servers_mock.live_migrate)
|
||||||
self.assertNotCalled(self.servers_mock.migrate)
|
self.assertNotCalled(self.servers_mock.migrate)
|
||||||
|
|
||||||
|
def test_server_migrate_with_host(self):
|
||||||
|
# Tests that --host is not allowed for a cold migration.
|
||||||
|
arglist = [
|
||||||
|
'--host', 'fakehost', self.server.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('live', None),
|
||||||
|
('live_migration', False),
|
||||||
|
('host', 'fakehost'),
|
||||||
|
('block_migration', False),
|
||||||
|
('disk_overcommit', False),
|
||||||
|
('wait', False),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
ex = self.assertRaises(exceptions.CommandError, self.cmd.take_action,
|
||||||
|
parsed_args)
|
||||||
|
|
||||||
|
# Make sure it's the error we expect.
|
||||||
|
self.assertIn("--live-migration must be specified if "
|
||||||
|
"--block-migration, --disk-overcommit or --host is "
|
||||||
|
"specified", six.text_type(ex))
|
||||||
|
self.servers_mock.get.assert_called_with(self.server.id)
|
||||||
|
self.assertNotCalled(self.servers_mock.live_migrate)
|
||||||
|
self.assertNotCalled(self.servers_mock.migrate)
|
||||||
|
|
||||||
def test_server_live_migrate(self):
|
def test_server_live_migrate(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--live', 'fakehost', self.server.id,
|
'--live', 'fakehost', self.server.id,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('live', 'fakehost'),
|
('live', 'fakehost'),
|
||||||
|
('live_migration', False),
|
||||||
|
('host', None),
|
||||||
('block_migration', False),
|
('block_migration', False),
|
||||||
('disk_overcommit', False),
|
('disk_overcommit', False),
|
||||||
('wait', False),
|
('wait', False),
|
||||||
@ -2430,7 +2459,8 @@ class TestServerMigrate(TestServer):
|
|||||||
self.app.client_manager.compute.api_version = \
|
self.app.client_manager.compute.api_version = \
|
||||||
api_versions.APIVersion('2.24')
|
api_versions.APIVersion('2.24')
|
||||||
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
self.servers_mock.get.assert_called_with(self.server.id)
|
self.servers_mock.get.assert_called_with(self.server.id)
|
||||||
self.server.live_migrate.assert_called_with(block_migration=False,
|
self.server.live_migrate.assert_called_with(block_migration=False,
|
||||||
@ -2438,6 +2468,132 @@ class TestServerMigrate(TestServer):
|
|||||||
host='fakehost')
|
host='fakehost')
|
||||||
self.assertNotCalled(self.servers_mock.migrate)
|
self.assertNotCalled(self.servers_mock.migrate)
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
# A warning should have been logged for using --live.
|
||||||
|
mock_warning.assert_called_once()
|
||||||
|
self.assertIn('The --live option has been deprecated.',
|
||||||
|
six.text_type(mock_warning.call_args[0][0]))
|
||||||
|
|
||||||
|
def test_server_live_migrate_host_pre_2_30(self):
|
||||||
|
# Tests that the --host option is not supported for --live-migration
|
||||||
|
# before microversion 2.30 (the test defaults to 2.1).
|
||||||
|
arglist = [
|
||||||
|
'--live-migration', '--host', 'fakehost', self.server.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('live', None),
|
||||||
|
('live_migration', True),
|
||||||
|
('host', 'fakehost'),
|
||||||
|
('block_migration', False),
|
||||||
|
('disk_overcommit', False),
|
||||||
|
('wait', False),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
ex = self.assertRaises(exceptions.CommandError, self.cmd.take_action,
|
||||||
|
parsed_args)
|
||||||
|
|
||||||
|
# Make sure it's the error we expect.
|
||||||
|
self.assertIn('--os-compute-api-version 2.30 or greater is required '
|
||||||
|
'when using --host', six.text_type(ex))
|
||||||
|
|
||||||
|
self.servers_mock.get.assert_called_with(self.server.id)
|
||||||
|
self.assertNotCalled(self.servers_mock.live_migrate)
|
||||||
|
self.assertNotCalled(self.servers_mock.migrate)
|
||||||
|
|
||||||
|
def test_server_live_migrate_no_host(self):
|
||||||
|
# Tests the --live-migration option without --host or --live.
|
||||||
|
arglist = [
|
||||||
|
'--live-migration', self.server.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('live', None),
|
||||||
|
('live_migration', True),
|
||||||
|
('host', None),
|
||||||
|
('block_migration', False),
|
||||||
|
('disk_overcommit', False),
|
||||||
|
('wait', False),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.servers_mock.get.assert_called_with(self.server.id)
|
||||||
|
self.server.live_migrate.assert_called_with(block_migration=False,
|
||||||
|
disk_over_commit=False,
|
||||||
|
host=None)
|
||||||
|
self.assertNotCalled(self.servers_mock.migrate)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
# Since --live wasn't used a warning shouldn't have been logged.
|
||||||
|
mock_warning.assert_not_called()
|
||||||
|
|
||||||
|
def test_server_live_migrate_with_host(self):
|
||||||
|
# Tests the --live-migration option with --host but no --live.
|
||||||
|
# This requires --os-compute-api-version >= 2.30 so the test uses 2.30.
|
||||||
|
arglist = [
|
||||||
|
'--live-migration', '--host', 'fakehost', self.server.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('live', None),
|
||||||
|
('live_migration', True),
|
||||||
|
('host', 'fakehost'),
|
||||||
|
('block_migration', False),
|
||||||
|
('disk_overcommit', False),
|
||||||
|
('wait', False),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.app.client_manager.compute.api_version = \
|
||||||
|
api_versions.APIVersion('2.30')
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.servers_mock.get.assert_called_with(self.server.id)
|
||||||
|
# No disk_overcommit with microversion >= 2.25.
|
||||||
|
self.server.live_migrate.assert_called_with(block_migration=False,
|
||||||
|
host='fakehost')
|
||||||
|
self.assertNotCalled(self.servers_mock.migrate)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_server_live_migrate_without_host_override_live(self):
|
||||||
|
# Tests the --live-migration option without --host and with --live.
|
||||||
|
# The --live-migration option will take precedence and a warning is
|
||||||
|
# logged for using --live.
|
||||||
|
arglist = [
|
||||||
|
'--live', 'fakehost', '--live-migration', self.server.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('live', 'fakehost'),
|
||||||
|
('live_migration', True),
|
||||||
|
('host', None),
|
||||||
|
('block_migration', False),
|
||||||
|
('disk_overcommit', False),
|
||||||
|
('wait', False),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.servers_mock.get.assert_called_with(self.server.id)
|
||||||
|
self.server.live_migrate.assert_called_with(block_migration=False,
|
||||||
|
disk_over_commit=False,
|
||||||
|
host=None)
|
||||||
|
self.assertNotCalled(self.servers_mock.migrate)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
# A warning should have been logged for using --live.
|
||||||
|
mock_warning.assert_called_once()
|
||||||
|
self.assertIn('The --live option has been deprecated.',
|
||||||
|
six.text_type(mock_warning.call_args[0][0]))
|
||||||
|
|
||||||
|
def test_server_live_migrate_live_and_host_mutex(self):
|
||||||
|
# Tests specifying both the --live and --host options which are in a
|
||||||
|
# mutex group so argparse should fail.
|
||||||
|
arglist = [
|
||||||
|
'--live', 'fakehost', '--host', 'fakehost', self.server.id,
|
||||||
|
]
|
||||||
|
self.assertRaises(utils.ParserException,
|
||||||
|
self.check_parser, self.cmd, arglist, verify_args=[])
|
||||||
|
|
||||||
def test_server_block_live_migrate(self):
|
def test_server_block_live_migrate(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
deprecations:
|
||||||
|
- |
|
||||||
|
The ``--live`` option on the ``openstack server migrate`` command has
|
||||||
|
been deprecated and is being replaced with two new options:
|
||||||
|
|
||||||
|
* ``--live-migration``: This will signal that the migration is a live
|
||||||
|
migration.
|
||||||
|
* ``--host``: This can be used to request a target host for the live
|
||||||
|
migration but requires ``--os-compute-api-version`` 2.30 or greater
|
||||||
|
so the requested host can be validated by the scheduler.
|
||||||
|
|
||||||
|
The ``--live`` option is problematic in that it requires a host and
|
||||||
|
prior to compute API version 2.30, specifying a host during live migration
|
||||||
|
will bypass validation by the scheduler which could result in failures to
|
||||||
|
actually migrate the server to the specified host or over-subscribe the
|
||||||
|
host.
|
||||||
|
|
||||||
|
The ``--live`` and ``--host`` options are mutually exclusive. Furthermore,
|
||||||
|
if both the ``--live`` and ``--live-migration`` options are used the
|
||||||
|
``--live-migration`` option takes priority.
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
`Bug 1411190`_ has been fixed by providing a ``--live-migration`` and
|
||||||
|
``--host`` option to the ``openstack server migrate`` command.
|
||||||
|
|
||||||
|
.. _Bug 1411190: https://bugs.launchpad.net/python-openstackclient/+bug/1411190
|
Loading…
Reference in New Issue
Block a user