Integrate Ironic client

Change-Id: If956902cf63517d62c600d54b12af6eb232f7dd1
This commit is contained in:
Federico Ressi 2021-04-14 11:51:23 +02:00
parent 99a7b0a219
commit f42ddc9b17
6 changed files with 251 additions and 0 deletions

View File

@ -14,6 +14,7 @@ pbr==5.5.1
podman==1.6.0
python-glanceclient==3.2.2
python-heatclient==2.3.0
python-ironicclient==4.6.1
python-neutronclient==7.2.1
python-novaclient==17.2.1
python-octaviaclient==2.2.0

View File

@ -13,6 +13,7 @@ paramiko>=2.7.2 # LGPLv2.1
pbr>=5.5.1 # Apache-2.0
python-glanceclient>=3.2.2 # Apache-2.0
python-heatclient>=2.3.0 # Apache-2.0
python-ironicclient>=4.6.1 # Apache-2.0
python-neutronclient>=7.2.1 # Apache-2.0
python-novaclient>=17.2.1 # Apache-2.0
python-octaviaclient>=2.2.0 # Apache-2.0

View File

@ -0,0 +1,23 @@
# Copyright 2021 Red Hat
#
# 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 __future__ import absolute_import
from tobiko.openstack.ironic import _client
from tobiko.openstack.ironic import _node
get_ironic_client = _client.get_ironic_client
power_off_node = _node.power_off_node
power_on_node = _node.power_on_node
IronicNodeType = _node.IronicNodeType

View File

@ -0,0 +1,74 @@
# Copyright 2021 Red Hat
#
# 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 __future__ import absolute_import
import typing
import ironicclient
import ironicclient.v1.client
from oslo_log import log
import tobiko
from tobiko.openstack import _client
LOG = log.getLogger(__name__)
CLIENT_CLASSES = (ironicclient.v1.client.Client,)
IronicClient = typing.Union[ironicclient.v1.client.Client]
class IronicClientFixture(_client.OpenstackClientFixture):
def init_client(self, session) -> IronicClient:
return ironicclient.client.get_client(1, session=session)
class IronicClientManager(_client.OpenstackClientManager):
def create_client(self, session) -> IronicClientFixture:
return IronicClientFixture(session=session)
CLIENTS = IronicClientManager()
IronicClientType = typing.Union[IronicClient,
IronicClientFixture,
typing.Type[IronicClientFixture],
None]
def ironic_client(obj: IronicClientType) -> IronicClient:
if obj is None:
return get_ironic_client()
if isinstance(obj, CLIENT_CLASSES):
return obj
fixture = tobiko.setup_fixture(obj)
if isinstance(fixture, IronicClientFixture):
assert fixture.client is not None
return fixture.client
message = f"Object '{obj}' is not an IronicClientFixture instance"
raise TypeError(message)
def get_ironic_client(session=None, shared=True, init_client=None,
manager=None) -> IronicClient:
manager = manager or CLIENTS
client = manager.get_client(session=session, shared=shared,
init_client=init_client)
tobiko.setup_fixture(client)
return client.client

View File

@ -0,0 +1,151 @@
# Copyright 2021 Red Hat
#
# 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 __future__ import absolute_import
import typing
import ironicclient.v1.client
from oslo_log import log
import tobiko
from tobiko.openstack.ironic import _client
LOG = log.getLogger(__name__)
IronicNode = typing.Union[ironicclient.v1.node.Node]
IronicNodeType = typing.Union[str, IronicNode]
def get_node_id(node: typing.Optional[IronicNodeType] = None,
node_id: typing.Optional[str] = None) -> str:
if node_id is None:
if isinstance(node, str):
node_id = node
else:
assert node is not None
node_id = node.uuid
return node_id
def get_node(node: typing.Optional[IronicNodeType] = None,
node_id: typing.Optional[str] = None,
client: _client.IronicClientType = None,
**params) -> IronicNode:
node_id = get_node_id(node=node, node_id=node_id)
return _client.ironic_client(client).node.get(node_id, **params)
class WaitForNodePowerStateError(tobiko.TobikoException):
message = ("Node {node_id} not changing power state from "
"{node_power_state} to {power_state}")
class WaitForNodePowerStateTimeout(WaitForNodePowerStateError):
message = ("Node {node_id} didn't change its state from "
"{node_power_state} to {power_state} state after "
"{timeout} seconds")
IRONIC_NODE_TRANSIENT_POWER_STATES: typing.Dict[str, typing.List[str]] = {
'power on': ['power off'],
'power off': ['power on'],
}
def wait_for_node_power_state(
node: IronicNodeType,
power_state: str,
client: _client.IronicClientType = None,
timeout: tobiko.Seconds = None,
sleep_time: tobiko.Seconds = None,
transient_status: typing.Optional[typing.List[str]] = None) -> \
IronicNode:
if transient_status is None:
transient_status = IRONIC_NODE_TRANSIENT_POWER_STATES.get(
power_state) or []
node_id = get_node_id(node)
for attempt in tobiko.retry(timeout=timeout,
interval=sleep_time,
default_timeout=300.,
default_interval=5.):
_node = get_node(node_id=node_id, client=client)
if _node.power_state == power_state:
break
if _node.power_state not in transient_status:
raise WaitForNodePowerStateError(
node_id=node_id,
node_power_state=_node.power_state,
power_state=power_state)
try:
attempt.check_time_left()
except tobiko.RetryTimeLimitError as ex:
raise WaitForNodePowerStateTimeout(
node_id=node_id,
node_power_state=_node.power_state,
power_state=power_state,
timeout=timeout) from ex
LOG.debug(f"Waiting for baremetal node {node_id} power state to get "
f"from {_node.power_state} to {power_state}...")
else:
raise RuntimeError("Retry look break before timing out")
return _node
def power_off_node(node: IronicNodeType,
soft=False,
client: _client.IronicClientType = None,
timeout: tobiko.Seconds = None,
sleep_time: tobiko.Seconds = None) \
-> IronicNode:
client = _client.ironic_client(client)
node = get_node(node=node, client=client)
if node.power_state == 'power off':
return node
LOG.info(f"Power off baremetal node '{node.uuid}' "
f"(power state = '{node.power_state}').")
client.node.set_power_state(node.uuid,
state='off',
soft=soft,
timeout=timeout)
return wait_for_node_power_state(node=node.uuid,
power_state='power off',
client=client,
timeout=timeout,
sleep_time=sleep_time)
def power_on_node(node: IronicNodeType,
client: _client.IronicClientType = None,
timeout: tobiko.Seconds = None,
sleep_time: tobiko.Seconds = None) -> \
IronicNode:
client = _client.ironic_client(client)
node = get_node(node=node, client=client)
if node.power_state == 'power on':
return node
LOG.info(f"Power on baremetal node '{node.uuid}' "
f"(power_state='{node.power_state}').")
client.node.set_power_state(node_id=node.uuid, state='on')
return wait_for_node_power_state(node=node.uuid,
power_state='power on',
client=client,
timeout=timeout,
sleep_time=sleep_time)

View File

@ -45,6 +45,7 @@ migrate_server = _client.migrate_server
confirm_resize = _client.confirm_resize
reboot_server = _client.reboot_server
NovaServer = _client.NovaServer
ServerType = _client.ServerType
WaitForCloudInitTimeoutError = _cloud_init.WaitForCloudInitTimeoutError
cloud_config = _cloud_init.cloud_config