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:
@@ -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(
|
||||
|
||||
88
openstack/compute/v2/server_action.py
Normal file
88
openstack/compute/v2/server_action.py
Normal 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",
|
||||
)
|
||||
@@ -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'},
|
||||
)
|
||||
|
||||
91
openstack/tests/unit/compute/v2/test_server_actions.py
Normal file
91
openstack/tests/unit/compute/v2/test_server_actions.py
Normal 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,
|
||||
)
|
||||
Reference in New Issue
Block a user