Add new API to force live migration to complete

This change adds manual knob to force ongoing live migration to
complete. It is implemented as a new server-migrations API.

DocImpact
ApiImpact

Implements: blueprint pause-vm-during-live-migration
Change-Id: I034b4041414a797f65ede52db2963107f2ef7456
This commit is contained in:
Pawel Koniszewski 2016-02-08 08:59:52 +01:00
parent 23063011b1
commit c9091d0871
32 changed files with 511 additions and 8 deletions

View File

@ -0,0 +1,3 @@
{
"force_complete": null
}

View File

@ -0,0 +1,7 @@
{
"os-migrateLive": {
"host": "01c0cadef72d47e28a672a76060d492c",
"block_migration": false,
"disk_over_commit": false
}
}

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.21",
"version": "2.22",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.21",
"version": "2.22",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -13,7 +13,7 @@
"disabled_reason": null,
"report_count": 1,
"forced_down": false,
"version": 6
"version": 7
}
},
"event_type": "service.update",

View File

@ -270,6 +270,8 @@
"os_compute_api:servers:start": "rule:admin_or_owner",
"os_compute_api:servers:stop": "rule:admin_or_owner",
"os_compute_api:servers:trigger_crash_dump": "rule:admin_or_owner",
"os_compute_api:servers:migrations:discoverable": "",
"os_compute_api:servers:migrations:force_complete": "rule:admin_api",
"os_compute_api:os-access-ips:discoverable": "",
"os_compute_api:os-access-ips": "",
"os_compute_api:os-admin-actions": "rule:admin_api",

View File

@ -64,7 +64,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 2.20 - Add attach and detach volume operations for instances in shelved
and shelved_offloaded state
* 2.21 - Make os-instance-actions read deleted instances
* 2.22 - Add API to force live migration to complete
"""
# The minimum and maximum versions of the API supported
@ -73,7 +73,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.21"
_MAX_API_VERSION = "2.22"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -82,7 +82,7 @@ v21_to_v2_extension_list_mapping = {
v2_extension_suppress_list = ['servers', 'images', 'versions', 'flavors',
'os-block-device-mapping-v1', 'os-consoles',
'extensions', 'image-metadata', 'ips', 'limits',
'server-metadata'
'server-metadata', 'server-migrations'
]
# v2.1 plugins which should appear under a different name in v2

View File

@ -0,0 +1,26 @@
# Copyright 2016 OpenStack Foundation
# 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.
force_complete = {
'type': 'object',
'properties': {
'force_complete': {
'type': 'null'
}
},
'required': ['force_complete'],
'additionalProperties': False,
}

View File

@ -0,0 +1,78 @@
# Copyright 2016 OpenStack Foundation
# 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.
from webob import exc
from nova.api.openstack import common
from nova.api.openstack.compute.schemas import server_migrations
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api import validation
from nova import compute
from nova import exception
ALIAS = 'servers:migrations'
authorize = extensions.os_compute_authorizer(ALIAS)
class ServerMigrationsController(wsgi.Controller):
"""The server migrations API controller for the OpenStack API."""
def __init__(self):
self.compute_api = compute.API(skip_policy_check=True)
super(ServerMigrationsController, self).__init__()
@wsgi.Controller.api_version("2.22")
@wsgi.response(202)
@extensions.expected_errors((400, 403, 404, 409))
@wsgi.action('force_complete')
@validation.schema(server_migrations.force_complete)
def _force_complete(self, req, id, server_id, body):
context = req.environ['nova.context']
authorize(context, action='force_complete')
instance = common.get_instance(self.compute_api, context, server_id)
try:
self.compute_api.live_migrate_force_complete(context, instance, id)
except exception.InstanceNotFound as e:
raise exc.HTTPNotFound(explanation=e.format_message())
except (exception.MigrationNotFoundByStatus,
exception.InvalidMigrationState,
exception.MigrationNotFoundForInstance) as e:
raise exc.HTTPBadRequest(explanation=e.format_message())
except exception.InstanceIsLocked as e:
raise exc.HTTPConflict(explanation=e.format_message())
except exception.InstanceInvalidState as state_error:
common.raise_http_conflict_for_instance_invalid_state(
state_error, 'force_complete', server_id)
class ServerMigrations(extensions.V21APIExtensionBase):
"""Server Migrations API."""
name = "ServerMigrations"
alias = 'server-migrations'
version = 1
def get_resources(self):
parent = {'member_name': 'server',
'collection_name': 'servers'}
member_actions = {'action': 'POST'}
resources = [extensions.ResourceExtension(
'migrations', ServerMigrationsController(),
parent=parent, member_actions=member_actions)]
return resources
def get_controller_extensions(self):
return []

View File

@ -181,5 +181,17 @@ user documentation.
2.21
----
The ``os-instance-actions`` API now returns information from deleted
instances.
2.22
----
A new resource servers:migrations added. A new API to force live migration
to complete added::
POST /servers/<uuid>/migrations/<id>/action
{
"force_complete": null
}

View File

@ -3287,6 +3287,35 @@ class API(base.Base):
host_name, block_migration=block_migration,
disk_over_commit=disk_over_commit)
@check_instance_lock
@check_instance_cell
@check_instance_state(vm_state=[vm_states.ACTIVE],
task_state=[task_states.MIGRATING])
def live_migrate_force_complete(self, context, instance, migration_id):
"""Force live migration to complete.
:param context: Security context
:param instance: The instance that is being migrated
:param migration_id: ID of ongoing migration
"""
LOG.debug("Going to try to force live migration to complete",
instance=instance)
# NOTE(pkoniszewski): Get migration object to check if there is ongoing
# live migration for particular instance. Also pass migration id to
# compute to double check and avoid possible race condition.
migration = objects.Migration.get_by_id_and_instance(
context, migration_id, instance.uuid)
if migration.status != 'running':
raise exception.InvalidMigrationState(migration_id=migration_id,
instance_uuid=instance.uuid,
state=migration.status,
method='force complete')
self.compute_rpcapi.live_migration_force_complete(
context, instance, migration.id)
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED,
vm_states.ERROR])
def evacuate(self, context, instance, host, on_shared_storage,

View File

@ -671,7 +671,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
class ComputeManager(manager.Manager):
"""Manages the running instances from creation to destruction."""
target = messaging.Target(version='4.8')
target = messaging.Target(version='4.9')
# How long to wait in seconds before re-issuing a shutdown
# signal to an instance during power off. The overall
@ -5250,6 +5250,29 @@ class ComputeManager(manager.Manager):
block_migration, migration,
migrate_data)
@wrap_exception()
@wrap_instance_fault
def live_migration_force_complete(self, context, instance, migration_id):
"""Force live migration to complete.
:param context: Security context
:param instance: The instance that is being migrated
:param migration_id: ID of ongoing migration
"""
migration = objects.Migration.get_by_id(context, migration_id)
if migration.status != 'running':
raise exception.InvalidMigrationState(migration_id=migration_id,
instance_uuid=instance.uuid,
state=migration.status,
method='force complete')
self._notify_about_instance_usage(
context, instance, 'live.migration.force.complete.start')
self.driver.live_migration_force_complete(instance)
self._notify_about_instance_usage(
context, instance, 'live.migration.force.complete.end')
def _live_migration_cleanup_flags(self, block_migration, migrate_data):
"""Determine whether disks or instance path need to be cleaned up after
live migration (at source on success, at destination on rollback)

View File

@ -325,6 +325,7 @@ class ComputeAPI(object):
rollback_live_migration_at_destination, and
pre_live_migration.
* ... - Remove refresh_provider_fw_rules()
* 4.9 - Add live_migration_force_complete()
'''
VERSION_ALIASES = {
@ -636,6 +637,13 @@ class ComputeAPI(object):
dest=dest, block_migration=block_migration,
migrate_data=migrate_data, **args)
def live_migration_force_complete(self, ctxt, instance, migration_id):
version = '4.9'
cctxt = self.client.prepare(server=_compute_host(None, instance),
version=version)
cctxt.cast(ctxt, 'live_migration_force_complete', instance=instance,
migration_id=migration_id)
def pause_instance(self, ctxt, instance):
version = '4.0'
cctxt = self.client.prepare(server=_compute_host(None, instance),

View File

@ -1127,6 +1127,12 @@ class MigrationNotFoundForInstance(MigrationNotFound):
"%(instance_id)s")
class InvalidMigrationState(Invalid):
msg_fmt = _("Migration %(migration_id)s state of instance "
"%(instance_uuid)s is %(state)s. Cannot %(method)s while the "
"migration is in this state.")
class ConsoleLogOutputException(NovaException):
msg_fmt = _("Console log output could not be retrieved for instance "
"%(instance_id)s. Reason: %(reason)s")

View File

@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__)
# NOTE(danms): This is the global service version counter
SERVICE_VERSION = 6
SERVICE_VERSION = 7
# NOTE(danms): This is our SERVICE_VERSION history. The idea is that any
@ -65,6 +65,8 @@ SERVICE_VERSION_HISTORY = (
{'compute_rpc': '4.7'},
# Version 6: Compute RPC version 4.8
{'compute_rpc': '4.8'},
# Version 7: Add live_migration_force_complete in the compute_rpc
{'compute_rpc': '4.9'},
)

View File

@ -0,0 +1,3 @@
{
"force_complete": null
}

View File

@ -0,0 +1,7 @@
{
"os-migrateLive": {
"host": "%(hostname)s",
"block_migration": false,
"disk_over_commit": false
}
}

View File

@ -0,0 +1,52 @@
# Copyright 2016 OpenStack Foundation
# 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 mock
from nova.conductor import manager as conductor_manager
from nova import db
from nova import objects
from nova.tests.functional.api_sample_tests import test_servers
class ServerMigrationsSampleJsonTest(test_servers.ServersSampleBase):
extension_name = 'server-migrations'
scenarios = [('v2_22', {'api_major_version': 'v2.1'})]
extra_extensions_to_load = ["os-migrate-server", "os-access-ips"]
def setUp(self):
"""setUp method for server usage."""
super(ServerMigrationsSampleJsonTest, self).setUp()
self.uuid = self._post_server()
@mock.patch.object(conductor_manager.ComputeTaskManager, '_live_migrate')
@mock.patch.object(db, 'service_get_by_compute_host')
@mock.patch.object(objects.Migration, 'get_by_id_and_instance')
@mock.patch('nova.compute.manager.ComputeManager.'
'live_migration_force_complete')
def test_live_migrate_force_complete(self, live_migration_pause_instance,
get_by_id_and_instance,
service_get_by_compute_host,
_live_migrate):
migration = objects.Migration()
migration.id = 1
migration.status = 'running'
get_by_id_and_instance.return_value = migration
self._do_post('servers/%s/action' % self.uuid, 'live-migrate-server',
{'hostname': self.compute.host})
response = self._do_post('servers/%s/migrations/%s/action'
% (self.uuid, '3'), 'force_complete',
{}, api_version='2.22')
self.assertEqual(202, response.status_code)

View File

@ -0,0 +1,108 @@
# Copyright 2016 OpenStack Foundation
# 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 mock
import webob
from nova.api.openstack.compute import server_migrations
from nova import exception
from nova import test
from nova.tests.unit.api.openstack import fakes
class ServerMigrationsTestsV21(test.NoDBTestCase):
wsgi_api_version = '2.22'
def setUp(self):
super(ServerMigrationsTestsV21, self).setUp()
self.req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
self.context = self.req.environ['nova.context']
self.controller = server_migrations.ServerMigrationsController()
self.compute_api = self.controller.compute_api
def test_force_complete_succeeded(self):
@mock.patch.object(self.compute_api, 'live_migrate_force_complete')
@mock.patch.object(self.compute_api, 'get')
def _do_test(compute_api_get, live_migrate_force_complete):
self.controller._force_complete(self.req, '1', '1',
body={'force_complete': None})
live_migrate_force_complete.assert_called_once_with(
self.context, compute_api_get(), '1')
_do_test()
def _test_force_complete_failed_with_exception(self, fake_exc,
expected_exc):
@mock.patch.object(self.compute_api, 'live_migrate_force_complete',
side_effect=fake_exc)
@mock.patch.object(self.compute_api, 'get')
def _do_test(compute_api_get, live_migrate_force_complete):
self.assertRaises(expected_exc,
self.controller._force_complete,
self.req, '1', '1',
body={'force_complete': None})
_do_test()
def test_force_complete_instance_not_migrating(self):
self._test_force_complete_failed_with_exception(
exception.InstanceInvalidState(instance_uuid='', state='',
attr='', method=''),
webob.exc.HTTPConflict)
def test_force_complete_migration_not_found(self):
self._test_force_complete_failed_with_exception(
exception.MigrationNotFoundByStatus(instance_id='', status=''),
webob.exc.HTTPBadRequest)
def test_force_complete_instance_is_locked(self):
self._test_force_complete_failed_with_exception(
exception.InstanceIsLocked(instance_uuid=''),
webob.exc.HTTPConflict)
def test_force_complete_invalid_migration_state(self):
self._test_force_complete_failed_with_exception(
exception.InvalidMigrationState(migration_id='', instance_uuid='',
state='', method=''),
webob.exc.HTTPBadRequest)
def test_force_complete_instance_not_found(self):
self._test_force_complete_failed_with_exception(
exception.InstanceNotFound(instance_id=''),
webob.exc.HTTPNotFound)
def test_force_complete_unexpected_error(self):
self._test_force_complete_failed_with_exception(
exception.NovaException(), webob.exc.HTTPInternalServerError)
class ServerMigrationsPolicyEnforcementV21(test.NoDBTestCase):
wsgi_api_version = '2.22'
def setUp(self):
super(ServerMigrationsPolicyEnforcementV21, self).setUp()
self.controller = server_migrations.ServerMigrationsController()
self.req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
def test_migrate_live_policy_failed(self):
rule_name = "os_compute_api:servers:migrations:force_complete"
self.policy.set_rules({rule_name: "project:non_fake"})
body_args = {'force_complete': None}
exc = self.assertRaises(
exception.PolicyNotAuthorized,
self.controller._force_complete, self.req,
fakes.FAKE_UUID, fakes.FAKE_UUID,
body=body_args)
self.assertEqual(
"Policy doesn't allow %s to be performed." % rule_name,
exc.format_message())

View File

@ -3212,6 +3212,55 @@ class _ComputeAPIUnitTestMixIn(object):
self.assertEqual(expect_statuses[instance.uuid],
host_statuses[instance.uuid])
@mock.patch.object(objects.Migration, 'get_by_id_and_instance')
def test_live_migrate_force_complete_succeeded(
self, get_by_id_and_instance):
if self.cell_type == 'api':
# cell api has not been implemented.
return
rpcapi = self.compute_api.compute_rpcapi
instance = self._create_instance_obj()
instance.task_state = task_states.MIGRATING
migration = objects.Migration()
migration.id = 0
migration.status = 'running'
get_by_id_and_instance.return_value = migration
with mock.patch.object(
rpcapi, 'live_migration_force_complete') as lm_force_complete:
self.compute_api.live_migrate_force_complete(
self.context, instance, migration.id)
lm_force_complete.assert_called_once_with(self.context,
instance,
0)
@mock.patch.object(objects.Migration, 'get_by_id_and_instance')
def test_live_migrate_force_complete_invalid_migration_state(
self, get_by_id_and_instance):
instance = self._create_instance_obj()
instance.task_state = task_states.MIGRATING
migration = objects.Migration()
migration.id = 0
migration.status = 'error'
get_by_id_and_instance.return_value = migration
self.assertRaises(exception.InvalidMigrationState,
self.compute_api.live_migrate_force_complete,
self.context, instance, migration.id)
def test_live_migrate_force_complete_invalid_vm_state(self):
instance = self._create_instance_obj()
instance.task_state = None
self.assertRaises(exception.InstanceInvalidState,
self.compute_api.live_migrate_force_complete,
self.context, instance, '1')
class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
def setUp(self):

View File

@ -4356,3 +4356,50 @@ class ComputeManagerMigrationTestCase(test.NoDBTestCase):
'foo', False, {})
self.assertIsInstance(mock_lmcf.call_args_list[0][0][1],
migrate_data_obj.LiveMigrateData)
def test_live_migration_force_complete_succeeded(self):
instance = objects.Instance(uuid=str(uuid.uuid4()))
migration = objects.Migration()
migration.status = 'running'
migration.id = 0
@mock.patch.object(self.compute, '_notify_about_instance_usage')
@mock.patch.object(objects.Migration, 'get_by_id',
return_value=migration)
@mock.patch.object(self.compute.driver,
'live_migration_force_complete')
def _do_test(force_complete, get_by_id, _notify_about_instance_usage):
self.compute.live_migration_force_complete(
self.context, instance, migration.id)
force_complete.assert_called_once_with(instance)
_notify_usage_calls = [
mock.call(self.context, instance,
'live.migration.force.complete.start'),
mock.call(self.context, instance,
'live.migration.force.complete.end')
]
_notify_about_instance_usage.assert_has_calls(_notify_usage_calls)
_do_test()
@mock.patch.object(compute_utils, 'add_instance_fault_from_exc')
def test_live_migration_pause_vm_invalid_migration_state(
self, add_instance_fault_from_exc):
instance = objects.Instance(id=1234, uuid=str(uuid.uuid4()))
migration = objects.Migration()
migration.status = 'aborted'
migration.id = 0
@mock.patch.object(objects.Migration, 'get_by_id',
return_value=migration)
def _do_test(get_by_id):
self.assertRaises(exception.InvalidMigrationState,
self.compute.live_migration_force_complete,
self.context, instance, migration.id)
_do_test()

View File

@ -304,6 +304,11 @@ class ComputeRpcAPITestCase(test.NoDBTestCase):
migration='migration',
migrate_data={}, version='4.8')
def test_live_migration_force_complete(self):
self._test_compute_api('live_migration_force_complete', 'cast',
instance=self.fake_instance_obj,
migration_id='1', version='4.9')
def test_post_live_migration_at_destination(self):
self._test_compute_api('post_live_migration_at_destination', 'cast',
instance=self.fake_instance_obj,

View File

@ -125,6 +125,7 @@ policy_data = """
"os_compute_api:servers:start": "",
"os_compute_api:servers:stop": "",
"os_compute_api:servers:trigger_crash_dump": "",
"os_compute_api:servers:migrations:force_complete": "",
"os_compute_api:os-access-ips": "",
"compute_extension:accounts": "",
"compute_extension:admin_actions:pause": "",

View File

@ -300,6 +300,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
"os_compute_api:servers:detail:get_all_tenants",
"os_compute_api:servers:index:get_all_tenants",
"os_compute_api:servers:show:host_status",
"os_compute_api:servers:migrations:force_complete",
"network:attach_external_network",
"os_compute_api:os-admin-actions",
"os_compute_api:os-admin-actions:reset_network",
@ -672,6 +673,7 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
"os_compute_api:os-server-usage:discoverable",
"os_compute_api:os-server-groups",
"os_compute_api:os-server-groups:discoverable",
"os_compute_api:servers:migrations:discoverable",
"os_compute_api:os-services:discoverable",
"os_compute_api:server-metadata:discoverable",
"os_compute_api:servers:discoverable",

View File

@ -13015,6 +13015,12 @@ class LibvirtConnTestCase(test.NoDBTestCase):
lambda x: x,
lambda x: x)
@mock.patch.object(libvirt_driver.LibvirtDriver, "pause")
def test_live_migration_force_complete(self, pause):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
drvr.live_migration_force_complete(self.test_instance)
pause.assert_called_once_with(self.test_instance)
@mock.patch('os.path.exists', return_value=True)
@mock.patch('tempfile.mkstemp')
@mock.patch('os.close', return_value=None)

View File

@ -661,6 +661,11 @@ class _VirtDriverTestCase(_FakeDriverBackendTestCase):
self.connection.live_migration(self.ctxt, instance_ref, 'otherhost',
lambda *a: None, lambda *a: None)
@catch_notimplementederror
def test_live_migration_force_complete(self):
instance_ref, network_info = self._get_running_instance()
self.connection.live_migration_force_complete(instance_ref)
@catch_notimplementederror
def _check_available_resource_fields(self, host_status):
keys = ['vcpus', 'memory_mb', 'local_gb', 'vcpus_used',

View File

@ -860,6 +860,14 @@ class ComputeDriver(object):
"""
raise NotImplementedError()
def live_migration_force_complete(self, instance):
"""Force live migration to complete
:param instance: Instance being live migrated
"""
raise NotImplementedError()
def rollback_live_migration_at_destination(self, context, instance,
network_info,
block_device_info,

View File

@ -468,6 +468,9 @@ class FakeDriver(driver.ComputeDriver):
migrate_data)
return
def live_migration_force_complete(self, instance):
return
def check_can_live_migrate_destination_cleanup(self, context,
dest_check_data):
return

View File

@ -6413,6 +6413,12 @@ class LibvirtDriver(driver.ComputeDriver):
LOG.debug("Live migration monitoring is all done",
instance=instance)
def live_migration_force_complete(self, instance):
# NOTE(pkoniszewski): currently only pause during live migration is
# supported to force live migration to complete, so just try to pause
# the instance
self.pause(instance)
def _try_fetch_image(self, context, path, image_id, instance,
fallback_from_host=None):
try:

View File

@ -0,0 +1,4 @@
---
features:
- A new REST API to force live migration to complete has been added
in microversion 2.22.

View File

@ -137,6 +137,7 @@ nova.api.v21.extensions =
server_diagnostics = nova.api.openstack.compute.server_diagnostics:ServerDiagnostics
server_external_events = nova.api.openstack.compute.server_external_events:ServerExternalEvents
server_metadata = nova.api.openstack.compute.server_metadata:ServerMetadata
server_migrations = nova.api.openstack.compute.server_migrations:ServerMigrations
server_password = nova.api.openstack.compute.server_password:ServerPassword
server_usage = nova.api.openstack.compute.server_usage:ServerUsage
server_groups = nova.api.openstack.compute.server_groups:ServerGroups