List migrations through Admin API

The os-migrations extension exposes endpoint to fetch all migrations.
The migrations can filtered by host and status. If cells are
enabled migrations can be listed for all cells or can be filtered for a
particular cell.

The route for fetching migrations for
a region is - v2/{tenant_id}/os-migrations. Filters can be passed as
query parameters -
v2/{tenant_id}/os-migrations?host=host1&status=finished&cell_name=Child

DocImpact

Change-Id: Id70dbece344a722b2dc8c593dd340ef747eb43d3
Implements: blueprint list-resizes-through-admin-api
This commit is contained in:
Mahesh Panchaksharaiah 2013-05-15 17:53:36 +05:30
parent ba70576a63
commit 405ebb9028
26 changed files with 568 additions and 0 deletions

View File

@ -559,6 +559,14 @@
"name": "Volumes",
"namespace": "http://docs.openstack.org/compute/ext/volumes/api/v1.1",
"updated": "2011-03-25T00:00:00+00:00"
},
{
"alias": "os-migrations",
"description": "Provide data on migrations.",
"links": [],
"name": "Migrations",
"namespace": "http://docs.openstack.org/compute/ext/migrations/api/v2.0",
"updated": "2013-05-30T00:00:00+00:00"
}
]
}

View File

@ -228,4 +228,8 @@
<extension alias="os-volumes" updated="2011-03-25T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/volumes/api/v1.1" name="Volumes">
<description>Volumes support.</description>
</extension>
<extension alias="os-migrations" updated="2013-05-30T00:00:00+00:00"
namespace="http://docs.openstack.org/compute/ext/migrations/api/v2.0" name="Migrations">
<description>Provide data on migrations.</description>
</extension>
</extensions>

View File

@ -0,0 +1,32 @@
{
"migrations": [
{
"created_at": "2012-10-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1234,
"instance_uuid": "instance_id_123",
"new_instance_type_id": 2,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"status": "Done",
"updated_at": "2012-10-29T13:42:02.000000"
},
{
"created_at": "2013-10-22T13:42:02.000000",
"dest_compute": "compute20",
"dest_host": "5.6.7.8",
"dest_node": "node20",
"id": 5678,
"instance_uuid": "instance_id_456",
"new_instance_type_id": 6,
"old_instance_type_id": 5,
"source_compute": "compute10",
"source_node": "node10",
"status": "Done",
"updated_at": "2013-10-22T13:42:02.000000"
}
]
}

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<migrations>
<migration dest_host="1.2.3.4" status="Done" old_instance_type_id="1" updated_at="2012-10-29 13:42:02" dest_compute="compute2" created_at="2012-10-29 13:42:02" source_node="node1" instance_uuid="instance_id_123" dest_node="node2" id="1234" new_instance_type_id="2" source_compute="compute1"/>
<migration dest_host="5.6.7.8" status="Done" old_instance_type_id="5" updated_at="2013-10-22 13:42:02" dest_compute="compute20" created_at="2013-10-22 13:42:02" source_node="node10" instance_uuid="instance_id_456" dest_node="node20" id="5678" new_instance_type_id="6" source_compute="compute10"/>
</migrations>

View File

@ -129,6 +129,7 @@
"compute_extension:availability_zone:list": "",
"compute_extension:availability_zone:detail": "rule:admin_api",
"compute_extension:used_limits_for_admin": "rule:admin_api",
"compute_extension:migrations:index": "rule:admin_api",
"volume:create": "",

View File

@ -0,0 +1,77 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
from nova import compute
XMLNS = "http://docs.openstack.org/compute/ext/migrations/api/v2.0"
ALIAS = "os-migrations"
def authorize(context, action_name):
action = 'migrations:%s' % action_name
extensions.extension_authorizer('compute', action)(context)
class MigrationsTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('migrations')
elem = xmlutil.SubTemplateElement(root, 'migration',
selector='migrations')
elem.set('id')
elem.set('source_node')
elem.set('dest_node')
elem.set('source_compute')
elem.set('dest_compute')
elem.set('dest_host')
elem.set('status')
elem.set('instance_uuid')
elem.set('old_instance_type_id')
elem.set('new_instance_type_id')
elem.set('created_at')
elem.set('updated_at')
return xmlutil.MasterTemplate(root, 1)
class MigrationsController(object):
"""Controller for accessing migrations in OpenStack API."""
def __init__(self):
self.compute_api = compute.API()
@wsgi.serializers(xml=MigrationsTemplate)
def index(self, req):
"""Return all migrations in progress."""
context = req.environ['nova.context']
authorize(context, "index")
migrations = self.compute_api.get_migrations(context, req.GET)
return {'migrations': migrations}
class Migrations(extensions.ExtensionDescriptor):
"""Provide data on migrations."""
name = "Migrations"
alias = ALIAS
namespace = XMLNS
updated = "2013-05-30T00:00:00+00:00"
def get_resources(self):
resources = []
resource = extensions.ResourceExtension('os-migrations',
MigrationsController())
resources.append(resource)
return resources

View File

@ -46,6 +46,7 @@ cell_manager_opts = [
CONF = cfg.CONF
CONF.import_opt('name', 'nova.cells.opts', group='cells')
CONF.register_opts(cell_manager_opts, group='cells')
@ -401,3 +402,18 @@ class CellsManager(manager.Manager):
self.msg_runner.bdm_destroy_at_top(ctxt, instance_uuid,
device_name=device_name,
volume_id=volume_id)
def get_migrations(self, ctxt, filters):
"""Fetch migrations applying the filters."""
target_cell = None
if "cell_name" in filters:
_path_cell_sep = cells_utils._PATH_CELL_SEP
target_cell = '%s%s%s' % (CONF.cells.name, _path_cell_sep,
filters['cell_name'])
responses = self.msg_runner.get_migrations(ctxt, target_cell,
False, filters)
migrations = []
for response in responses:
migrations += response.value_or_raise()
return migrations

View File

@ -796,6 +796,10 @@ class _TargetedMessageMethods(_BaseMessageMethods):
return self.compute_rpcapi.validate_console_port(message.ctxt,
instance, console_port, console_type)
def get_migrations(self, message, filters):
context = message.ctxt
return self.compute_api.get_migrations(context, filters)
class _BroadcastMessageMethods(_BaseMessageMethods):
"""These are the methods that can be called as a part of a broadcast
@ -1027,6 +1031,10 @@ class _BroadcastMessageMethods(_BaseMessageMethods):
self.db.block_device_mapping_destroy_by_instance_and_volume(
message.ctxt, instance_uuid, volume_id)
def get_migrations(self, message, filters):
context = message.ctxt
return self.compute_api.get_migrations(context, filters)
_CELL_MESSAGE_TYPE_TO_MESSAGE_CLS = {'targeted': _TargetedMessage,
'broadcast': _BroadcastMessage,
@ -1126,6 +1134,19 @@ class MessageRunner(object):
response_kwargs, direction, target_cell,
response_uuid, **kwargs)
def _get_migrations_for_cell(self, ctxt, cell_name, filters):
method_kwargs = dict(filters=filters)
message = _TargetedMessage(self, ctxt, 'get_migrations',
method_kwargs, 'down', cell_name,
need_response=True)
response = message.process()
if response.failure and isinstance(response.value[1],
exception.CellRoutingInconsistency):
return []
return [response]
def message_from_json(self, json_message):
"""Turns a message in JSON format into an appropriate Message
instance. This is called when cells receive a message from
@ -1438,6 +1459,20 @@ class MessageRunner(object):
'up', run_locally=False)
message.process()
def get_migrations(self, ctxt, cell_name, run_locally, filters):
"""Fetch all migrations applying the filters for a given cell or all
cells.
"""
method_kwargs = dict(filters=filters)
if cell_name:
return self._get_migrations_for_cell(ctxt, cell_name, filters)
message = _BroadcastMessage(self, ctxt, 'get_migrations',
method_kwargs, 'down',
run_locally=run_locally,
need_response=True)
return message.process()
@staticmethod
def get_message_types():
return _CELL_MESSAGE_TYPE_TO_MESSAGE_CLS.keys()

View File

@ -66,6 +66,7 @@ class CellsAPI(rpc_proxy.RpcProxy):
1.8 - Adds build_instances(), deprecates schedule_run_instance()
1.9 - Adds get_capacities()
1.10 - Adds bdm_update_or_create_at_top(), and bdm_destroy_at_top()
1.11 - Adds get_migrations()
'''
BASE_RPC_API_VERSION = '1.0'
@ -351,3 +352,8 @@ class CellsAPI(rpc_proxy.RpcProxy):
version='1.10')
except Exception:
LOG.exception(_("Failed to notify cells of BDM destroy."))
def get_migrations(self, ctxt, filters):
"""Get all migrations applying the filters."""
return self.call(ctxt, self.make_msg('get_migrations',
filters=filters), version='1.11')

View File

@ -2616,6 +2616,10 @@ class API(base.Base):
on_shared_storage=on_shared_storage,
host=host)
def get_migrations(self, context, filters):
"""Get all migrations for the given filters."""
return self.db.migration_get_all_by_filters(context, filters)
class HostAPI(base.Base):
"""Sub-set of the Compute Manager API for managing host operations."""

View File

@ -579,6 +579,9 @@ class ComputeCellsAPI(compute_api.API):
self._cast_to_cells(context, instance, 'live_migrate',
block_migration, disk_over_commit, host_name)
def get_migrations(self, context, filters):
return self.cells_rpcapi.get_migrations(context, filters)
class HostAPI(compute_api.HostAPI):
"""HostAPI() class for cells.

View File

@ -418,6 +418,11 @@ def migration_get_in_progress_by_host_and_node(context, host, node):
return IMPL.migration_get_in_progress_by_host_and_node(context, host, node)
def migration_get_all_by_filters(context, filters):
"""Finds all migrations in progress."""
return IMPL.migration_get_all_by_filters(context, filters)
####################

View File

@ -3693,6 +3693,18 @@ def migration_get_in_progress_by_host_and_node(context, host, node,
all()
@require_admin_context
def migration_get_all_by_filters(context, filters):
query = model_query(context, models.Migration)
if "status" in filters:
query = query.filter(models.Migration.status == filters["status"])
if "host" in filters:
host = filters["host"]
query = query.filter(or_(models.Migration.source_compute == host,
models.Migration.dest_compute == host))
return query.all()
##################

View File

@ -0,0 +1,122 @@
# vim: tabstop=5 shiftwidth=4 softtabstop=4
# All Rights Reserved.
#
# 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.
import datetime
from lxml import etree
from nova.api.openstack.compute.contrib import migrations
from nova import context
from nova import exception
from nova import test
from nova.test import MoxStubout
fake_migrations = [
{
'id': 1234,
'source_node': 'node1',
'dest_node': 'node2',
'source_compute': 'compute1',
'dest_compute': 'compute2',
'dest_host': '1.2.3.4',
'status': 'Done',
'instance_uuid': 'instance_id_123',
'old_instance_type_id': 1,
'new_instance_type_id': 2,
'created_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
},
{
'id': 5678,
'source_node': 'node10',
'dest_node': 'node20',
'source_compute': 'compute10',
'dest_compute': 'compute20',
'dest_host': '5.6.7.8',
'status': 'Done',
'instance_uuid': 'instance_id_456',
'old_instance_type_id': 5,
'new_instance_type_id': 6,
'created_at': datetime.datetime(2013, 10, 22, 13, 42, 2),
'updated_at': datetime.datetime(2013, 10, 22, 13, 42, 2),
}
]
class FakeRequest(object):
environ = {"nova.context": context.get_admin_context()}
GET = {}
class MigrationsTestCase(test.TestCase):
def setUp(self):
"""Run before each test."""
super(MigrationsTestCase, self).setUp()
self.controller = migrations.MigrationsController()
self.context = context.get_admin_context()
self.req = FakeRequest()
self.req.environ['nova.context'] = self.context
mox_fixture = self.useFixture(MoxStubout())
self.mox = mox_fixture.mox
def test_index(self):
migrations_in_progress = {'migrations': fake_migrations}
filters = {'host': 'host1', 'status': 'migrating',
'cell_name': 'ChildCell'}
self.req.GET = filters
self.mox.StubOutWithMock(self.controller.compute_api,
"get_migrations")
self.controller.compute_api.\
get_migrations(self.context, filters).\
AndReturn(fake_migrations)
self.mox.ReplayAll()
response = self.controller.index(self.req)
self.assertEqual(migrations_in_progress, response)
def test_index_needs_authorization(self):
user_context = context.RequestContext(user_id=None,
project_id=None,
is_admin=False,
read_deleted="no",
overwrite=False)
self.req.environ['nova.context'] = user_context
self.assertRaises(exception.PolicyNotAuthorized, self.controller.index,
self.req)
class MigrationsTemplateTest(test.TestCase):
def setUp(self):
super(MigrationsTemplateTest, self).setUp()
self.serializer = migrations.MigrationsTemplate()
def test_index_serialization(self):
res_xml = self.serializer.serialize({'migrations': fake_migrations})
tree = etree.XML(res_xml)
children = tree.findall('migration')
self.assertEqual(tree.tag, 'migrations')
self.assertEqual(2, len(children))
for idx, child in enumerate(children):
self.assertEqual(child.tag, 'migration')
migration = fake_migrations[idx]
for attr in migration.keys():
self.assertEqual(str(migration[attr]),
child.get(attr))

View File

@ -572,3 +572,33 @@ class CellsManagerClassTestCase(test.TestCase):
'fake_instance_uuid',
device_name='fake_device_name',
volume_id='fake_volume_id')
def test_get_migrations(self):
filters = {'status': 'confirmed'}
cell1_migrations = [{'id': 123}]
cell2_migrations = [{'id': 456}]
fake_responses = [self._get_fake_response(cell1_migrations),
self._get_fake_response(cell2_migrations)]
self.mox.StubOutWithMock(self.msg_runner,
'get_migrations')
self.msg_runner.get_migrations(self.ctxt, None, False, filters).\
AndReturn(fake_responses)
self.mox.ReplayAll()
response = self.cells_manager.get_migrations(self.ctxt, filters)
self.assertEqual([cell1_migrations[0], cell2_migrations[0]], response)
def test_get_migrations_for_a_given_cell(self):
filters = {'status': 'confirmed', 'cell_name': 'ChildCell1'}
target_cell = '%s%s%s' % (CONF.cells.name, '!', filters['cell_name'])
migrations = [{'id': 123}]
fake_responses = [self._get_fake_response(migrations)]
self.mox.StubOutWithMock(self.msg_runner,
'get_migrations')
self.msg_runner.get_migrations(self.ctxt, target_cell, False,
filters).AndReturn(fake_responses)
self.mox.ReplayAll()
response = self.cells_manager.get_migrations(self.ctxt, filters)
self.assertEqual(migrations, response)

View File

@ -1044,6 +1044,31 @@ class CellsTargetedMethodsTestCase(test.TestCase):
result = response.value_or_raise()
self.assertEqual('fake_result', result)
def test_get_migrations_for_a_given_cell(self):
filters = {'cell_name': 'child-cell2', 'status': 'confirmed'}
migrations_in_progress = [{'id': 123}]
self.mox.StubOutWithMock(self.tgt_compute_api,
'get_migrations')
self.tgt_compute_api.get_migrations(self.ctxt, filters).\
AndReturn(migrations_in_progress)
self.mox.ReplayAll()
responses = self.src_msg_runner.get_migrations(
self.ctxt,
self.tgt_cell_name, False, filters)
result = responses[0].value_or_raise()
self.assertEqual(migrations_in_progress, result)
def test_get_migrations_for_an_invalid_cell(self):
filters = {'cell_name': 'invalid_Cell', 'status': 'confirmed'}
responses = self.src_msg_runner.get_migrations(
self.ctxt,
'api_cell!invalid_cell', False, filters)
self.assertEqual(0, len(responses))
class CellsBroadcastMethodsTestCase(test.TestCase):
"""Test case for _BroadcastMessageMethods class. Most of these
@ -1696,3 +1721,30 @@ class CellsBroadcastMethodsTestCase(test.TestCase):
self.src_msg_runner.bdm_destroy_at_top(self.ctxt, fake_instance_uuid,
device_name=fake_device_name)
def test_get_migrations(self):
self._setup_attrs(up=False)
filters = {'status': 'confirmed'}
migrations_from_cell1 = [{'id': 123}]
migrations_from_cell2 = [{'id': 456}]
self.mox.StubOutWithMock(self.mid_compute_api,
'get_migrations')
self.mid_compute_api.get_migrations(self.ctxt, filters).\
AndReturn(migrations_from_cell1)
self.mox.StubOutWithMock(self.tgt_compute_api,
'get_migrations')
self.tgt_compute_api.get_migrations(self.ctxt, filters).\
AndReturn(migrations_from_cell2)
self.mox.ReplayAll()
responses = self.src_msg_runner.get_migrations(
self.ctxt,
None, False, filters)
self.assertEquals(2, len(responses))
for response in responses:
self.assertIn(response.value_or_raise(), [migrations_from_cell1,
migrations_from_cell2])

View File

@ -451,3 +451,13 @@ class CellsAPITestCase(test.TestCase):
'volume_id': 'fake-vol'}
self._check_result(call_info, 'bdm_destroy_at_top',
expected_args, version='1.10')
def test_get_migrations(self):
call_info = self._stub_rpc_method('call', None)
filters = {'cell_name': 'ChildCell', 'status': 'confirmed'}
self.cells_rpcapi.get_migrations(self.fake_context, filters)
expected_args = {'filters': filters}
self._check_result(call_info, 'get_migrations', expected_args,
version="1.11")

View File

@ -8001,6 +8001,18 @@ class ComputeAPITestCase(BaseTestCase):
admin_password=None)
db.instance_destroy(self.context, instance['uuid'])
def test_get_migrations(self):
migration = {uuid: "1234"}
filters = {'host': 'host1'}
self.mox.StubOutWithMock(db, "migration_get_all_by_filters")
db.migration_get_all_by_filters(self.context,
filters).AndReturn([migration])
self.mox.ReplayAll()
migrations = self.compute_api.get_migrations(self.context,
filters)
self.assertEqual(migrations, [migration])
def fake_rpc_method(context, topic, msg, do_cast=True):
pass

View File

@ -214,6 +214,19 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
self.mox.ReplayAll()
self.compute_api.soft_delete(self.context, inst)
def test_get_migrations(self):
filters = {'cell_name': 'ChildCell', 'status': 'confirmed'}
migrations = {'migrations': [{'id': 1234}]}
cells_rpcapi = self.compute_api.cells_rpcapi
self.mox.StubOutWithMock(cells_rpcapi, 'get_migrations')
cells_rpcapi.get_migrations(self.context,
filters).AndReturn(migrations)
self.mox.ReplayAll()
response = self.compute_api.get_migrations(self.context, filters)
self.assertEqual(migrations, response)
class CellsComputePolicyTestCase(test_compute.ComputePolicyTestCase):
def setUp(self):

View File

@ -870,6 +870,23 @@ class MigrationTestCase(test.TestCase):
instance = migration['instance']
self.assertEqual(migration['instance_uuid'], instance['uuid'])
def test_get_migrations_by_filters(self):
filters = {"status": "migrating", "host": "host3"}
migrations = db.migration_get_all_by_filters(self.ctxt, filters)
self.assertEqual(2, len(migrations))
for migration in migrations:
self.assertEqual(filters["status"], migration['status'])
hosts = [migration['source_compute'], migration['dest_compute']]
self.assertIn(filters["host"], hosts)
def test_only_admin_can_get_all_migrations_by_filters(self):
user_ctxt = context.RequestContext(user_id=None, project_id=None,
is_admin=False, read_deleted="no",
overwrite=False)
self.assertRaises(exception.AdminRequired,
db.migration_get_all_by_filters, user_ctxt, {})
class ModelsObjectComparatorMixin(object):
def _dict_from_object(self, obj, ignored_keys):

View File

@ -205,6 +205,8 @@ policy_data = """
"compute_extension:availability_zone:list": "",
"compute_extension:availability_zone:detail": "is_admin:True",
"compute_extension:used_limits_for_admin": "is_admin:True",
"compute_extension:migrations:index": "is_admin:True",
"volume:create": "",
"volume:get": "",

View File

@ -559,6 +559,14 @@
"name": "InstanceActions",
"namespace": "http://docs.openstack.org/compute/ext/instance-actions/api/v1.1",
"updated": "%(timestamp)s"
},
{
"alias": "os-migrations",
"description": "%(text)s",
"links": [],
"name": "Migrations",
"namespace": "http://docs.openstack.org/compute/ext/migrations/api/v2.0",
"updated": "%(timestamp)s"
}
]
}

View File

@ -210,4 +210,7 @@
<extension alias="os-instance-actions" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/instance-actions/api/v1.1" name="InstanceActions">
<description>%(text)s</description>
</extension>
<extension alias="os-migrations" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/migrations/api/v2.0" name="Migrations">
<description>%(text)s</description>
</extension>
</extensions>

View File

@ -0,0 +1,32 @@
{
"migrations": [
{
"created_at": "2012-10-29T13:42:02.000000",
"dest_compute": "compute2",
"dest_host": "1.2.3.4",
"dest_node": "node2",
"id": 1234,
"instance_uuid": "instance_id_123",
"new_instance_type_id": 2,
"old_instance_type_id": 1,
"source_compute": "compute1",
"source_node": "node1",
"status": "Done",
"updated_at": "2012-10-29T13:42:02.000000"
},
{
"created_at": "2013-10-22T13:42:02.000000",
"dest_compute": "compute20",
"dest_host": "5.6.7.8",
"dest_node": "node20",
"id": 5678,
"instance_uuid": "instance_id_456",
"new_instance_type_id": 6,
"old_instance_type_id": 5,
"source_compute": "compute10",
"source_node": "node10",
"status": "Done",
"updated_at": "2013-10-22T13:42:02.000000"
}
]
}

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<migrations>
<migration dest_host="1.2.3.4" status="Done" old_instance_type_id="1" updated_at="2012-10-29 13:42:02" dest_compute="compute2" created_at="2012-10-29 13:42:02" source_node="node1" instance_uuid="instance_id_123" dest_node="node2" id="1234" new_instance_type_id="2" source_compute="compute1"/>
<migration dest_host="5.6.7.8" status="Done" old_instance_type_id="5" updated_at="2013-10-22 13:42:02" dest_compute="compute20" created_at="2013-10-22 13:42:02" source_node="node10" instance_uuid="instance_id_456" dest_node="node20" id="5678" new_instance_type_id="6" source_compute="compute10"/>
</migrations>

View File

@ -3994,3 +3994,57 @@ class VolumesSampleJsonTest(ServersSampleBase):
class VolumesSampleXmlTest(VolumesSampleJsonTest):
ctype = 'xml'
class MigrationsSamplesJsonTest(ApiSampleTestBase):
extension_name = ("nova.api.openstack.compute.contrib.migrations."
"Migrations")
def _stub_migrations(self, context, filters):
fake_migrations = [
{
'id': 1234,
'source_node': 'node1',
'dest_node': 'node2',
'source_compute': 'compute1',
'dest_compute': 'compute2',
'dest_host': '1.2.3.4',
'status': 'Done',
'instance_uuid': 'instance_id_123',
'old_instance_type_id': 1,
'new_instance_type_id': 2,
'created_at': datetime.datetime(2012, 10, 29, 13, 42, 2),
'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)
},
{
'id': 5678,
'source_node': 'node10',
'dest_node': 'node20',
'source_compute': 'compute10',
'dest_compute': 'compute20',
'dest_host': '5.6.7.8',
'status': 'Done',
'instance_uuid': 'instance_id_456',
'old_instance_type_id': 5,
'new_instance_type_id': 6,
'created_at': datetime.datetime(2013, 10, 22, 13, 42, 2),
'updated_at': datetime.datetime(2013, 10, 22, 13, 42, 2)
}
]
return fake_migrations
def setUp(self):
super(MigrationsSamplesJsonTest, self).setUp()
self.stubs.Set(compute_api.API, 'get_migrations',
self._stub_migrations)
def test_get_migrations(self):
response = self._do_get('os-migrations')
subs = self._get_regexes()
self.assertEqual(response.status, 200)
self._verify_response('migrations-get', subs, response, 200)
class MigrationsSamplesXmlTest(MigrationsSamplesJsonTest):
ctype = 'xml'