compute: Add support for server migrations API
There are two migrations APIs in nova: the migrations API ('/os-migrations') and the *server* migrations API ('/servers/{id}/migrations'). The former is responsible for listing all migrations in a deployment, while the latter only lists those for a given server. In this change, we're adding support for the latter. Change-Id: Ideeca99a89c920a09cfc3799bbcc7e24046a5c43 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
6c96faa7d1
commit
6d1321f5dd
@ -152,7 +152,16 @@ Extension Operations
|
||||
|
||||
QuotaSet Operations
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. autoclass:: openstack.compute.v2._proxy.Proxy
|
||||
:noindex:
|
||||
:members: get_quota_set, get_quota_set_defaults,
|
||||
revert_quota_set, update_quota_set
|
||||
|
||||
Server Migration Operations
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. autoclass:: openstack.compute.v2._proxy.Proxy
|
||||
:noindex:
|
||||
:members: abort_server_migration, force_complete_server_migration,
|
||||
get_server_migration, server_migrations
|
||||
|
@ -11,6 +11,7 @@ Compute Resources
|
||||
v2/limits
|
||||
v2/server
|
||||
v2/server_interface
|
||||
v2/server_migration
|
||||
v2/server_ip
|
||||
v2/hypervisor
|
||||
v2/quota_set
|
||||
|
13
doc/source/user/resources/compute/v2/server_migration.rst
Normal file
13
doc/source/user/resources/compute/v2/server_migration.rst
Normal file
@ -0,0 +1,13 @@
|
||||
openstack.compute.v2.server_migration
|
||||
=====================================
|
||||
|
||||
.. automodule:: openstack.compute.v2.server_migration
|
||||
|
||||
The ServerMigration Class
|
||||
-------------------------
|
||||
|
||||
The ``ServerMigration`` class inherits from
|
||||
:class:`~openstack.resource.Resource`.
|
||||
|
||||
.. autoclass:: openstack.compute.v2.server_migration.ServerMigration
|
||||
:members:
|
@ -26,6 +26,7 @@ from openstack.compute.v2 import server_diagnostics as _server_diagnostics
|
||||
from openstack.compute.v2 import server_group as _server_group
|
||||
from openstack.compute.v2 import server_interface as _server_interface
|
||||
from openstack.compute.v2 import server_ip
|
||||
from openstack.compute.v2 import server_migration as _server_migration
|
||||
from openstack.compute.v2 import server_remote_console as _src
|
||||
from openstack.compute.v2 import service as _service
|
||||
from openstack.compute.v2 import volume_attachment as _volume_attachment
|
||||
@ -1717,6 +1718,118 @@ class Proxy(proxy.Proxy):
|
||||
block_migration=block_migration,
|
||||
)
|
||||
|
||||
def abort_server_migration(
|
||||
self, server_migration, server, ignore_missing=True,
|
||||
):
|
||||
"""Abort an in-progress server migration
|
||||
|
||||
:param server_migration: The value can be either the ID of a server
|
||||
migration or a
|
||||
:class:`~openstack.compute.v2.server_migration.ServerMigration`
|
||||
instance.
|
||||
:param server: This parameter needs to be specified when
|
||||
ServerMigration ID is given as value. It can be either the ID of a
|
||||
server or a :class:`~openstack.compute.v2.server.Server` instance
|
||||
that the migration belongs to.
|
||||
:param bool ignore_missing: When set to ``False``
|
||||
:class:`~openstack.exceptions.ResourceNotFound` will be raised when
|
||||
the volume attachment does not exist. When set to ``True``, no
|
||||
exception will be set when attempting to delete a nonexistent
|
||||
volume attachment.
|
||||
|
||||
:returns: ``None``
|
||||
"""
|
||||
server_id = self._get_uri_attribute(
|
||||
server_migration, server, 'server_id',
|
||||
)
|
||||
server_migration = resource.Resource._get_id(server_migration)
|
||||
|
||||
self._delete(
|
||||
_server_migration.ServerMigration,
|
||||
server_migration,
|
||||
server_id=server_id,
|
||||
ignore_missing=ignore_missing,
|
||||
)
|
||||
|
||||
def force_complete_server_migration(self, server_migration, server=None):
|
||||
"""Force complete an in-progress server migration
|
||||
|
||||
:param server_migration: The value can be either the ID of a server
|
||||
migration or a
|
||||
:class:`~openstack.compute.v2.server_migration.ServerMigration`
|
||||
instance.
|
||||
:param server: This parameter needs to be specified when
|
||||
ServerMigration ID is given as value. It can be either the ID of a
|
||||
server or a :class:`~openstack.compute.v2.server.Server` instance
|
||||
that the migration belongs to.
|
||||
|
||||
:returns: ``None``
|
||||
"""
|
||||
server_id = self._get_uri_attribute(
|
||||
server_migration, server, 'server_id',
|
||||
)
|
||||
server_migration = self._get_resource(
|
||||
_server_migration.ServerMigration,
|
||||
server_migration,
|
||||
server_id=server_id,
|
||||
)
|
||||
server_migration.force_complete(self)
|
||||
|
||||
def get_server_migration(
|
||||
self,
|
||||
server_migration,
|
||||
server,
|
||||
ignore_missing=True,
|
||||
):
|
||||
"""Get a single volume attachment
|
||||
|
||||
:param server_migration: The value can be the ID of a server migration
|
||||
or a
|
||||
:class:`~openstack.compute.v2.server_migration.ServerMigration`
|
||||
instance.
|
||||
:param server: This parameter need to be specified when ServerMigration
|
||||
ID is given as value. It can be either the ID of a server or a
|
||||
:class:`~openstack.compute.v2.server.Server` instance that the
|
||||
migration belongs to.
|
||||
:param bool ignore_missing: When set to ``False``
|
||||
:class:`~openstack.exceptions.ResourceNotFound` will be raised when
|
||||
the server migration does not exist. When set to ``True``, no
|
||||
exception will be set when attempting to delete a nonexistent
|
||||
server migration.
|
||||
|
||||
:returns: One
|
||||
:class:`~openstack.compute.v2.server_migration.ServerMigration`
|
||||
:raises: :class:`~openstack.exceptions.ResourceNotFound`
|
||||
when no resource can be found.
|
||||
"""
|
||||
server_id = self._get_uri_attribute(
|
||||
server_migration, server, 'server_id',
|
||||
)
|
||||
server_migration = resource.Resource._get_id(server_migration)
|
||||
|
||||
return self._get(
|
||||
_server_migration.ServerMigration,
|
||||
server_migration,
|
||||
server_id=server_id,
|
||||
ignore_missing=ignore_missing,
|
||||
)
|
||||
|
||||
def server_migrations(self, server):
|
||||
"""Return a generator of migrations for a server.
|
||||
|
||||
:param server: The server can be either the ID of a server or a
|
||||
:class:`~openstack.compute.v2.server.Server`.
|
||||
|
||||
:returns: A generator of ServerMigration objects
|
||||
:rtype:
|
||||
:class:`~openstack.compute.v2.server_migration.ServerMigration`
|
||||
"""
|
||||
server_id = resource.Resource._get_id(server)
|
||||
return self._list(
|
||||
_server_migration.ServerMigration,
|
||||
server_id=server_id,
|
||||
)
|
||||
|
||||
# ========== Server diagnostics ==========
|
||||
|
||||
def get_server_diagnostics(self, server):
|
||||
|
98
openstack/compute/v2/server_migration.py
Normal file
98
openstack/compute/v2/server_migration.py
Normal file
@ -0,0 +1,98 @@
|
||||
# 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 openstack import exceptions
|
||||
from openstack import resource
|
||||
from openstack import utils
|
||||
|
||||
|
||||
class ServerMigration(resource.Resource):
|
||||
resource_key = 'migration'
|
||||
resources_key = 'migrations'
|
||||
base_path = '/servers/%(server_uuid)s/migrations'
|
||||
|
||||
# capabilities
|
||||
allow_fetch = True
|
||||
allow_list = True
|
||||
allow_delete = True
|
||||
|
||||
#: The ID for the server.
|
||||
server_id = resource.URI('server_uuid')
|
||||
|
||||
#: The date and time when the resource was created.
|
||||
created_at = resource.Body('created_at')
|
||||
#: The target host of the migration.
|
||||
dest_host = resource.Body('dest_host')
|
||||
#: The target compute of the migration.
|
||||
dest_compute = resource.Body('dest_compute')
|
||||
#: The target node of the migration.
|
||||
dest_node = resource.Body('dest_node')
|
||||
#: The amount of disk, in bytes, that has been processed during the
|
||||
#: migration.
|
||||
disk_processed_bytes = resource.Body('disk_processed_bytes')
|
||||
#: The amount of disk, in bytes, that still needs to be migrated.
|
||||
disk_remaining_bytes = resource.Body('disk_remaining_bytes')
|
||||
#: The total amount of disk, in bytes, that needs to be migrated.
|
||||
disk_total_bytes = resource.Body('disk_total_bytes')
|
||||
#: The amount of memory, in bytes, that has been processed during the
|
||||
#: migration.
|
||||
memory_processed_bytes = resource.Body('memory_processed_bytes')
|
||||
#: The amount of memory, in bytes, that still needs to be migrated.
|
||||
memory_remaining_bytes = resource.Body('memory_remaining_bytes')
|
||||
#: The total amount of memory, in bytes, that needs to be migrated.
|
||||
memory_total_bytes = resource.Body('memory_total_bytes')
|
||||
#: The ID of the project that initiated the server migration (since
|
||||
#: microversion 2.80)
|
||||
project_id = resource.Body('project_id')
|
||||
# FIXME(stephenfin): This conflicts since there is a server ID in the URI
|
||||
# *and* in the body. We need a field that handles both or we need to use
|
||||
# different names.
|
||||
# #: The UUID of the server
|
||||
# server_id = resource.Body('server_uuid')
|
||||
#: The source compute of the migration.
|
||||
source_compute = resource.Body('source_compute')
|
||||
#: The source node of the migration.
|
||||
source_node = resource.Body('source_node')
|
||||
#: The current status of the migration.
|
||||
status = resource.Body('status')
|
||||
#: The date and time when the resource was last updated.
|
||||
updated_at = resource.Body('updated_at')
|
||||
#: The ID of the user that initiated the server migration (since
|
||||
#: microversion 2.80)
|
||||
user_id = resource.Body('user_id')
|
||||
#: The UUID of the migration (since microversion 2.59)
|
||||
uuid = resource.Body('uuid', alternate_id=True)
|
||||
|
||||
_max_microversion = '2.80'
|
||||
|
||||
@classmethod
|
||||
def _get_microversion_for_action(cls, session):
|
||||
return cls._get_microversion_for_list(session)
|
||||
|
||||
def _action(self, session, body):
|
||||
"""Preform server migration actions given the message body."""
|
||||
session = self._get_session(session)
|
||||
microversion = self._get_microversion_for_list(session)
|
||||
|
||||
url = utils.urljoin(
|
||||
self.base_path % {'server_uuid': self.server_id},
|
||||
self.id,
|
||||
'action',
|
||||
)
|
||||
response = session.post(url, microversion=microversion, json=body)
|
||||
exceptions.raise_from_response(response)
|
||||
return response
|
||||
|
||||
def force_complete(self, session):
|
||||
"""Force on-going live migration to complete."""
|
||||
body = {'force_complete': None}
|
||||
self._action(session, body)
|
@ -26,6 +26,7 @@ from openstack.compute.v2 import server
|
||||
from openstack.compute.v2 import server_group
|
||||
from openstack.compute.v2 import server_interface
|
||||
from openstack.compute.v2 import server_ip
|
||||
from openstack.compute.v2 import server_migration
|
||||
from openstack.compute.v2 import server_remote_console
|
||||
from openstack.compute.v2 import service
|
||||
from openstack import resource
|
||||
@ -1004,6 +1005,53 @@ class TestCompute(TestComputeProxy):
|
||||
expected_args=[self.proxy, "host1"],
|
||||
expected_kwargs={'force': False, 'block_migration': None})
|
||||
|
||||
def test_abort_server_migration(self):
|
||||
self._verify(
|
||||
'openstack.proxy.Proxy._delete',
|
||||
self.proxy.abort_server_migration,
|
||||
method_args=['server_migration', 'server'],
|
||||
expected_args=[
|
||||
server_migration.ServerMigration,
|
||||
'server_migration',
|
||||
],
|
||||
expected_kwargs={
|
||||
'server_id': 'server',
|
||||
'ignore_missing': True,
|
||||
},
|
||||
)
|
||||
|
||||
def test_force_complete_server_migration(self):
|
||||
self._verify(
|
||||
'openstack.compute.v2.server_migration.ServerMigration.force_complete', # noqa: E501
|
||||
self.proxy.force_complete_server_migration,
|
||||
method_args=['server_migration', 'server'],
|
||||
expected_args=[self.proxy],
|
||||
)
|
||||
|
||||
def test_get_server_migration(self):
|
||||
self._verify(
|
||||
'openstack.proxy.Proxy._get',
|
||||
self.proxy.get_server_migration,
|
||||
method_args=['server_migration', 'server'],
|
||||
expected_args=[
|
||||
server_migration.ServerMigration,
|
||||
'server_migration',
|
||||
],
|
||||
expected_kwargs={
|
||||
'server_id': 'server',
|
||||
'ignore_missing': True,
|
||||
},
|
||||
)
|
||||
|
||||
def test_server_migrations(self):
|
||||
self._verify(
|
||||
'openstack.proxy.Proxy._list',
|
||||
self.proxy.server_migrations,
|
||||
method_args=['server'],
|
||||
expected_args=[server_migration.ServerMigration],
|
||||
expected_kwargs={'server_id': 'server'},
|
||||
)
|
||||
|
||||
def test_fetch_security_groups(self):
|
||||
self._verify(
|
||||
'openstack.compute.v2.server.Server.fetch_security_groups',
|
||||
|
112
openstack/tests/unit/compute/v2/test_server_migration.py
Normal file
112
openstack/tests/unit/compute/v2/test_server_migration.py
Normal file
@ -0,0 +1,112 @@
|
||||
# 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 unittest import mock
|
||||
|
||||
from openstack.compute.v2 import server_migration
|
||||
from openstack.tests.unit import base
|
||||
|
||||
EXAMPLE = {
|
||||
'id': 4,
|
||||
'server_uuid': '4cfba335-03d8-49b2-8c52-e69043d1e8fe',
|
||||
'user_id': '8dbaa0f0-ab95-4ffe-8cb4-9c89d2ac9d24',
|
||||
'project_id': '5f705771-3aa9-4f4c-8660-0d9522ffdbea',
|
||||
'created_at': '2016-01-29T13:42:02.000000',
|
||||
'updated_at': '2016-01-29T13:42:02.000000',
|
||||
'status': 'migrating',
|
||||
'source_compute': 'compute1',
|
||||
'source_node': 'node1',
|
||||
'dest_host': '1.2.3.4',
|
||||
'dest_compute': 'compute2',
|
||||
'dest_node': 'node2',
|
||||
'memory_processed_bytes': 12345,
|
||||
'memory_remaining_bytes': 111111,
|
||||
'memory_total_bytes': 123456,
|
||||
'disk_processed_bytes': 23456,
|
||||
'disk_remaining_bytes': 211111,
|
||||
'disk_total_bytes': 234567,
|
||||
}
|
||||
|
||||
|
||||
class TestServerMigration(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.resp = mock.Mock()
|
||||
self.resp.body = None
|
||||
self.resp.json = mock.Mock(return_value=self.resp.body)
|
||||
self.resp.status_code = 200
|
||||
self.sess = mock.Mock()
|
||||
self.sess.post = mock.Mock(return_value=self.resp)
|
||||
|
||||
def test_basic(self):
|
||||
sot = server_migration.ServerMigration()
|
||||
self.assertEqual('migration', sot.resource_key)
|
||||
self.assertEqual('migrations', sot.resources_key)
|
||||
self.assertEqual('/servers/%(server_uuid)s/migrations', sot.base_path)
|
||||
self.assertFalse(sot.allow_create)
|
||||
self.assertTrue(sot.allow_fetch)
|
||||
self.assertTrue(sot.allow_list)
|
||||
self.assertFalse(sot.allow_commit)
|
||||
self.assertTrue(sot.allow_delete)
|
||||
|
||||
def test_make_it(self):
|
||||
sot = server_migration.ServerMigration(**EXAMPLE)
|
||||
self.assertEqual(EXAMPLE['id'], sot.id)
|
||||
# FIXME(stephenfin): This conflicts since there is a server ID in the
|
||||
# URI *and* in the body. We need a field that handles both or we need
|
||||
# to use different names.
|
||||
# self.assertEqual(EXAMPLE['server_uuid'], sot.server_id)
|
||||
self.assertEqual(EXAMPLE['user_id'], sot.user_id)
|
||||
self.assertEqual(EXAMPLE['project_id'], sot.project_id)
|
||||
self.assertEqual(EXAMPLE['created_at'], sot.created_at)
|
||||
self.assertEqual(EXAMPLE['updated_at'], sot.updated_at)
|
||||
self.assertEqual(EXAMPLE['status'], sot.status)
|
||||
self.assertEqual(EXAMPLE['source_compute'], sot.source_compute)
|
||||
self.assertEqual(EXAMPLE['source_node'], sot.source_node)
|
||||
self.assertEqual(EXAMPLE['dest_host'], sot.dest_host)
|
||||
self.assertEqual(EXAMPLE['dest_compute'], sot.dest_compute)
|
||||
self.assertEqual(EXAMPLE['dest_node'], sot.dest_node)
|
||||
self.assertEqual(
|
||||
EXAMPLE['memory_processed_bytes'],
|
||||
sot.memory_processed_bytes,
|
||||
)
|
||||
self.assertEqual(
|
||||
EXAMPLE['memory_remaining_bytes'],
|
||||
sot.memory_remaining_bytes,
|
||||
)
|
||||
self.assertEqual(EXAMPLE['memory_total_bytes'], sot.memory_total_bytes)
|
||||
self.assertEqual(
|
||||
EXAMPLE['disk_processed_bytes'],
|
||||
sot.disk_processed_bytes,
|
||||
)
|
||||
self.assertEqual(
|
||||
EXAMPLE['disk_remaining_bytes'],
|
||||
sot.disk_remaining_bytes,
|
||||
)
|
||||
self.assertEqual(EXAMPLE['disk_total_bytes'], sot.disk_total_bytes)
|
||||
|
||||
@mock.patch.object(
|
||||
server_migration.ServerMigration, '_get_session', lambda self, x: x,
|
||||
)
|
||||
def test_force_complete(self):
|
||||
sot = server_migration.ServerMigration(**EXAMPLE)
|
||||
|
||||
self.assertIsNone(sot.force_complete(self.sess))
|
||||
|
||||
url = 'servers/%s/migrations/%s/action' % (
|
||||
EXAMPLE['server_uuid'], EXAMPLE['id']
|
||||
)
|
||||
body = {'force_complete': None}
|
||||
self.sess.post.assert_called_with(
|
||||
url, microversion=mock.ANY, json=body,
|
||||
)
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add support for the Compute service's server migrations API, allowing users
|
||||
to list all migrations for a server as well as force complete or abort
|
||||
in-progress migrations.
|
Loading…
Reference in New Issue
Block a user