Skip to content

Commit 25ccca4

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Deprecate openstack server migrate --host option"
2 parents 86d7490 + 3057989 commit 25ccca4

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
@@ -1445,9 +1445,38 @@ def get_parser(self, prog_name):
14451445
help=_('Server (name or ID)'),
14461446
)
14471447
parser.add_argument(
1448+
'--live-migration',
1449+
dest='live_migration',
1450+
action='store_true',
1451+
help=_('Live migrate the server. Use the ``--host`` option to '
1452+
'specify a target host for the migration which will be '
1453+
'validated by the scheduler.'),
1454+
)
1455+
# The --live and --host options are mutually exclusive ways of asking
1456+
# for a target host during a live migration.
1457+
host_group = parser.add_mutually_exclusive_group()
1458+
# TODO(mriedem): Remove --live in the next major version bump after
1459+
# the Train release.
1460+
host_group.add_argument(
14481461
'--live',
14491462
metavar='<hostname>',
1450-
help=_('Target hostname'),
1463+
help=_('**Deprecated** This option is problematic in that it '
1464+
'requires a host and prior to compute API version 2.30, '
1465+
'specifying a host during live migration will bypass '
1466+
'validation by the scheduler which could result in '
1467+
'failures to actually migrate the server to the specified '
1468+
'host or over-subscribe the host. Use the '
1469+
'``--live-migration`` option instead. If both this option '
1470+
'and ``--live-migration`` are used, ``--live-migration`` '
1471+
'takes priority.'),
1472+
)
1473+
# TODO(mriedem): Add support for --os-compute-api-version >= 2.56 where
1474+
# you can cold migrate to a specified target host.
1475+
host_group.add_argument(
1476+
'--host',
1477+
metavar='<hostname>',
1478+
help=_('Live migrate the server to the specified host. Requires '
1479+
'``--os-compute-api-version`` 2.30 or greater.'),
14511480
)
14521481
migration_group = parser.add_mutually_exclusive_group()
14531482
migration_group.add_argument(
@@ -1485,6 +1514,15 @@ def get_parser(self, prog_name):
14851514
)
14861515
return parser
14871516

1517+
def _log_warning_for_live(self, parsed_args):
1518+
if parsed_args.live:
1519+
# NOTE(mriedem): The --live option requires a host and if
1520+
# --os-compute-api-version is less than 2.30 it will forcefully
1521+
# bypass the scheduler which is dangerous.
1522+
self.log.warning(_(
1523+
'The --live option has been deprecated. Please use the '
1524+
'--live-migration option instead.'))
1525+
14881526
def take_action(self, parsed_args):
14891527

14901528
def _show_progress(progress):
@@ -1498,19 +1536,45 @@ def _show_progress(progress):
14981536
compute_client.servers,
14991537
parsed_args.server,
15001538
)
1501-
if parsed_args.live:
1539+
# Check for live migration.
1540+
if parsed_args.live or parsed_args.live_migration:
1541+
# Always log a warning if --live is used.
1542+
self._log_warning_for_live(parsed_args)
15021543
kwargs = {
1503-
'host': parsed_args.live,
15041544
'block_migration': parsed_args.block_migration
15051545
}
1546+
# Prefer --live-migration over --live if both are specified.
1547+
if parsed_args.live_migration:
1548+
# Technically we could pass a non-None host with
1549+
# --os-compute-api-version < 2.30 but that is the same thing
1550+
# as the --live option bypassing the scheduler which we don't
1551+
# want to support, so if the user is using --live-migration
1552+
# and --host, we want to enforce that they are using version
1553+
# 2.30 or greater.
1554+
if (parsed_args.host and
1555+
compute_client.api_version <
1556+
api_versions.APIVersion('2.30')):
1557+
raise exceptions.CommandError(
1558+
'--os-compute-api-version 2.30 or greater is required '
1559+
'when using --host')
1560+
# The host parameter is required in the API even if None.
1561+
kwargs['host'] = parsed_args.host
1562+
else:
1563+
kwargs['host'] = parsed_args.live
1564+
15061565
if compute_client.api_version < api_versions.APIVersion('2.25'):
15071566
kwargs['disk_over_commit'] = parsed_args.disk_overcommit
15081567
server.live_migrate(**kwargs)
15091568
else:
1510-
if parsed_args.block_migration or parsed_args.disk_overcommit:
1511-
raise exceptions.CommandError("--live must be specified if "
1512-
"--block-migration or "
1513-
"--disk-overcommit is specified")
1569+
if (parsed_args.block_migration or parsed_args.disk_overcommit or
1570+
parsed_args.host):
1571+
# TODO(mriedem): Allow --host for cold migration if
1572+
# --os-compute-api-version >= 2.56.
1573+
raise exceptions.CommandError(
1574+
"--live-migration must be specified if "
1575+
"--block-migration, --disk-overcommit or --host is "
1576+
"specified")
1577+
15141578
server.migrate()
15151579

15161580
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
@@ -2565,12 +2566,40 @@ def test_server_migrate_with_disk_overcommit(self):
25652566
self.assertNotCalled(self.servers_mock.live_migrate)
25662567
self.assertNotCalled(self.servers_mock.migrate)
25672568

2569+
def test_server_migrate_with_host(self):
2570+
# Tests that --host is not allowed for a cold migration.
2571+
arglist = [
2572+
'--host', 'fakehost', self.server.id,
2573+
]
2574+
verifylist = [
2575+
('live', None),
2576+
('live_migration', False),
2577+
('host', 'fakehost'),
2578+
('block_migration', False),
2579+
('disk_overcommit', False),
2580+
('wait', False),
2581+
]
2582+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2583+
2584+
ex = self.assertRaises(exceptions.CommandError, self.cmd.take_action,
2585+
parsed_args)
2586+
2587+
# Make sure it's the error we expect.
2588+
self.assertIn("--live-migration must be specified if "
2589+
"--block-migration, --disk-overcommit or --host is "
2590+
"specified", six.text_type(ex))
2591+
self.servers_mock.get.assert_called_with(self.server.id)
2592+
self.assertNotCalled(self.servers_mock.live_migrate)
2593+
self.assertNotCalled(self.servers_mock.migrate)
2594+
25682595
def test_server_live_migrate(self):
25692596
arglist = [
25702597
'--live', 'fakehost', self.server.id,
25712598
]
25722599
verifylist = [
25732600
('live', 'fakehost'),
2601+
('live_migration', False),
2602+
('host', None),
25742603
('block_migration', False),
25752604
('disk_overcommit', False),
25762605
('wait', False),
@@ -2580,14 +2609,141 @@ def test_server_live_migrate(self):
25802609
self.app.client_manager.compute.api_version = \
25812610
api_versions.APIVersion('2.24')
25822611

2583-
result = self.cmd.take_action(parsed_args)
2612+
with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
2613+
result = self.cmd.take_action(parsed_args)
25842614

25852615
self.servers_mock.get.assert_called_with(self.server.id)
25862616
self.server.live_migrate.assert_called_with(block_migration=False,
25872617
disk_over_commit=False,
25882618
host='fakehost')
25892619
self.assertNotCalled(self.servers_mock.migrate)
25902620
self.assertIsNone(result)
2621+
# A warning should have been logged for using --live.
2622+
mock_warning.assert_called_once()
2623+
self.assertIn('The --live option has been deprecated.',
2624+
six.text_type(mock_warning.call_args[0][0]))
2625+
2626+
def test_server_live_migrate_host_pre_2_30(self):
2627+
# Tests that the --host option is not supported for --live-migration
2628+
# before microversion 2.30 (the test defaults to 2.1).
2629+
arglist = [
2630+
'--live-migration', '--host', 'fakehost', self.server.id,
2631+
]
2632+
verifylist = [
2633+
('live', None),
2634+
('live_migration', True),
2635+
('host', 'fakehost'),
2636+
('block_migration', False),
2637+
('disk_overcommit', False),
2638+
('wait', False),
2639+
]
2640+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2641+
2642+
ex = self.assertRaises(exceptions.CommandError, self.cmd.take_action,
2643+
parsed_args)
2644+
2645+
# Make sure it's the error we expect.
2646+
self.assertIn('--os-compute-api-version 2.30 or greater is required '
2647+
'when using --host', six.text_type(ex))
2648+
2649+
self.servers_mock.get.assert_called_with(self.server.id)
2650+
self.assertNotCalled(self.servers_mock.live_migrate)
2651+
self.assertNotCalled(self.servers_mock.migrate)
2652+
2653+
def test_server_live_migrate_no_host(self):
2654+
# Tests the --live-migration option without --host or --live.
2655+
arglist = [
2656+
'--live-migration', self.server.id,
2657+
]
2658+
verifylist = [
2659+
('live', None),
2660+
('live_migration', True),
2661+
('host', None),
2662+
('block_migration', False),
2663+
('disk_overcommit', False),
2664+
('wait', False),
2665+
]
2666+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2667+
2668+
with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
2669+
result = self.cmd.take_action(parsed_args)
2670+
2671+
self.servers_mock.get.assert_called_with(self.server.id)
2672+
self.server.live_migrate.assert_called_with(block_migration=False,
2673+
disk_over_commit=False,
2674+
host=None)
2675+
self.assertNotCalled(self.servers_mock.migrate)
2676+
self.assertIsNone(result)
2677+
# Since --live wasn't used a warning shouldn't have been logged.
2678+
mock_warning.assert_not_called()
2679+
2680+
def test_server_live_migrate_with_host(self):
2681+
# Tests the --live-migration option with --host but no --live.
2682+
# This requires --os-compute-api-version >= 2.30 so the test uses 2.30.
2683+
arglist = [
2684+
'--live-migration', '--host', 'fakehost', self.server.id,
2685+
]
2686+
verifylist = [
2687+
('live', None),
2688+
('live_migration', True),
2689+
('host', 'fakehost'),
2690+
('block_migration', False),
2691+
('disk_overcommit', False),
2692+
('wait', False),
2693+
]
2694+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2695+
2696+
self.app.client_manager.compute.api_version = \
2697+
api_versions.APIVersion('2.30')
2698+
2699+
result = self.cmd.take_action(parsed_args)
2700+
2701+
self.servers_mock.get.assert_called_with(self.server.id)
2702+
# No disk_overcommit with microversion >= 2.25.
2703+
self.server.live_migrate.assert_called_with(block_migration=False,
2704+
host='fakehost')
2705+
self.assertNotCalled(self.servers_mock.migrate)
2706+
self.assertIsNone(result)
2707+
2708+
def test_server_live_migrate_without_host_override_live(self):
2709+
# Tests the --live-migration option without --host and with --live.
2710+
# The --live-migration option will take precedence and a warning is
2711+
# logged for using --live.
2712+
arglist = [
2713+
'--live', 'fakehost', '--live-migration', self.server.id,
2714+
]
2715+
verifylist = [
2716+
('live', 'fakehost'),
2717+
('live_migration', True),
2718+
('host', None),
2719+
('block_migration', False),
2720+
('disk_overcommit', False),
2721+
('wait', False),
2722+
]
2723+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
2724+
2725+
with mock.patch.object(self.cmd.log, 'warning') as mock_warning:
2726+
result = self.cmd.take_action(parsed_args)
2727+
2728+
self.servers_mock.get.assert_called_with(self.server.id)
2729+
self.server.live_migrate.assert_called_with(block_migration=False,
2730+
disk_over_commit=False,
2731+
host=None)
2732+
self.assertNotCalled(self.servers_mock.migrate)
2733+
self.assertIsNone(result)
2734+
# A warning should have been logged for using --live.
2735+
mock_warning.assert_called_once()
2736+
self.assertIn('The --live option has been deprecated.',
2737+
six.text_type(mock_warning.call_args[0][0]))
2738+
2739+
def test_server_live_migrate_live_and_host_mutex(self):
2740+
# Tests specifying both the --live and --host options which are in a
2741+
# mutex group so argparse should fail.
2742+
arglist = [
2743+
'--live', 'fakehost', '--host', 'fakehost', self.server.id,
2744+
]
2745+
self.assertRaises(utils.ParserException,
2746+
self.check_parser, self.cmd, arglist, verify_args=[])
25912747

25922748
def test_server_block_live_migrate(self):
25932749
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)