Integrate Ironic client
Change-Id: If956902cf63517d62c600d54b12af6eb232f7dd1
This commit is contained in:
parent
99a7b0a219
commit
f42ddc9b17
@ -14,6 +14,7 @@ pbr==5.5.1
|
|||||||
podman==1.6.0
|
podman==1.6.0
|
||||||
python-glanceclient==3.2.2
|
python-glanceclient==3.2.2
|
||||||
python-heatclient==2.3.0
|
python-heatclient==2.3.0
|
||||||
|
python-ironicclient==4.6.1
|
||||||
python-neutronclient==7.2.1
|
python-neutronclient==7.2.1
|
||||||
python-novaclient==17.2.1
|
python-novaclient==17.2.1
|
||||||
python-octaviaclient==2.2.0
|
python-octaviaclient==2.2.0
|
||||||
|
@ -13,6 +13,7 @@ paramiko>=2.7.2 # LGPLv2.1
|
|||||||
pbr>=5.5.1 # Apache-2.0
|
pbr>=5.5.1 # Apache-2.0
|
||||||
python-glanceclient>=3.2.2 # Apache-2.0
|
python-glanceclient>=3.2.2 # Apache-2.0
|
||||||
python-heatclient>=2.3.0 # 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-neutronclient>=7.2.1 # Apache-2.0
|
||||||
python-novaclient>=17.2.1 # Apache-2.0
|
python-novaclient>=17.2.1 # Apache-2.0
|
||||||
python-octaviaclient>=2.2.0 # Apache-2.0
|
python-octaviaclient>=2.2.0 # Apache-2.0
|
||||||
|
23
tobiko/openstack/ironic/__init__.py
Normal file
23
tobiko/openstack/ironic/__init__.py
Normal 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
|
74
tobiko/openstack/ironic/_client.py
Normal file
74
tobiko/openstack/ironic/_client.py
Normal 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
|
151
tobiko/openstack/ironic/_node.py
Normal file
151
tobiko/openstack/ironic/_node.py
Normal 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)
|
@ -45,6 +45,7 @@ migrate_server = _client.migrate_server
|
|||||||
confirm_resize = _client.confirm_resize
|
confirm_resize = _client.confirm_resize
|
||||||
reboot_server = _client.reboot_server
|
reboot_server = _client.reboot_server
|
||||||
NovaServer = _client.NovaServer
|
NovaServer = _client.NovaServer
|
||||||
|
ServerType = _client.ServerType
|
||||||
|
|
||||||
WaitForCloudInitTimeoutError = _cloud_init.WaitForCloudInitTimeoutError
|
WaitForCloudInitTimeoutError = _cloud_init.WaitForCloudInitTimeoutError
|
||||||
cloud_config = _cloud_init.cloud_config
|
cloud_config = _cloud_init.cloud_config
|
||||||
|
Loading…
Reference in New Issue
Block a user