Add --migration-type and --source-compute to migration-list

The GET /os-migrations API take a migration_type and source_compute
filter parameter on the request since the v2.0 API. This adds support
for specifying those parameters in the "nova migration-list" CLI
and related MigrationManager.list() python API binding methods.

A functional test is added which will cover the new options on all
three of the decorated do_migration_list shell methods with lower
bounds on 2.1, 2.59 and 2.66. Since the only type of migration we
can safely generate in a single-node CI job is a resize the test
does a resize. As such, _pick_alternate_flavor is moved into the
base test class for re-use.

Implements blueprint more-migration-list-filters

Change-Id: I4be9a06df3e0d40d3990d067ce112247a81b45b4
This commit is contained in:
Matt Riedemann 2019-08-07 09:11:14 -04:00
parent e281368c96
commit 0e7c99c8ea
7 changed files with 227 additions and 34 deletions

View File

@ -2556,13 +2556,26 @@ nova migration-list
.. code-block:: console .. code-block:: console
usage: nova migration-list [--instance-uuid <instance_uuid>] [--host <host>] usage: nova migration-list [--instance-uuid <instance_uuid>]
[--status <status>] [--marker <marker>] [--host <host>]
[--limit <limit>] [--changes-since <changes_since>] [--status <status>]
[--migration-type <migration_type>]
[--source-compute <source_compute>]
[--marker <marker>]
[--limit <limit>]
[--changes-since <changes_since>]
[--changes-before <changes_before>] [--changes-before <changes_before>]
Print a list of migrations. Print a list of migrations.
**Examples**
To see the list of evacuation operations *from* a compute service host:
.. code-block:: console
nova migration-list --migration-type evacuation --source-compute host.foo.bar
**Optional arguments:** **Optional arguments:**
``--instance-uuid <instance_uuid>`` ``--instance-uuid <instance_uuid>``
@ -2574,6 +2587,17 @@ Print a list of migrations.
``--status <status>`` ``--status <status>``
Fetch migrations for the given status. Fetch migrations for the given status.
``--migration-type <migration_type>``
Filter migrations by type. Valid values are:
* evacuation
* live-migration
* migration
* resize
``--source-compute <source_compute>``
Filter migrations by source compute host name.
``--marker <marker>`` ``--marker <marker>``
The last migration of the previous page; displays list of migrations after The last migration of the previous page; displays list of migrations after
"marker". Note that the marker is the migration UUID. "marker". Note that the marker is the migration UUID.

View File

@ -522,6 +522,27 @@ class ClientTestBase(testtools.TestCase):
return {limit.name: limit.value return {limit.name: limit.value
for limit in self.client.limits.get(reserved=True).absolute} for limit in self.client.limits.get(reserved=True).absolute}
def _pick_alternate_flavor(self):
"""Given the flavor picked in the base class setup, this finds the
opposite flavor to use for a resize test. For example, if m1.nano is
the flavor, then use m1.micro, but those are only available if Tempest
is configured. If m1.tiny, then use m1.small.
"""
flavor_name = self.flavor.name
if flavor_name == 'm1.nano':
# This is an upsize test.
return 'm1.micro'
if flavor_name == 'm1.micro':
# This is a downsize test.
return 'm1.nano'
if flavor_name == 'm1.tiny':
# This is an upsize test.
return 'm1.small'
if flavor_name == 'm1.small':
# This is a downsize test.
return 'm1.tiny'
self.fail('Unable to find alternate for flavor: %s' % flavor_name)
class TenantTestBase(ClientTestBase): class TenantTestBase(ClientTestBase):
"""Base test class for additional tenant and user creation which """Base test class for additional tenant and user creation which

View File

@ -0,0 +1,99 @@
# 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 oslo_utils import uuidutils
from novaclient.tests.functional import base
class TestMigrationList(base.ClientTestBase):
"""Tests the "nova migration-list" command."""
def _filter_migrations(
self, version, migration_type, source_compute):
"""
Filters migrations by --migration-type and --source-compute.
:param version: The --os-compute-api-version to use.
:param migration_type: The type of migrations to filter.
:param source_compute: The source compute service hostname to filter.
:return: output of the nova migration-list command with filters applied
"""
return self.nova('migration-list',
flags='--os-compute-api-version %s' % version,
params='--migration-type %s --source-compute %s' % (
migration_type, source_compute))
def test_migration_list(self):
"""Tests creating a server, resizing it and then listing and filtering
migrations using various microversion milestones.
"""
server_id = self._create_server(flavor=self.flavor.id).id
# Find the source compute by getting OS-EXT-SRV-ATTR:host from the
# nova show output.
server = self.nova('show', params='%s' % server_id)
source_compute = self._get_value_from_the_table(
server, 'OS-EXT-SRV-ATTR:host')
# now resize up
alternate_flavor = self._pick_alternate_flavor()
self.nova('resize',
params='%s %s --poll' % (server_id, alternate_flavor))
# now confirm the resize
self.nova('resize-confirm', params='%s' % server_id)
# wait for the server to be active and then check the migration list
self._wait_for_state_change(server_id, 'active')
# First, list migrations with v2.1 and our server id should be in the
# output. There should only be the one migration.
migrations = self.nova('migration-list',
flags='--os-compute-api-version 2.1')
instance_uuid = self._get_column_value_from_single_row_table(
migrations, 'Instance UUID')
self.assertEqual(server_id, instance_uuid)
# A successfully confirmed resize should have the migration status
# of "confirmed".
migration_status = self._get_column_value_from_single_row_table(
migrations, 'Status')
self.assertEqual('confirmed', migration_status)
# Now listing migrations with 2.23 should give us the Type column which
# should have a value of "resize".
migrations = self.nova('migration-list',
flags='--os-compute-api-version 2.23')
migration_type = self._get_column_value_from_single_row_table(
migrations, 'Type')
self.assertEqual('resize', migration_type)
# Filter migrations with v2.1.
migrations = self._filter_migrations('2.1', 'resize', source_compute)
# Make sure we got something back.
src_compute = self._get_column_value_from_single_row_table(
migrations, 'Source Compute')
self.assertEqual(source_compute, src_compute)
# Filter migrations with v2.59 and make sure there is a migration UUID
# value in the output.
migrations = self._filter_migrations('2.59', 'resize', source_compute)
# _get_column_value_from_single_row_table will raise ValueError if a
# value is not found for the given column. We don't actually care what
# the migration UUID value is just that the filter works and the UUID
# is shown.
self._get_column_value_from_single_row_table(migrations, 'UUID')
# Filter migrations with v2.66, same as 2.59.
migrations = self._filter_migrations('2.66', 'resize', source_compute)
self._get_column_value_from_single_row_table(migrations, 'UUID')
# Now do a negative test to show that filtering on a migration type
# that we don't have a migration for will not return anything.
migrations = self._filter_migrations(
'2.1', 'evacuation', source_compute)
self.assertNotIn(server_id, migrations)
# Similarly, make sure we don't get anything back when filtering on
# a --source-compute that doesn't exist.
migrations = self._filter_migrations(
'2.66', 'resize', uuidutils.generate_uuid())
self.assertNotIn(server_id, migrations)

View File

@ -20,27 +20,6 @@ class TestServersResize(base.ClientTestBase):
COMPUTE_API_VERSION = '2.1' COMPUTE_API_VERSION = '2.1'
def _pick_alternate_flavor(self):
"""Given the flavor picked in the base class setup, this finds the
opposite flavor to use for a resize test. For example, if m1.nano is
the flavor, then use m1.micro, but those are only available if Tempest
is configured. If m1.tiny, then use m1.small.
"""
flavor_name = self.flavor.name
if flavor_name == 'm1.nano':
# This is an upsize test.
return 'm1.micro'
if flavor_name == 'm1.micro':
# This is a downsize test.
return 'm1.nano'
if flavor_name == 'm1.tiny':
# This is an upsize test.
return 'm1.small'
if flavor_name == 'm1.small':
# This is a downsize test.
return 'm1.tiny'
self.fail('Unable to find alternate for flavor: %s' % flavor_name)
def _compare_quota_usage(self, old_usage, new_usage, expect_diff=True): def _compare_quota_usage(self, old_usage, new_usage, expect_diff=True):
"""Compares the quota usage in the provided AbsoluteLimits.""" """Compares the quota usage in the provided AbsoluteLimits."""
# For a resize, instance usage shouldn't change. # For a resize, instance usage shouldn't change.

View File

@ -28,7 +28,8 @@ class MigrationManager(base.ManagerWithFind):
def _list_base(self, host=None, status=None, instance_uuid=None, def _list_base(self, host=None, status=None, instance_uuid=None,
marker=None, limit=None, changes_since=None, marker=None, limit=None, changes_since=None,
changes_before=None): changes_before=None, migration_type=None,
source_compute=None):
opts = {} opts = {}
if host: if host:
opts['host'] = host opts['host'] = host
@ -44,23 +45,34 @@ class MigrationManager(base.ManagerWithFind):
opts['changes-since'] = changes_since opts['changes-since'] = changes_since
if changes_before: if changes_before:
opts['changes-before'] = changes_before opts['changes-before'] = changes_before
if migration_type:
opts['migration_type'] = migration_type
if source_compute:
opts['source_compute'] = source_compute
return self._list("/os-migrations", "migrations", filters=opts) return self._list("/os-migrations", "migrations", filters=opts)
@api_versions.wraps("2.0", "2.58") @api_versions.wraps("2.0", "2.58")
def list(self, host=None, status=None, instance_uuid=None): def list(self, host=None, status=None, instance_uuid=None,
migration_type=None, source_compute=None):
""" """
Get a list of migrations. Get a list of migrations.
:param host: filter migrations by host name (optional). :param host: filter migrations by host name (optional).
:param status: filter migrations by status (optional). :param status: filter migrations by status (optional).
:param instance_uuid: filter migrations by instance uuid (optional). :param instance_uuid: filter migrations by instance uuid (optional).
:param migration_type: Filter migrations by type. Valid values are:
evacuation, live-migration, migration, resize
:param source_compute: Filter migrations by source compute host name.
""" """
return self._list_base(host=host, status=status, return self._list_base(host=host, status=status,
instance_uuid=instance_uuid) instance_uuid=instance_uuid,
migration_type=migration_type,
source_compute=source_compute)
@api_versions.wraps("2.59", "2.65") @api_versions.wraps("2.59", "2.65")
def list(self, host=None, status=None, instance_uuid=None, def list(self, host=None, status=None, instance_uuid=None,
marker=None, limit=None, changes_since=None): marker=None, limit=None, changes_since=None,
migration_type=None, source_compute=None):
""" """
Get a list of migrations. Get a list of migrations.
:param host: filter migrations by host name (optional). :param host: filter migrations by host name (optional).
@ -76,16 +88,21 @@ class MigrationManager(base.ManagerWithFind):
:param changes_since: only return migrations changed later or equal :param changes_since: only return migrations changed later or equal
to a certain point of time. The provided time should be an ISO 8061 to a certain point of time. The provided time should be an ISO 8061
formatted time. e.g. 2016-03-04T06:27:59Z . (optional). formatted time. e.g. 2016-03-04T06:27:59Z . (optional).
:param migration_type: Filter migrations by type. Valid values are:
evacuation, live-migration, migration, resize
:param source_compute: Filter migrations by source compute host name.
""" """
return self._list_base(host=host, status=status, return self._list_base(host=host, status=status,
instance_uuid=instance_uuid, instance_uuid=instance_uuid,
marker=marker, limit=limit, marker=marker, limit=limit,
changes_since=changes_since) changes_since=changes_since,
migration_type=migration_type,
source_compute=source_compute)
@api_versions.wraps("2.66") @api_versions.wraps("2.66")
def list(self, host=None, status=None, instance_uuid=None, def list(self, host=None, status=None, instance_uuid=None,
marker=None, limit=None, changes_since=None, marker=None, limit=None, changes_since=None,
changes_before=None): changes_before=None, migration_type=None, source_compute=None):
""" """
Get a list of migrations. Get a list of migrations.
:param host: filter migrations by host name (optional). :param host: filter migrations by host name (optional).
@ -104,9 +121,14 @@ class MigrationManager(base.ManagerWithFind):
:param changes_before: Only return migrations changed earlier or :param changes_before: Only return migrations changed earlier or
equal to a certain point of time. The provided time should be an ISO equal to a certain point of time. The provided time should be an ISO
8061 formatted time. e.g. 2016-03-05T06:27:59Z . (optional). 8061 formatted time. e.g. 2016-03-05T06:27:59Z . (optional).
:param migration_type: Filter migrations by type. Valid values are:
evacuation, live-migration, migration, resize
:param source_compute: Filter migrations by source compute host name.
""" """
return self._list_base(host=host, status=status, return self._list_base(host=host, status=status,
instance_uuid=instance_uuid, instance_uuid=instance_uuid,
marker=marker, limit=limit, marker=marker, limit=limit,
changes_since=changes_since, changes_since=changes_since,
changes_before=changes_before) changes_before=changes_before,
migration_type=migration_type,
source_compute=source_compute)

View File

@ -5422,10 +5422,23 @@ def _print_migrations(cs, migrations):
dest='status', dest='status',
metavar='<status>', metavar='<status>',
help=_('Fetch migrations for the given status.')) help=_('Fetch migrations for the given status.'))
@utils.arg(
'--migration-type',
dest='migration_type',
metavar='<migration_type>',
help=_('Filter migrations by type. Valid values are: evacuation, '
'live-migration, migration, resize'))
@utils.arg(
'--source-compute',
dest='source_compute',
metavar='<source_compute>',
help=_('Filter migrations by source compute host name.'))
def do_migration_list(cs, args): def do_migration_list(cs, args):
"""Print a list of migrations.""" """Print a list of migrations."""
migrations = cs.migrations.list(args.host, args.status, migrations = cs.migrations.list(args.host, args.status,
instance_uuid=args.instance_uuid) instance_uuid=args.instance_uuid,
migration_type=args.migration_type,
source_compute=args.source_compute)
_print_migrations(cs, migrations) _print_migrations(cs, migrations)
@ -5445,6 +5458,17 @@ def do_migration_list(cs, args):
dest='status', dest='status',
metavar='<status>', metavar='<status>',
help=_('Fetch migrations for the given status.')) help=_('Fetch migrations for the given status.'))
@utils.arg(
'--migration-type',
dest='migration_type',
metavar='<migration_type>',
help=_('Filter migrations by type. Valid values are: evacuation, '
'live-migration, migration, resize'))
@utils.arg(
'--source-compute',
dest='source_compute',
metavar='<source_compute>',
help=_('Filter migrations by source compute host name.'))
@utils.arg( @utils.arg(
'--marker', '--marker',
dest='marker', dest='marker',
@ -5483,7 +5507,9 @@ def do_migration_list(cs, args):
migrations = cs.migrations.list(args.host, args.status, migrations = cs.migrations.list(args.host, args.status,
instance_uuid=args.instance_uuid, instance_uuid=args.instance_uuid,
marker=args.marker, limit=args.limit, marker=args.marker, limit=args.limit,
changes_since=args.changes_since) changes_since=args.changes_since,
migration_type=args.migration_type,
source_compute=args.source_compute)
# TODO(yikun): Output a "Marker" column if there is a next link? # TODO(yikun): Output a "Marker" column if there is a next link?
_print_migrations(cs, migrations) _print_migrations(cs, migrations)
@ -5504,6 +5530,17 @@ def do_migration_list(cs, args):
dest='status', dest='status',
metavar='<status>', metavar='<status>',
help=_('Fetch migrations for the given status.')) help=_('Fetch migrations for the given status.'))
@utils.arg(
'--migration-type',
dest='migration_type',
metavar='<migration_type>',
help=_('Filter migrations by type. Valid values are: evacuation, '
'live-migration, migration, resize'))
@utils.arg(
'--source-compute',
dest='source_compute',
metavar='<source_compute>',
help=_('Filter migrations by source compute host name.'))
@utils.arg( @utils.arg(
'--marker', '--marker',
dest='marker', dest='marker',
@ -5559,7 +5596,9 @@ def do_migration_list(cs, args):
instance_uuid=args.instance_uuid, instance_uuid=args.instance_uuid,
marker=args.marker, limit=args.limit, marker=args.marker, limit=args.limit,
changes_since=args.changes_since, changes_since=args.changes_since,
changes_before=args.changes_before) changes_before=args.changes_before,
migration_type=args.migration_type,
source_compute=args.source_compute)
_print_migrations(cs, migrations) _print_migrations(cs, migrations)

View File

@ -0,0 +1,9 @@
---
features:
- |
The ``--migration-type`` and ``--source-compute`` options are added to the
``nova migration-list`` CLI and related kwargs are added to the
``novaclient.v2.migrations.MigrationManager.list`` method. These can be
used to filter the list of migrations by type (evacuation, live-migration,
migration, resize) and the name of the source compute service host involved
in the migration.