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:
parent
ba70576a63
commit
405ebb9028
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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>
|
||||
|
32
doc/api_samples/os-migrations/migrations-get.json
Normal file
32
doc/api_samples/os-migrations/migrations-get.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
5
doc/api_samples/os-migrations/migrations-get.xml
Normal file
5
doc/api_samples/os-migrations/migrations-get.xml
Normal 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>
|
@ -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": "",
|
||||
|
77
nova/api/openstack/compute/contrib/migrations.py
Normal file
77
nova/api/openstack/compute/contrib/migrations.py
Normal 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
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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')
|
||||
|
@ -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."""
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
||||
|
||||
####################
|
||||
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
||||
##################
|
||||
|
||||
|
||||
|
122
nova/tests/api/openstack/compute/contrib/test_migrations.py
Normal file
122
nova/tests/api/openstack/compute/contrib/test_migrations.py
Normal 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))
|
@ -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)
|
||||
|
@ -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])
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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": "",
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
@ -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>
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user