compute: Add support for instance actions

These become server actions, in keeping with our preference for using
"server" rather than "instance". This is a read-only API so relatively
easy to implement.

Change-Id: I97d885cbaf99862cff801816c306e2d95b44e7ce
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane
2021-10-23 15:56:29 +01:00
parent 0876a2486a
commit 6d8be2b2df
4 changed files with 247 additions and 0 deletions

View File

@@ -23,6 +23,7 @@ from openstack.compute.v2 import limits
from openstack.compute.v2 import migration as _migration
from openstack.compute.v2 import quota_set as _quota_set
from openstack.compute.v2 import server as _server
from openstack.compute.v2 import server_action as _server_action
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
@@ -2010,6 +2011,49 @@ class Proxy(proxy.Proxy):
query = {}
return res.commit(self, **query)
# ========== Server actions ==========
def get_server_action(self, server_action, server, ignore_missing=True):
"""Get a single server action
:param server_action: The value can be the ID of a server action or a
:class:`~openstack.compute.v2.server_action.ServerAction` instance.
:param server: This parameter need to be specified when ServerAction 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
action is associated with.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be raised when
the server action does not exist. When set to ``True``, no
exception will be set when attempting to retrieve a non-existent
server action.
:returns: One :class:`~openstack.compute.v2.server_action.ServerAction`
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
resource can be found.
"""
server_id = self._get_uri_attribute(server_action, server, 'server_id')
server_action = resource.Resource._get_id(server_action)
return self._get(
_server_action.ServerAction,
server_id=server_id,
action_id=server_action,
ignore_missing=ignore_missing,
)
def server_actions(self, server):
"""Return a generator of server actions
:param server: The server can be either the ID of a server or a
:class:`~openstack.compute.v2.server.Server`.
:returns: A generator of ServerAction objects
:rtype: :class:`~openstack.compute.v2.server_action.ServerAction`
"""
server_id = resource.Resource._get_id(server)
return self._list(_server_action.ServerAction, server_id=server_id)
# ========== Utilities ==========
def wait_for_server(

View File

@@ -0,0 +1,88 @@
# 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 resource
class ServerActionEvent(resource.Resource):
# Added the 'details' field in 2.84
_max_microversion = '2.84'
#: The name of the event
event = resource.Body('event')
#: The date and time when the event was started. The date and time stamp
#: format is ISO 8601
start_time = resource.Body('start_time')
#: The date and time when the event finished. The date and time stamp
#: format is ISO 8601
finish_time = resource.Body('finish_time')
#: The result of the event
result = resource.Body('result')
#: The traceback stack if an error occurred in this event.
#: This is only visible to cloud admins by default.
traceback = resource.Body('traceback')
#: The name of the host on which the event occurred.
#: This is only visible to cloud admins by default.
host = resource.Body('host')
#: An obfuscated hashed host ID string, or the empty string if there is no
#: host for the event. This is a hashed value so will not actually look
#: like a hostname, and is hashed with data from the project_id, so the
#: same physical host as seen by two different project_ids will be
#: different. This is useful when within the same project you need to
#: determine if two events occurred on the same or different physical
#: hosts.
host_id = resource.Body('hostId')
#: Details of the event. May be unset.
details = resource.Body('details')
class ServerAction(resource.Resource):
resource_key = 'instanceAction'
resources_key = 'instanceActions'
base_path = '/servers/{server_id}/os-instance-actions'
# capabilities
allow_fetch = True
allow_list = True
# Properties
#: The ID of the server that this action relates to.
server_id = resource.URI('server_id')
#: The name of the action.
action = resource.Body('action')
# 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 ID of the server that this action relates to.
# server_id = resource.Body('instance_uuid')
#: The ID of the request that this action related to.
request_id = resource.Body('request_id')
#: The ID of the user which initiated the server action.
user_id = resource.Body('user_id')
#: The ID of the project that this server belongs to.
project_id = resource.Body('project_id')
start_time = resource.Body('start_time')
#: The related error message for when an action fails.
message = resource.Body('message')
#: Events
events = resource.Body('events', type=list, list_type=ServerActionEvent)
# events.details field added in 2.84
_max_microversion = '2.84'
_query_mapping = resource.QueryParameters(
changes_since="changes-since",
changes_before="changes-before",
)

View File

@@ -23,6 +23,7 @@ from openstack.compute.v2 import keypair
from openstack.compute.v2 import migration
from openstack.compute.v2 import quota_set
from openstack.compute.v2 import server
from openstack.compute.v2 import server_action
from openstack.compute.v2 import server_group
from openstack.compute.v2 import server_interface
from openstack.compute.v2 import server_ip
@@ -1243,3 +1244,26 @@ class TestQuota(TestComputeProxy):
quota_set.QuotaSet,
'qs', a='b'
)
class TestServerAction(TestComputeProxy):
def test_server_action_get(self):
self._verify(
'openstack.proxy.Proxy._get',
self.proxy.get_server_action,
method_args=['action_id'],
method_kwargs={'server': 'server_id'},
expected_args=[server_action.ServerAction],
expected_kwargs={
'action_id': 'action_id', 'server_id': 'server_id',
},
)
def test_server_actions(self):
self.verify_list(
self.proxy.server_actions,
server_action.ServerAction,
method_kwargs={'server': 'server_a'},
expected_kwargs={'server_id': 'server_a'},
)

View File

@@ -0,0 +1,91 @@
# 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_action
from openstack.tests.unit import base
EXAMPLE = {
'action': 'stop',
'events': [
{
'event': 'compute_stop_instance',
'finish_time': '2018-04-25T01:26:36.790544',
'host': 'compute',
'hostId': '2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6', # noqa: E501
'result': 'Success',
'start_time': '2018-04-25T01:26:36.539271',
'traceback': None,
'details': None
}
],
'instance_uuid': '4bf3473b-d550-4b65-9409-292d44ab14a2',
'message': None,
'project_id': '6f70656e737461636b20342065766572',
'request_id': 'req-0d819d5c-1527-4669-bdf0-ffad31b5105b',
'start_time': '2018-04-25T01:26:36.341290',
'updated_at': '2018-04-25T01:26:36.790544',
'user_id': 'admin',
}
class TestServerAction(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_action.ServerAction()
self.assertEqual('instanceAction', sot.resource_key)
self.assertEqual('instanceActions', sot.resources_key)
self.assertEqual(
'/servers/{server_id}/os-instance-actions',
sot.base_path,
)
self.assertTrue(sot.allow_fetch)
self.assertTrue(sot.allow_list)
self.assertFalse(sot.allow_create)
self.assertFalse(sot.allow_commit)
self.assertFalse(sot.allow_delete)
self.assertDictEqual(
{
'changes_before': 'changes-before',
'changes_since': 'changes-since',
'limit': 'limit',
'marker': 'marker',
},
sot._query_mapping._mapping,
)
def test_make_it(self):
sot = server_action.ServerAction(**EXAMPLE)
self.assertEqual(EXAMPLE['action'], sot.action)
# FIXME: This isn't populated since it conflicts with the server_id URI
# argument
# self.assertEqual(EXAMPLE['instance_uuid'], sot.server_id)
self.assertEqual(EXAMPLE['message'], sot.message)
self.assertEqual(EXAMPLE['project_id'], sot.project_id)
self.assertEqual(EXAMPLE['request_id'], sot.request_id)
self.assertEqual(EXAMPLE['start_time'], sot.start_time)
self.assertEqual(EXAMPLE['user_id'], sot.user_id)
self.assertEqual(
[server_action.ServerActionEvent(**e) for e in EXAMPLE['events']],
sot.events,
)