Skip to content

Commit 3057989

Browse files
committed
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
1 parent 1bc44fc commit 3057989

3 files changed

Lines changed: 255 additions & 8 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,9 +1407,38 @@ def get_parser(self, prog_name):
14071407
help=_('Server (name or ID)'),
14081408
)
14091409
parser.add_argument(
1410+
'--live-migration',
1411+
dest='live_migration',
1412+
action='store_true',
1413+
help=_('Live migrate the server. Use the ``--host`` option to '
1414+
'specify a target host for the migration which will be '
1415+
'validated by the scheduler.'),
1416+
)
1417+
# The --live and --host options are mutually exclusive ways of asking
1418+
# for a target host during a live migration.
1419+
host_group = parser.add_mutually_exclusive_group()
1420+
# TODO(mriedem): Remove --live in the next major version bump after
1421+
# the Train release.
1422+
host_group.add_argument(
14101423
'--live',
14111424
metavar='<hostname>',
1412-
help=_('Target hostname'),
1425+
help=_('**Deprecated** This option is problematic in that it '
1426+
'requires a host and prior to compute API version 2.30, '
1427+
'specifying a host during live migration will bypass '
1428+
'validation by the scheduler which could result in '
1429+
'failures to actually migrate the server to the specified '
1430+
'host or over-subscribe the host. Use the '
1431+
'``--live-migration`` option instead. If both this option '
1432+
'and ``--live-migration`` are used, ``--live-migration`` '
1433+
'takes priority.'),
1434+
)
1435+
# TODO(mriedem): Add support for --os-compute-api-version >= 2.56 where
1436+
# you can cold migrate to a specified target host.
1437+
host_group.add_argument(
1438+
'--host',
1439+
metavar='<hostname>',
1440+
help=_('Live migrate the server to the specified host. Requires '
1441+
'``--os-compute-api-version`` 2.30 or greater.'),
14131442
)
14141443
migration_group = parser.add_mutually_exclusive_group()
14151444
migration_group.add_argument(
@@ -1447,6 +1476,15 @@ def get_parser(self, prog_name):
14471476
)
14481477
return parser
14491478

1479+
def _log_warning_for_live(self, parsed_args):
1480+
if parsed_args.live:
1481+
# NOTE(mriedem): The --live option requires a host and if
1482+
# --os-compute-api-version is less than 2.30 it will forcefully
1483+
# bypass the scheduler which is dangerous.
1484+
self.log.warning(_(
1485+
'The --live option has been deprecated. Please use the '
1486+
'--live-migration option instead.'))
1487+
14501488
def take_action(self, parsed_args):
14511489

14521490
def _show_progress(progress):
@@ -1460,19 +1498,45 @@ def _show_progress(progress):
14601498
compute_client.servers,
14611499
parsed_args.server,
14621500
)
1463-
if parsed_args.live:
1501+
# Check for live migration.
1502+
if parsed_args.live or parsed_args.live_migration:
1503+
# Always log a warning if --live is used.
1504+
self._log_warning_for_live(parsed_args)
14641505
kwargs = {
1465-
'host': parsed_args.live,
14661506
'block_migration': parsed_args.block_migration
14671507
}
1508+
# Prefer --live-migration over --live if both are specified.
1509+
if parsed_args.live_migration:
1510+
# Technically we could pass a non-None host with
1511+
# --os-compute-api-version < 2.30 but that is the same thing
1512+
# as the --live option bypassing the scheduler which we don't
1513+
# want to support, so if the user is using --live-migration
1514+
# and --host, we want to enforce that they are using version
1515+
# 2.30 or greater.
1516+
if (parsed_args.host and
1517+
compute_client.api_version <
1518+
api_versions.APIVersion('2.30')):
1519+
raise exceptions.CommandError(
1520+
'--os-compute-api-version 2.30 or greater is required '
1521+
'when using --host')
1522+
# The host parameter is required in the API even if None.
1523+
kwargs['host'] = parsed_args.host
1524+
else:
1525+
kwargs['host'] = parsed_args.live
1526+
14681527
if compute_client.api_version < api_versions.APIVersion('2.25'):
14691528
kwargs['disk_over_commit'] = parsed_args.disk_overcommit
14701529
server.live_migrate(**kwargs)
14711530
else:
1472-
if parsed_args.block_migration or parsed_args.disk_overcommit:
1473-
raise exceptions.CommandError("--live must be specified if "
1474-
"--block-migration or "
1475-
"--disk-overcommit is specified")
1531+
if (parsed_args.block_migration or parsed_args.disk_overcommit or
1532+
parsed_args.host):
1533+
# TODO(mriedem): Allow --host for cold migration if
1534+
# --os-compute-api-version >= 2.56.
1535+
raise exceptions.CommandError(
1536+
"--live-migration must be specified if "
1537+
"--block-migration, --disk-overcommit or --host is "
1538+
"specified")
1539+
14761540
server.migrate()
14771541

14781542
if parsed_args.wait:

openstackclient/tests/unit/compute/v2/test_server.py

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from osc_lib import exceptions
2424
from osc_lib import utils as common_utils
2525
from oslo_utils import timeutils
26+
import six
2627

2728
from openstackclient.compute.v2 import server
2829
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
@@ -2415,12 +2416,40 @@ def test_server_migrate_with_disk_overcommit(self):
24152416
self.assertNotCalled(self.servers_mock.live_migrate)
24162417
self.assertNotCalled(self.servers_mock.migrate)
24172418

2419+
def test_server_migrate_with_host(self):
2420+
# Tests that --host is not allowed for a cold migration.
2421+
arglist = [
2422+
'--host', 'fakehost', self.server.id,
2423+
]
2424+
verifylist = [
2425+
('live', None),
2426+
('live_migration', False),
2427+
('host', 'fakehost'),
2428+
('block_migration', False),
2429+
('disk_overcommit', False),
2430+
('wait', False),
2431+
]
2432+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2433+
2434+
ex = self.assertRaises(exceptions.CommandError, self.cmd.take_action,
2435+
parsed_args)
2436+
2437+
# Make sure it's the error we expect.
2438+
self.assertIn("--live-migration must be specified if "
2439+
"--block-migration, --disk-overcommit or --host is "
2440+
"specified", six.text_type(ex))
2441+
self.servers_mock.get.assert_called_with(self.server.id)
2442+
self.assertNotCalled(self.servers_mock.live_migrate)
2443+
self.assertNotCalled(self.servers_mock.migrate)
2444+
24182445
def test_server_live_migrate(self):
24192446
arglist = [
24202447
'--live', 'fakehost', self.server.id,
24212448
]
24222449
verifylist = [
24232450
('live', 'fakehost'),
2451+
('live_migration', False),
2452+
('host', None),
24242453
('block_migration', False),
24252454
('disk_overcommit', False),
24262455
('wait', False),
@@ -2430,14 +2459,141 @@ def test_server_live_migrate(self):
24302459
self.app.client_manager.compute.api_version = \
24312460
api_versions.APIVersion('2.24')
24322461

2433-
result = self.cmd.take_action(parsed_args)
2462+
with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
2463+
result = self.cmd.take_action(parsed_args)
24342464

24352465
self.servers_mock.get.assert_called_with(self.server.id)
24362466
self.server.live_migrate.assert_called_with(block_migration=False,
24372467
disk_over_commit=False,
24382468
host='fakehost')
24392469
self.assertNotCalled(self.servers_mock.migrate)
24402470
self.assertIsNone(result)
2471+
# A warning should have been logged for using --live.
2472+
mock_warning.assert_called_once()
2473+
self.assertIn('The --live option has been deprecated.',
2474+
six.text_type(mock_warning.call_args[0][0]))
2475+
2476+
def test_server_live_migrate_host_pre_2_30(self):
2477+
# Tests that the --host option is not supported for --live-migration
2478+
# before microversion 2.30 (the test defaults to 2.1).
2479+
arglist = [
2480+
'--live-migration', '--host', 'fakehost', self.server.id,
2481+
]
2482+
verifylist = [
2483+
('live', None),
2484+
('live_migration', True),
2485+
('host', 'fakehost'),
2486+
('block_migration', False),
2487+
('disk_overcommit', False),
2488+
('wait', False),
2489+
]
2490+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2491+
2492+
ex = self.assertRaises(exceptions.CommandError, self.cmd.take_action,
2493+
parsed_args)
2494+
2495+
# Make sure it's the error we expect.
2496+
self.assertIn('--os-compute-api-version 2.30 or greater is required '
2497+
'when using --host', six.text_type(ex))
2498+
2499+
self.servers_mock.get.assert_called_with(self.server.id)
2500+
self.assertNotCalled(self.servers_mock.live_migrate)
2501+
self.assertNotCalled(self.servers_mock.migrate)
2502+
2503+
def test_server_live_migrate_no_host(self):
2504+
# Tests the --live-migration option without --host or --live.
2505+
arglist = [
2506+
'--live-migration', self.server.id,
2507+
]
2508+
verifylist = [
2509+
('live', None),
2510+
('live_migration', True),
2511+
('host', None),
2512+
('block_migration', False),
2513+
('disk_overcommit', False),
2514+
('wait', False),
2515+
]
2516+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2517+
2518+
with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
2519+
result = self.cmd.take_action(parsed_args)
2520+
2521+
self.servers_mock.get.assert_called_with(self.server.id)
2522+
self.server.live_migrate.assert_called_with(block_migration=False,
2523+
disk_over_commit=False,
2524+
host=None)
2525+
self.assertNotCalled(self.servers_mock.migrate)
2526+
self.assertIsNone(result)
2527+
# Since --live wasn't used a warning shouldn't have been logged.
2528+
mock_warning.assert_not_called()
2529+
2530+
def test_server_live_migrate_with_host(self):
2531+
# Tests the --live-migration option with --host but no --live.
2532+
# This requires --os-compute-api-version >= 2.30 so the test uses 2.30.
2533+
arglist = [
2534+
'--live-migration', '--host', 'fakehost', self.server.id,
2535+
]
2536+
verifylist = [
2537+
('live', None),
2538+
('live_migration', True),
2539+
('host', 'fakehost'),
2540+
('block_migration', False),
2541+
('disk_overcommit', False),
2542+
('wait', False),
2543+
]
2544+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2545+
2546+
self.app.client_manager.compute.api_version = \
2547+
api_versions.APIVersion('2.30')
2548+
2549+
result = self.cmd.take_action(parsed_args)
2550+
2551+
self.servers_mock.get.assert_called_with(self.server.id)
2552+
# No disk_overcommit with microversion >= 2.25.
2553+
self.server.live_migrate.assert_called_with(block_migration=False,
2554+
host='fakehost')
2555+
self.assertNotCalled(self.servers_mock.migrate)
2556+
self.assertIsNone(result)
2557+
2558+
def test_server_live_migrate_without_host_override_live(self):
2559+
# Tests the --live-migration option without --host and with --live.
2560+
# The --live-migration option will take precedence and a warning is
2561+
# logged for using --live.
2562+
arglist = [
2563+
'--live', 'fakehost', '--live-migration', self.server.id,
2564+
]
2565+
verifylist = [
2566+
('live', 'fakehost'),
2567+
('live_migration', True),
2568+
('host', None),
2569+
('block_migration', False),
2570+
('disk_overcommit', False),
2571+
('wait', False),
2572+
]
2573+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2574+
2575+
with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
2576+
result = self.cmd.take_action(parsed_args)
2577+
2578+
self.servers_mock.get.assert_called_with(self.server.id)
2579+
self.server.live_migrate.assert_called_with(block_migration=False,
2580+
disk_over_commit=False,
2581+
host=None)
2582+
self.assertNotCalled(self.servers_mock.migrate)
2583+
self.assertIsNone(result)
2584+
# A warning should have been logged for using --live.
2585+
mock_warning.assert_called_once()
2586+
self.assertIn('The --live option has been deprecated.',
2587+
six.text_type(mock_warning.call_args[0][0]))
2588+
2589+
def test_server_live_migrate_live_and_host_mutex(self):
2590+
# Tests specifying both the --live and --host options which are in a
2591+
# mutex group so argparse should fail.
2592+
arglist = [
2593+
'--live', 'fakehost', '--host', 'fakehost', self.server.id,
2594+
]
2595+
self.assertRaises(utils.ParserException,
2596+
self.check_parser, self.cmd, arglist, verify_args=[])
24412597

24422598
def test_server_block_live_migrate(self):
24432599
arglist = [
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
deprecations:
3+
- |
4+
The ``--live`` option on the ``openstack server migrate`` command has
5+
been deprecated and is being replaced with two new options:
6+
7+
* ``--live-migration``: This will signal that the migration is a live
8+
migration.
9+
* ``--host``: This can be used to request a target host for the live
10+
migration but requires ``--os-compute-api-version`` 2.30 or greater
11+
so the requested host can be validated by the scheduler.
12+
13+
The ``--live`` option is problematic in that it requires a host and
14+
prior to compute API version 2.30, specifying a host during live migration
15+
will bypass validation by the scheduler which could result in failures to
16+
actually migrate the server to the specified host or over-subscribe the
17+
host.
18+
19+
The ``--live`` and ``--host`` options are mutually exclusive. Furthermore,
20+
if both the ``--live`` and ``--live-migration`` options are used the
21+
``--live-migration`` option takes priority.
22+
fixes:
23+
- |
24+
`Bug 1411190`_ has been fixed by providing a ``--live-migration`` and
25+
``--host`` option to the ``openstack server migrate`` command.
26+
27+
.. _Bug 1411190: https://bugs.launchpad.net/python-openstackclient/+bug/1411190

0 commit comments

Comments
 (0)