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:
parent
e281368c96
commit
0e7c99c8ea
@ -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.
|
||||||
|
@ -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
|
||||||
|
99
novaclient/tests/functional/v2/test_migrations.py
Normal file
99
novaclient/tests/functional/v2/test_migrations.py
Normal 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)
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user