Add support for the runbooks feature

Adds support for managing runbooks within the OpenStack SDK.

Related-Change: #922142
Change-Id: Ia590918c2e4bd629724c2e50146a904099858477
This commit is contained in:
cid
2024-08-15 15:11:29 +01:00
parent b65b7d4d3e
commit 604d29bc9b
6 changed files with 203 additions and 3 deletions

View File

@@ -94,6 +94,10 @@ CHANGE_BOOT_MODE_VERSION = '1.76'
FIRMWARE_VERSION = '1.86'
"""API version in which firmware components of a node can be accessed"""
RUNBOOKS_VERSION = '1.92'
"""API version in which a runbook can be used in place of arbitrary steps
for provisioning"""
class Resource(resource.Resource):
base_path: str

View File

@@ -100,8 +100,8 @@ class Node(_common.Resource):
is_maintenance='maintenance',
)
# Ability to have a firmware_interface on a node.
_max_microversion = '1.87'
# Ability to run predefined sets of steps on a node using runbooks.
_max_microversion = '1.92'
# Properties
#: The UUID of the allocation associated with this node. Added in API
@@ -207,9 +207,13 @@ class Node(_common.Resource):
#: A string to be used by external schedulers to identify this node as a
#: unit of a specific type of resource. Added in API microversion 1.21.
resource_class = resource.Body("resource_class")
#: A string represents the current service step being executed upon.
#: A string representing the current service step being executed upon.
#: Added in API microversion 1.87.
service_step = resource.Body("service_step")
#: A string representing the uuid or logical name of a runbook as an
#: alternative to providing ``clean_steps`` or ``service_steps``.
#: Added in API microversion 1.92.
runbook = resource.Body("runbook")
#: A string indicating the shard this node belongs to. Added in API
#: microversion 1,82.
shard = resource.Body("shard")
@@ -407,6 +411,7 @@ class Node(_common.Resource):
timeout=None,
deploy_steps=None,
service_steps=None,
runbook=None,
):
"""Run an action modifying this node's provision state.
@@ -431,6 +436,7 @@ class Node(_common.Resource):
and ``rebuild`` target.
:param service_steps: Service steps to execute, only valid for
``service`` target.
:param ``runbook``: UUID or logical name of a runbook.
:return: This :class:`Node` instance.
:raises: ValueError if ``config_drive``, ``clean_steps``,
@@ -460,6 +466,31 @@ class Node(_common.Resource):
version = self._assert_microversion_for(session, 'commit', version)
body = {'target': target}
if runbook:
version = self._assert_microversion_for(
session, 'commit', _common.RUNBOOKS_VERSION
)
if clean_steps is not None:
raise ValueError(
'Please provide either clean steps or a '
'runbook, but not both.'
)
if service_steps is not None:
raise ValueError(
'Please provide either service steps or a '
'runbook, but not both.'
)
if target != 'clean' and target != 'service':
msg = (
'A runbook can only be provided when setting target '
'provision state to any of "[clean, service]"'
)
raise ValueError(msg)
body['runbook'] = runbook
if config_drive:
if target not in ('active', 'rebuild'):
raise ValueError(

View File

@@ -0,0 +1,54 @@
# 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.baremetal.v1 import _common
from openstack import resource
class Runbook(_common.Resource):
resources_key = 'runbooks'
base_path = '/runbooks'
# capabilities
allow_create = True
allow_fetch = True
allow_commit = True
allow_delete = True
allow_list = True
allow_patch = True
commit_method = 'PATCH'
commit_jsonpatch = True
_query_mapping = resource.QueryParameters(
'detail',
fields={'type': _common.fields_type},
)
# Runbooks is available since 1.92
_max_microversion = '1.92'
name = resource.Body('name')
#: Timestamp at which the runbook was created.
created_at = resource.Body('created_at')
#: A set of one or more arbitrary metadata key and value pairs.
extra = resource.Body('extra')
#: A list of relative links. Includes the self and bookmark links.
links = resource.Body('links', type=list)
#: A set of physical information of the runbook.
steps = resource.Body('steps', type=list)
#: Indicates whether the runbook is publicly accessible.
public = resource.Body('public', type=bool)
#: The name or ID of the project that owns the runbook.
owner = resource.Body('owner', type=str)
#: Timestamp at which the runbook was last updated.
updated_at = resource.Body('updated_at')
#: The UUID of the resource.
id = resource.Body('uuid', alternate_id=True)

View File

@@ -83,6 +83,7 @@ FAKE = {
"service_step": {},
"secure_boot": True,
"shard": "TestShard",
"runbook": None,
"states": [
{
"href": "http://127.0.0.1:6385/v1/nodes/<NODE_ID>/states",
@@ -161,6 +162,7 @@ class TestNode(base.TestCase):
self.assertEqual(FAKE['resource_class'], sot.resource_class)
self.assertEqual(FAKE['service_step'], sot.service_step)
self.assertEqual(FAKE['secure_boot'], sot.is_secure_boot)
self.assertEqual(FAKE['runbook'], sot.runbook)
self.assertEqual(FAKE['states'], sot.states)
self.assertEqual(
FAKE['target_provision_state'], sot.target_provision_state
@@ -438,6 +440,36 @@ class TestNodeSetProvisionState(base.TestCase):
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
def test_set_provision_state_clean_runbook(self):
runbook = 'CUSTOM_AWESOME'
result = self.node.set_provision_state(
self.session, 'clean', runbook=runbook
)
self.assertIs(result, self.node)
self.session.put.assert_called_once_with(
'nodes/%s/states/provision' % self.node.id,
json={'target': 'clean', 'runbook': runbook},
headers=mock.ANY,
microversion='1.92',
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
def test_set_provision_state_service_runbook(self):
runbook = 'CUSTOM_AWESOME'
result = self.node.set_provision_state(
self.session, 'service', runbook=runbook
)
self.assertIs(result, self.node)
self.session.put.assert_called_once_with(
'nodes/%s/states/provision' % self.node.id,
json={'target': 'service', 'runbook': runbook},
headers=mock.ANY,
microversion='1.92',
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
@mock.patch.object(node.Node, '_translate_response', mock.Mock())
@mock.patch.object(node.Node, '_get_session', lambda self, x: x)

View File

@@ -0,0 +1,73 @@
# 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.baremetal.v1 import runbooks
from openstack.tests.unit import base
FAKE = {
"created_at": "2024-08-18T22:28:48.643434+11:11",
"extra": {},
"links": [
{
"href": """http://10.60.253.180:6385/v1/runbooks
/bbb45f41-d4bc-4307-8d1d-32f95ce1e920""",
"rel": "self",
},
{
"href": """http://10.60.253.180:6385/runbooks
/bbb45f41-d4bc-4307-8d1d-32f95ce1e920""",
"rel": "bookmark",
},
],
"name": "CUSTOM_AWESOME",
"public": False,
"owner": "blah",
"steps": [
{
"args": {
"settings": [{"name": "LogicalProc", "value": "Enabled"}]
},
"interface": "bios",
"order": 1,
"step": "apply_configuration",
}
],
"updated_at": None,
"uuid": "32f95ce1-4307-d4bc-8d1d-e920bbb45f41",
}
class Runbooks(base.TestCase):
def test_basic(self):
sot = runbooks.Runbook()
self.assertIsNone(sot.resource_key)
self.assertEqual('runbooks', sot.resources_key)
self.assertEqual('/runbooks', sot.base_path)
self.assertTrue(sot.allow_create)
self.assertTrue(sot.allow_fetch)
self.assertTrue(sot.allow_commit)
self.assertTrue(sot.allow_delete)
self.assertTrue(sot.allow_list)
self.assertEqual('PATCH', sot.commit_method)
def test_instantiate(self):
sot = runbooks.Runbook(**FAKE)
self.assertEqual(FAKE['steps'], sot.steps)
self.assertEqual(FAKE['created_at'], sot.created_at)
self.assertEqual(FAKE['extra'], sot.extra)
self.assertEqual(FAKE['links'], sot.links)
self.assertEqual(FAKE['name'], sot.name)
self.assertEqual(FAKE['public'], sot.public)
self.assertEqual(FAKE['owner'], sot.owner)
self.assertEqual(FAKE['updated_at'], sot.updated_at)
self.assertEqual(FAKE['uuid'], sot.id)

View File

@@ -0,0 +1,6 @@
---
features:
- |
Adds support for runbooks; an API feature that enables project members
to self-serve maintenance tasks via predefined step lists in lieu of
an arbitrary list of clean/service steps.