Add two server-migration commands and bump migration-list command

1. Add two new commands
Add nova client server-migration-list and server-migration-show

2. Bump and old command
Add migration_type field for migration-list command

Partially implements blueprint live-migration-progress-report

Depends-On: Ia92ecbe3c99082e3a34adf4fd29041b1a95ef21e

Change-Id: I071198fa9ba0699383bdebf4fab54714a435e6c3
This commit is contained in:
ShaoHe Feng 2016-02-29 23:07:33 +01:00
parent 66b4085c9e
commit e0c7d2c673
9 changed files with 279 additions and 22 deletions

View File

@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
# when client supported the max version, and bumped sequentially, otherwise # when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some # the client may break due to server side new version may include some
# backward incompatible change. # backward incompatible change.
API_MAX_VERSION = api_versions.APIVersion("2.22") API_MAX_VERSION = api_versions.APIVersion("2.23")

View File

@ -25,3 +25,54 @@ class Fixture(base.Fixture):
self.requests.register_uri('POST', url, self.requests.register_uri('POST', url,
status_code=202, status_code=202,
headers=self.json_headers) headers=self.json_headers)
get_migrations = {'migrations': [
{
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe",
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"memory_total_bytes": 123456,
"memory_processed_bytes": 12345,
"memory_remaining_bytes": 120000,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 230000,
"updated_at": "2016-01-29T13:42:02.000000"
}]}
url = self.url('1234', 'migrations')
self.requests.register_uri('GET', url,
status_code=200,
json=get_migrations,
headers=self.json_headers)
get_migration = {'migration': {
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe",
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"memory_total_bytes": 123456,
"memory_processed_bytes": 12345,
"memory_remaining_bytes": 120000,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 230000,
"updated_at": "2016-01-29T13:42:02.000000"
}}
url = self.url('1234', 'migrations', '1')
self.requests.register_uri('GET', url,
status_code=200,
json=get_migration,
headers=self.json_headers)

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from novaclient import api_versions
from novaclient import extension from novaclient import extension
from novaclient.tests.unit import utils from novaclient.tests.unit import utils
from novaclient.tests.unit.v2 import fakes from novaclient.tests.unit.v2 import fakes
@ -30,6 +31,17 @@ class MigrationsTest(utils.TestCase):
cs.assert_called('GET', '/os-migrations') cs.assert_called('GET', '/os-migrations')
for m in ml: for m in ml:
self.assertIsInstance(m, migrations.Migration) self.assertIsInstance(m, migrations.Migration)
self.assertRaises(AttributeError, getattr, m, "migration_type")
def test_list_migrations_v223(self):
cs = fakes.FakeClient(extensions=extensions,
api_version=api_versions.APIVersion("2.23"))
ml = cs.migrations.list()
self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST)
cs.assert_called('GET', '/os-migrations')
for m in ml:
self.assertIsInstance(m, migrations.Migration)
self.assertEqual(m.migration_type, 'live-migration')
def test_list_migrations_with_filters(self): def test_list_migrations_with_filters(self):
ml = cs.migrations.list('host1', 'finished', 'child1') ml = cs.migrations.list('host1', 'finished', 'child1')

View File

@ -23,8 +23,10 @@ import six
from six.moves.urllib import parse from six.moves.urllib import parse
import novaclient import novaclient
from novaclient import api_versions
from novaclient import client as base_client from novaclient import client as base_client
from novaclient import exceptions from novaclient import exceptions
from novaclient.i18n import _
from novaclient.tests.unit import fakes from novaclient.tests.unit import fakes
from novaclient.tests.unit import utils from novaclient.tests.unit import utils
from novaclient.v2 import client from novaclient.v2 import client
@ -58,7 +60,8 @@ class FakeClient(fakes.FakeClient, client.Client):
'project_id', 'auth_url', 'project_id', 'auth_url',
extensions=kwargs.get('extensions'), extensions=kwargs.get('extensions'),
direct_use=False) direct_use=False)
self.api_version = api_version self.api_version = api_version or api_versions.APIVersion("2.1")
kwargs["api_version"] = self.api_version
self.client = FakeHTTPClient(**kwargs) self.client = FakeHTTPClient(**kwargs)
@ -91,6 +94,7 @@ class FakeHTTPClient(base_client.HTTPClient):
self.http_log_debug = 'http_log_debug' self.http_log_debug = 'http_log_debug'
self.last_request_id = None self.last_request_id = None
self.management_url = self.get_endpoint() self.management_url = self.get_endpoint()
self.api_version = kwargs.get("api_version")
def _cs_request(self, url, method, **kwargs): def _cs_request(self, url, method, **kwargs):
# Check that certain things are called correctly # Check that certain things are called correctly
@ -2367,21 +2371,26 @@ class FakeHTTPClient(base_client.HTTPClient):
return self.get_os_cells_capacities() return self.get_os_cells_capacities()
def get_os_migrations(self, **kw): def get_os_migrations(self, **kw):
migrations = {'migrations': [ migration = {
{ "created_at": "2012-10-29T13:42:02.000000",
"created_at": "2012-10-29T13:42:02.000000", "dest_compute": "compute2",
"dest_compute": "compute2", "dest_host": "1.2.3.4",
"dest_host": "1.2.3.4", "dest_node": "node2",
"dest_node": "node2", "id": 1234,
"id": 1234, "instance_uuid": "instance_id_123",
"instance_uuid": "instance_id_123", "new_instance_type_id": 2,
"new_instance_type_id": 2, "old_instance_type_id": 1,
"old_instance_type_id": 1, "source_compute": "compute1",
"source_compute": "compute1", "source_node": "node1",
"source_node": "node1", "status": "Done",
"status": "Done", "updated_at": "2012-10-29T13:42:02.000000"
"updated_at": "2012-10-29T13:42:02.000000" }
}]}
if self.api_version >= api_versions.APIVersion("2.23"):
migration.update({"migration_type": "live-migration"})
migrations = {'migrations': [migration]}
return (200, FAKE_RESPONSE_HEADERS, migrations) return (200, FAKE_RESPONSE_HEADERS, migrations)
def post_os_server_external_events(self, **kw): def post_os_server_external_events(self, **kw):
@ -2435,6 +2444,57 @@ class FakeHTTPClient(base_client.HTTPClient):
def post_servers_1234_migrations_1_action(self, body): def post_servers_1234_migrations_1_action(self, body):
return (202, {}, None) return (202, {}, None)
def get_servers_1234_migrations_1(self, **kw):
# TODO(Shaohe Feng) this condition check can be a decorator
if self.api_version < api_versions.APIVersion("2.23"):
raise exceptions.UnsupportedVersion(_("Unsupport version %s")
% self.api_version)
migration = {"migration": {
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe",
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"memory_total_bytes": 123456,
"memory_processed_bytes": 12345,
"memory_remaining_bytes": 120000,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 230000,
"updated_at": "2016-01-29T13:42:02.000000"
}}
return (200, FAKE_RESPONSE_HEADERS, migration)
def get_servers_1234_migrations(self, **kw):
# TODO(Shaohe Feng) this condition check can be a decorator
if self.api_version < api_versions.APIVersion("2.23"):
raise exceptions.UnsupportedVersion(_("Unsupport version %s")
% self.api_version)
migrations = {'migrations': [
{
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe",
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"memory_total_bytes": 123456,
"memory_processed_bytes": 12345,
"memory_remaining_bytes": 120000,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 230000,
"updated_at": "2016-01-29T13:42:02.000000"
}]}
return (200, FAKE_RESPONSE_HEADERS, migrations)
class FakeSessionClient(fakes.FakeClient, client.Client): class FakeSessionClient(fakes.FakeClient, client.Client):
@ -2443,6 +2503,7 @@ class FakeSessionClient(fakes.FakeClient, client.Client):
'project_id', 'auth_url', 'project_id', 'auth_url',
extensions=kwargs.get('extensions'), extensions=kwargs.get('extensions'),
api_version=api_version, direct_use=False) api_version=api_version, direct_use=False)
kwargs['api_version'] = api_version
self.client = FakeSessionMockClient(**kwargs) self.client = FakeSessionMockClient(**kwargs)
@ -2461,7 +2522,7 @@ class FakeSessionMockClient(base_client.SessionClient, FakeHTTPClient):
self.interface = None self.interface = None
self.region_name = None self.region_name = None
self.version = None self.version = None
self.api_version = kwargs.get('api_version')
self.auth.get_auth_ref.return_value.project_id = 'tenant_id' self.auth.get_auth_ref.return_value.project_id = 'tenant_id'
def request(self, url, method, **kwargs): def request(self, url, method, **kwargs):

View File

@ -14,9 +14,12 @@
# under the License. # under the License.
from novaclient import api_versions from novaclient import api_versions
from novaclient import base
from novaclient.tests.unit.fixture_data import client from novaclient.tests.unit.fixture_data import client
from novaclient.tests.unit.fixture_data import server_migrations as data from novaclient.tests.unit.fixture_data import server_migrations as data
from novaclient.tests.unit import utils from novaclient.tests.unit import utils
from novaclient.tests.unit.v2 import fakes
from novaclient.v2 import server_migrations
class ServerMigrationsTest(utils.FixturedTestCase): class ServerMigrationsTest(utils.FixturedTestCase):
@ -31,3 +34,49 @@ class ServerMigrationsTest(utils.FixturedTestCase):
body = {'force_complete': None} body = {'force_complete': None}
self.cs.server_migrations.live_migrate_force_complete(1234, 1) self.cs.server_migrations.live_migrate_force_complete(1234, 1)
self.assert_called('POST', '/servers/1234/migrations/1/action', body) self.assert_called('POST', '/servers/1234/migrations/1/action', body)
class ServerMigrationsTestV223(ServerMigrationsTest):
migration = {
"created_at": "2016-01-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1,
"server_uuid": "4cfba335-03d8-49b2-8c52-e69043d1e8fe",
"source_compute": "compute1",
"source_node": "node1",
"status": "running",
"memory_total_bytes": 123456,
"memory_processed_bytes": 12345,
"memory_remaining_bytes": 120000,
"disk_total_bytes": 234567,
"disk_processed_bytes": 23456,
"disk_remaining_bytes": 230000,
"updated_at": "2016-01-29T13:42:02.000000"
}
def setUp(self):
super(ServerMigrationsTestV223, self).setUp()
self.cs.api_version = api_versions.APIVersion("2.23")
def test_list_migrations(self):
ml = self.cs.server_migrations.list(1234)
self.assertIsInstance(ml, base.ListWithMeta)
self.assert_request_id(ml, fakes.FAKE_REQUEST_ID_LIST)
for k in self.migration:
self.assertEqual(self.migration[k], getattr(ml[0], k))
self.assert_called('GET', '/servers/1234/migrations')
def test_get_migration(self):
migration = self.cs.server_migrations.get(1234, 1)
self.assertIsInstance(migration, server_migrations.ServerMigration)
for k in migration._info:
self.assertEqual(self.migration[k], migration._info[k])
self.assert_request_id(migration, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('GET', '/servers/1234/migrations/1')

View File

@ -1687,6 +1687,16 @@ class ShellTest(utils.TestCase):
self.assert_called('POST', '/servers/1234/migrations/1/action', self.assert_called('POST', '/servers/1234/migrations/1/action',
{'force_complete': None}) {'force_complete': None})
def test_list_migrations(self):
self.run_command('server-migration-list sample-server',
api_version='2.23')
self.assert_called('GET', '/servers/1234/migrations')
def test_get_migration(self):
self.run_command('server-migration-show sample-server 1',
api_version='2.23')
self.assert_called('GET', '/servers/1234/migrations/1')
def test_host_evacuate_live_with_no_target_host(self): def test_host_evacuate_live_with_no_target_host(self):
self.run_command('host-evacuate-live hyper') self.run_command('host-evacuate-live hyper')
self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0) self.assert_called('GET', '/os-hypervisors/hyper/servers', pos=0)
@ -2501,6 +2511,10 @@ class ShellTest(utils.TestCase):
self.run_command('migration-list') self.run_command('migration-list')
self.assert_called('GET', '/os-migrations') self.assert_called('GET', '/os-migrations')
def test_migration_list_v223(self):
self.run_command('migration-list', api_version="2.23")
self.assert_called('GET', '/os-migrations')
def test_migration_list_with_filters(self): def test_migration_list_with_filters(self):
self.run_command('migration-list --host host1 --cell_name child1 ' self.run_command('migration-list --host host1 --cell_name child1 '
'--status finished') '--status finished')

View File

@ -16,6 +16,7 @@ migration interface
from six.moves.urllib import parse from six.moves.urllib import parse
from novaclient import api_versions
from novaclient import base from novaclient import base
from novaclient.i18n import _ from novaclient.i18n import _
from novaclient.openstack.common import cliutils from novaclient.openstack.common import cliutils
@ -71,11 +72,11 @@ class MigrationManager(base.ManagerWithFind):
help=_('Fetch migrations for the given cell_name.')) help=_('Fetch migrations for the given cell_name.'))
def do_migration_list(cs, args): def do_migration_list(cs, args):
"""Print a list of migrations.""" """Print a list of migrations."""
_print_migrations(cs.migrations.list(args.host, args.status, migrations = cs.migrations.list(args.host, args.status, args.cell_name)
args.cell_name)) _print_migrations(cs, migrations)
def _print_migrations(migrations): def _print_migrations(cs, migrations):
fields = ['Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', fields = ['Source Node', 'Dest Node', 'Source Compute', 'Dest Compute',
'Dest Host', 'Status', 'Instance UUID', 'Old Flavor', 'Dest Host', 'Status', 'Instance UUID', 'Old Flavor',
'New Flavor', 'Created At', 'Updated At'] 'New Flavor', 'Created At', 'Updated At']
@ -86,6 +87,14 @@ def _print_migrations(migrations):
def new_flavor(migration): def new_flavor(migration):
return migration.new_instance_type_id return migration.new_instance_type_id
def migration_type(migration):
return migration.migration_type
formatters = {'Old Flavor': old_flavor, 'New Flavor': new_flavor} formatters = {'Old Flavor': old_flavor, 'New Flavor': new_flavor}
if cs.api_version >= api_versions.APIVersion("2.23"):
fields.insert(0, "Id")
fields.append("Type")
formatters.update({"Type": migration_type})
utils.print_list(migrations, fields, formatters) utils.print_list(migrations, fields, formatters)

View File

@ -22,7 +22,7 @@ class ServerMigration(base.Resource):
return "<ServerMigration>" return "<ServerMigration>"
class ServerMigrationsManager(base.Manager): class ServerMigrationsManager(base.ManagerWithFind):
resource_class = ServerMigration resource_class = ServerMigration
@api_versions.wraps("2.22") @api_versions.wraps("2.22")
@ -40,3 +40,28 @@ class ServerMigrationsManager(base.Manager):
base.getid(migration)), base.getid(migration)),
body=body) body=body)
return self.convert_into_with_meta(body, resp) return self.convert_into_with_meta(body, resp)
@api_versions.wraps("2.23")
def get(self, server, migration):
"""
Get a migration of a specified server
:param server: The :class:`Server` (or its ID)
:param migration: Migration id that will be gotten.
:returns: An instance of
novaclient.v2.server_migrations.ServerMigration
"""
return self._get('/servers/%s/migrations/%s' %
(base.getid(server), base.getid(migration)),
'migration')
@api_versions.wraps("2.23")
def list(self, server):
"""
Get a migrations list of a specified server
:param server: The :class:`Server` (or its ID)
:returns: An instance of novaclient.base.ListWithMeta
"""
return self._list(
'/servers/%s/migrations' % base.getid(server), "migrations")

View File

@ -3847,6 +3847,42 @@ def do_live_migration_force_complete(cs, args):
cs.server_migrations.live_migrate_force_complete(server, args.migration) cs.server_migrations.live_migrate_force_complete(server, args.migration)
@api_versions.wraps("2.23")
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_server_migration_list(cs, args):
"""Get the migrations list of specified server."""
server = _find_server(cs, args.server)
migrations = cs.server_migrations.list(server)
fields = ['Id', 'Source Node', 'Dest Node', 'Source Compute',
'Dest Compute', 'Dest Host', 'Status', 'Server UUID',
'Created At', 'Updated At']
format_name = ["Total Memory Bytes", "Processed Memory Bytes",
"Remaining Memory Bytes", "Total Disk Bytes",
"Processed Disk Bytes", "Remaining Disk Bytes"]
format_key = ["memory_total_bytes", "memory_processed_bytes",
"memory_remaining_bytes", "disk_total_bytes",
"disk_processed_bytes", "disk_remaining_bytes"]
formatters = map(lambda field: utils._make_field_formatter(field)[1],
format_key)
formatters = dict(zip(format_name, formatters))
utils.print_list(migrations, fields + format_name, formatters)
@api_versions.wraps("2.23")
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg('migration', metavar='<migration>', help=_('ID of migration.'))
def do_server_migration_show(cs, args):
"""Get the migration of specified server."""
server = _find_server(cs, args.server)
migration = cs.server_migrations.get(server, args.migration)
utils.print_dict(migration._info)
@cliutils.arg( @cliutils.arg(
'--all-tenants', '--all-tenants',
action='store_const', action='store_const',