Blackify openstack.baremetal, openstack.baremetal_introspection

Black used with the '-l 79 -S' flags.

A future change will ignore this commit in git-blame history by adding a
'git-blame-ignore-revs' file.

Change-Id: I1effcaff4f4c931b46541f8db44ed50c10104cad
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2023-05-05 10:55:42 +01:00
parent 4589e293e8
commit f8e42017e7
36 changed files with 1817 additions and 1180 deletions

View File

@ -23,8 +23,13 @@ import tempfile
@contextlib.contextmanager
def populate_directory(metadata, user_data=None, versions=None,
network_data=None, vendor_data=None):
def populate_directory(
metadata,
user_data=None,
versions=None,
network_data=None,
vendor_data=None,
):
"""Populate a directory with configdrive files.
:param dict metadata: Metadata.
@ -46,21 +51,24 @@ def populate_directory(metadata, user_data=None, versions=None,
json.dump(metadata, fp)
if network_data:
with open(os.path.join(subdir, 'network_data.json'),
'w') as fp:
with open(
os.path.join(subdir, 'network_data.json'), 'w'
) as fp:
json.dump(network_data, fp)
if vendor_data:
with open(os.path.join(subdir, 'vendor_data2.json'),
'w') as fp:
with open(
os.path.join(subdir, 'vendor_data2.json'), 'w'
) as fp:
json.dump(vendor_data, fp)
if user_data:
# Strictly speaking, user data is binary, but in many cases
# it's actually a text (cloud-init, ignition, etc).
flag = 't' if isinstance(user_data, str) else 'b'
with open(os.path.join(subdir, 'user_data'),
'w%s' % flag) as fp:
with open(
os.path.join(subdir, 'user_data'), 'w%s' % flag
) as fp:
fp.write(user_data)
yield d
@ -68,8 +76,13 @@ def populate_directory(metadata, user_data=None, versions=None,
shutil.rmtree(d)
def build(metadata, user_data=None, versions=None, network_data=None,
vendor_data=None):
def build(
metadata,
user_data=None,
versions=None,
network_data=None,
vendor_data=None,
):
"""Make a configdrive compatible with the Bare Metal service.
Requires the genisoimage utility to be available.
@ -81,8 +94,9 @@ def build(metadata, user_data=None, versions=None, network_data=None,
:param dict vendor_data: Extra supplied vendor data.
:return: configdrive contents as a base64-encoded string.
"""
with populate_directory(metadata, user_data, versions,
network_data, vendor_data) as path:
with populate_directory(
metadata, user_data, versions, network_data, vendor_data
) as path:
return pack(path)
@ -100,16 +114,27 @@ def pack(path):
cmds = ['genisoimage', 'mkisofs', 'xorrisofs']
for c in cmds:
try:
p = subprocess.Popen([c,
'-o', tmpfile.name,
'-ldots', '-allow-lowercase',
'-allow-multidot', '-l',
'-publisher', 'metalsmith',
'-quiet', '-J',
'-r', '-V', 'config-2',
path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
p = subprocess.Popen(
[
c,
'-o',
tmpfile.name,
'-ldots',
'-allow-lowercase',
'-allow-multidot',
'-l',
'-publisher',
'metalsmith',
'-quiet',
'-J',
'-r',
'-V',
'config-2',
path,
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except OSError as e:
error = e
else:
@ -120,14 +145,16 @@ def pack(path):
raise RuntimeError(
'Error generating the configdrive. Make sure the '
'"genisoimage", "mkisofs" or "xorrisofs" tool is installed. '
'Error: %s' % error)
'Error: %s' % error
)
stdout, stderr = p.communicate()
if p.returncode != 0:
raise RuntimeError(
'Error generating the configdrive.'
'Stdout: "%(stdout)s". Stderr: "%(stderr)s"' %
{'stdout': stdout, 'stderr': stderr})
'Stdout: "%(stdout)s". Stderr: "%(stderr)s"'
% {'stdout': stdout, 'stderr': stderr}
)
tmpfile.seek(0)

View File

@ -17,7 +17,7 @@ RETRIABLE_STATUS_CODES = [
# HTTP Conflict - happens if a node is locked
409,
# HTTP Service Unavailable happens if there's no free conductor
503
503,
]
"""HTTP status codes that should be retried."""
@ -88,7 +88,6 @@ CHANGE_BOOT_MODE_VERSION = '1.76'
class ListMixin:
@classmethod
def list(cls, session, details=False, **params):
"""This method is a generator which yields resource objects.
@ -112,8 +111,9 @@ class ListMixin:
base_path = cls.base_path
if details:
base_path += '/detail'
return super(ListMixin, cls).list(session, paginated=True,
base_path=base_path, **params)
return super(ListMixin, cls).list(
session, paginated=True, base_path=base_path, **params
)
def comma_separated_list(value):

View File

@ -63,8 +63,10 @@ class Proxy(proxy.Proxy):
return res.fetch(
self,
error_message="No {resource_type} found for {value}".format(
resource_type=resource_type.__name__, value=value),
**kwargs)
resource_type=resource_type.__name__, value=value
),
**kwargs
)
def chassis(self, details=False, **query):
"""Retrieve a generator of chassis.
@ -123,8 +125,9 @@ class Proxy(proxy.Proxy):
:returns: One :class:`~openstack.baremetal.v1.chassis.Chassis` object
or None.
"""
return self._find(_chassis.Chassis, name_or_id,
ignore_missing=ignore_missing)
return self._find(
_chassis.Chassis, name_or_id, ignore_missing=ignore_missing
)
def get_chassis(self, chassis, fields=None):
"""Get a specific chassis.
@ -178,8 +181,9 @@ class Proxy(proxy.Proxy):
:returns: The instance of the chassis which was deleted.
:rtype: :class:`~openstack.baremetal.v1.chassis.Chassis`.
"""
return self._delete(_chassis.Chassis, chassis,
ignore_missing=ignore_missing)
return self._delete(
_chassis.Chassis, chassis, ignore_missing=ignore_missing
)
def drivers(self, details=False, **query):
"""Retrieve a generator of drivers.
@ -221,8 +225,9 @@ class Proxy(proxy.Proxy):
driver = self.get_driver(driver)
return driver.list_vendor_passthru(self)
def call_driver_vendor_passthru(self, driver,
verb: str, method: str, body=None):
def call_driver_vendor_passthru(
self, driver, verb: str, method: str, body=None
):
"""Call driver's vendor_passthru method.
:param driver: The value can be the name of a driver or a
@ -311,8 +316,9 @@ class Proxy(proxy.Proxy):
:returns: One :class:`~openstack.baremetal.v1.node.Node` object
or None.
"""
return self._find(_node.Node, name_or_id,
ignore_missing=ignore_missing)
return self._find(
_node.Node, name_or_id, ignore_missing=ignore_missing
)
def get_node(self, node, fields=None):
"""Get a specific node.
@ -345,8 +351,9 @@ class Proxy(proxy.Proxy):
res = self._get_resource(_node.Node, node, **attrs)
return res.commit(self, retry_on_conflict=retry_on_conflict)
def patch_node(self, node, patch, reset_interfaces=None,
retry_on_conflict=True):
def patch_node(
self, node, patch, reset_interfaces=None, retry_on_conflict=True
):
"""Apply a JSON patch to the node.
:param node: The value can be the name or ID of a node or a
@ -368,12 +375,24 @@ class Proxy(proxy.Proxy):
:rtype: :class:`~openstack.baremetal.v1.node.Node`
"""
res = self._get_resource(_node.Node, node)
return res.patch(self, patch, retry_on_conflict=retry_on_conflict,
reset_interfaces=reset_interfaces)
return res.patch(
self,
patch,
retry_on_conflict=retry_on_conflict,
reset_interfaces=reset_interfaces,
)
def set_node_provision_state(self, node, target, config_drive=None,
clean_steps=None, rescue_password=None,
wait=False, timeout=None, deploy_steps=None):
def set_node_provision_state(
self,
node,
target,
config_drive=None,
clean_steps=None,
rescue_password=None,
wait=False,
timeout=None,
deploy_steps=None,
):
"""Run an action modifying node's provision state.
This call is asynchronous, it will return success as soon as the Bare
@ -405,11 +424,16 @@ class Proxy(proxy.Proxy):
invalid ``target``.
"""
res = self._get_resource(_node.Node, node)
return res.set_provision_state(self, target, config_drive=config_drive,
clean_steps=clean_steps,
rescue_password=rescue_password,
wait=wait, timeout=timeout,
deploy_steps=deploy_steps)
return res.set_provision_state(
self,
target,
config_drive=config_drive,
clean_steps=clean_steps,
rescue_password=rescue_password,
wait=wait,
timeout=timeout,
deploy_steps=deploy_steps,
)
def get_node_boot_device(self, node):
"""Get node boot device
@ -480,10 +504,14 @@ class Proxy(proxy.Proxy):
res = self._get_resource(_node.Node, node)
res.inject_nmi(self)
def wait_for_nodes_provision_state(self, nodes, expected_state,
timeout=None,
abort_on_failed_state=True,
fail=True):
def wait_for_nodes_provision_state(
self,
nodes,
expected_state,
timeout=None,
abort_on_failed_state=True,
fail=True,
):
"""Wait for the nodes to reach the expected state.
:param nodes: List of nodes - name, ID or
@ -507,24 +535,27 @@ class Proxy(proxy.Proxy):
reaches an error state and ``abort_on_failed_state`` is ``True``.
:raises: :class:`~openstack.exceptions.ResourceTimeout` on timeout.
"""
log_nodes = ', '.join(n.id if isinstance(n, _node.Node) else n
for n in nodes)
log_nodes = ', '.join(
n.id if isinstance(n, _node.Node) else n for n in nodes
)
finished = []
failed = []
remaining = nodes
try:
for count in utils.iterate_timeout(
timeout,
"Timeout waiting for nodes %(nodes)s to reach "
"target state '%(state)s'" % {'nodes': log_nodes,
'state': expected_state}):
timeout,
"Timeout waiting for nodes %(nodes)s to reach "
"target state '%(state)s'"
% {'nodes': log_nodes, 'state': expected_state},
):
nodes = [self.get_node(n) for n in remaining]
remaining = []
for n in nodes:
try:
if n._check_state_reached(self, expected_state,
abort_on_failed_state):
if n._check_state_reached(
self, expected_state, abort_on_failed_state
):
finished.append(n)
else:
remaining.append(n)
@ -543,8 +574,11 @@ class Proxy(proxy.Proxy):
self.log.debug(
'Still waiting for nodes %(nodes)s to reach state '
'"%(target)s"',
{'nodes': ', '.join(n.id for n in remaining),
'target': expected_state})
{
'nodes': ', '.join(n.id for n in remaining),
'target': expected_state,
},
)
except exceptions.ResourceTimeout:
if fail:
raise
@ -568,7 +602,8 @@ class Proxy(proxy.Proxy):
``None`` (the default) means no client-side timeout.
"""
self._get_resource(_node.Node, node).set_power_state(
self, target, wait=wait, timeout=timeout)
self, target, wait=wait, timeout=timeout
)
def wait_for_node_power_state(self, node, expected_state, timeout=None):
"""Wait for the node to reach the power state.
@ -731,8 +766,9 @@ class Proxy(proxy.Proxy):
:returns: One :class:`~openstack.baremetal.v1.port.Port` object
or None.
"""
return self._find(_port.Port, name_or_id,
ignore_missing=ignore_missing)
return self._find(
_port.Port, name_or_id, ignore_missing=ignore_missing
)
def get_port(self, port, fields=None):
"""Get a specific port.
@ -849,8 +885,9 @@ class Proxy(proxy.Proxy):
:returns: One :class:`~openstack.baremetal.v1.port_group.PortGroup`
object or None.
"""
return self._find(_portgroup.PortGroup, name_or_id,
ignore_missing=ignore_missing)
return self._find(
_portgroup.PortGroup, name_or_id, ignore_missing=ignore_missing
)
def get_port_group(self, port_group, fields=None):
"""Get a specific port group.
@ -863,8 +900,9 @@ class Proxy(proxy.Proxy):
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
port group matching the name or ID could be found.
"""
return self._get_with_fields(_portgroup.PortGroup, port_group,
fields=fields)
return self._get_with_fields(
_portgroup.PortGroup, port_group, fields=fields
)
def update_port_group(self, port_group, **attrs):
"""Update a port group.
@ -909,8 +947,9 @@ class Proxy(proxy.Proxy):
:returns: The instance of the port group which was deleted.
:rtype: :class:`~openstack.baremetal.v1.port_group.PortGroup`.
"""
return self._delete(_portgroup.PortGroup, port_group,
ignore_missing=ignore_missing)
return self._delete(
_portgroup.PortGroup, port_group, ignore_missing=ignore_missing
)
def attach_vif_to_node(self, node, vif_id, retry_on_conflict=True):
"""Attach a VIF to the node.
@ -1026,8 +1065,9 @@ class Proxy(proxy.Proxy):
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
allocation matching the name or ID could be found.
"""
return self._get_with_fields(_allocation.Allocation, allocation,
fields=fields)
return self._get_with_fields(
_allocation.Allocation, allocation, fields=fields
)
def update_allocation(self, allocation, **attrs):
"""Update an allocation.
@ -1052,8 +1092,9 @@ class Proxy(proxy.Proxy):
:returns: The updated allocation.
:rtype: :class:`~openstack.baremetal.v1.allocation.Allocation`
"""
return self._get_resource(_allocation.Allocation,
allocation).patch(self, patch)
return self._get_resource(_allocation.Allocation, allocation).patch(
self, patch
)
def delete_allocation(self, allocation, ignore_missing=True):
"""Delete an allocation.
@ -1069,11 +1110,13 @@ class Proxy(proxy.Proxy):
:returns: The instance of the allocation which was deleted.
:rtype: :class:`~openstack.baremetal.v1.allocation.Allocation`.
"""
return self._delete(_allocation.Allocation, allocation,
ignore_missing=ignore_missing)
return self._delete(
_allocation.Allocation, allocation, ignore_missing=ignore_missing
)
def wait_for_allocation(self, allocation, timeout=None,
ignore_error=False):
def wait_for_allocation(
self, allocation, timeout=None, ignore_error=False
):
"""Wait for the allocation to become active.
:param allocation: The value can be the name or ID of an allocation or
@ -1252,8 +1295,11 @@ class Proxy(proxy.Proxy):
:class:`~openstack.baremetal.v1.volumeconnector.VolumeConnector`
object or None.
"""
return self._find(_volumeconnector.VolumeConnector, vc_id,
ignore_missing=ignore_missing)
return self._find(
_volumeconnector.VolumeConnector,
vc_id,
ignore_missing=ignore_missing,
)
def get_volume_connector(self, volume_connector, fields=None):
"""Get a specific volume_connector.
@ -1269,9 +1315,9 @@ class Proxy(proxy.Proxy):
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
volume_connector matching the name or ID could be found.`
"""
return self._get_with_fields(_volumeconnector.VolumeConnector,
volume_connector,
fields=fields)
return self._get_with_fields(
_volumeconnector.VolumeConnector, volume_connector, fields=fields
)
def update_volume_connector(self, volume_connector, **attrs):
"""Update a volume_connector.
@ -1287,8 +1333,9 @@ class Proxy(proxy.Proxy):
:rtype:
:class:`~openstack.baremetal.v1.volume_connector.VolumeConnector`
"""
return self._update(_volumeconnector.VolumeConnector,
volume_connector, **attrs)
return self._update(
_volumeconnector.VolumeConnector, volume_connector, **attrs
)
def patch_volume_connector(self, volume_connector, patch):
"""Apply a JSON patch to the volume_connector.
@ -1303,11 +1350,11 @@ class Proxy(proxy.Proxy):
:rtype:
:class:`~openstack.baremetal.v1.volume_connector.VolumeConnector.`
"""
return self._get_resource(_volumeconnector.VolumeConnector,
volume_connector).patch(self, patch)
return self._get_resource(
_volumeconnector.VolumeConnector, volume_connector
).patch(self, patch)
def delete_volume_connector(self, volume_connector,
ignore_missing=True):
def delete_volume_connector(self, volume_connector, ignore_missing=True):
"""Delete an volume_connector.
:param volume_connector: The value can be either the ID of a
@ -1324,8 +1371,11 @@ class Proxy(proxy.Proxy):
:rtype:
:class:`~openstack.baremetal.v1.volume_connector.VolumeConnector`.
"""
return self._delete(_volumeconnector.VolumeConnector,
volume_connector, ignore_missing=ignore_missing)
return self._delete(
_volumeconnector.VolumeConnector,
volume_connector,
ignore_missing=ignore_missing,
)
def volume_targets(self, details=False, **query):
"""Retrieve a generator of volume_target.
@ -1392,8 +1442,9 @@ class Proxy(proxy.Proxy):
:class:`~openstack.baremetal.v1.volumetarget.VolumeTarget`
object or None.
"""
return self._find(_volumetarget.VolumeTarget, vt_id,
ignore_missing=ignore_missing)
return self._find(
_volumetarget.VolumeTarget, vt_id, ignore_missing=ignore_missing
)
def get_volume_target(self, volume_target, fields=None):
"""Get a specific volume_target.
@ -1409,9 +1460,9 @@ class Proxy(proxy.Proxy):
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
volume_target matching the name or ID could be found.`
"""
return self._get_with_fields(_volumetarget.VolumeTarget,
volume_target,
fields=fields)
return self._get_with_fields(
_volumetarget.VolumeTarget, volume_target, fields=fields
)
def update_volume_target(self, volume_target, **attrs):
"""Update a volume_target.
@ -1426,8 +1477,7 @@ class Proxy(proxy.Proxy):
:rtype:
:class:`~openstack.baremetal.v1.volume_target.VolumeTarget`
"""
return self._update(_volumetarget.VolumeTarget,
volume_target, **attrs)
return self._update(_volumetarget.VolumeTarget, volume_target, **attrs)
def patch_volume_target(self, volume_target, patch):
"""Apply a JSON patch to the volume_target.
@ -1442,11 +1492,11 @@ class Proxy(proxy.Proxy):
:rtype:
:class:`~openstack.baremetal.v1.volume_target.VolumeTarget.`
"""
return self._get_resource(_volumetarget.VolumeTarget,
volume_target).patch(self, patch)
return self._get_resource(
_volumetarget.VolumeTarget, volume_target
).patch(self, patch)
def delete_volume_target(self, volume_target,
ignore_missing=True):
def delete_volume_target(self, volume_target, ignore_missing=True):
"""Delete an volume_target.
:param volume_target: The value can be either the ID of a
@ -1463,8 +1513,11 @@ class Proxy(proxy.Proxy):
:rtype:
:class:`~openstack.baremetal.v1.volume_target.VolumeTarget`.
"""
return self._delete(_volumetarget.VolumeTarget,
volume_target, ignore_missing=ignore_missing)
return self._delete(
_volumetarget.VolumeTarget,
volume_target,
ignore_missing=ignore_missing,
)
def deploy_templates(self, details=False, **query):
"""Retrieve a generator of deploy_templates.
@ -1506,11 +1559,11 @@ class Proxy(proxy.Proxy):
:rtype:
:class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`
"""
return self._update(_deploytemplates.DeployTemplate,
deploy_template, **attrs)
return self._update(
_deploytemplates.DeployTemplate, deploy_template, **attrs
)
def delete_deploy_template(self, deploy_template,
ignore_missing=True):
def delete_deploy_template(self, deploy_template, ignore_missing=True):
"""Delete a deploy_template.
:param deploy_template:The value can be
@ -1532,8 +1585,11 @@ class Proxy(proxy.Proxy):
:class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`.
"""
return self._delete(_deploytemplates.DeployTemplate,
deploy_template, ignore_missing=ignore_missing)
return self._delete(
_deploytemplates.DeployTemplate,
deploy_template,
ignore_missing=ignore_missing,
)
def get_deploy_template(self, deploy_template, fields=None):
"""Get a specific deployment template.
@ -1551,8 +1607,9 @@ class Proxy(proxy.Proxy):
when no deployment template matching the name or
ID could be found.
"""
return self._get_with_fields(_deploytemplates.DeployTemplate,
deploy_template, fields=fields)
return self._get_with_fields(
_deploytemplates.DeployTemplate, deploy_template, fields=fields
)
def patch_deploy_template(self, deploy_template, patch):
"""Apply a JSON patch to the deploy_templates.
@ -1568,8 +1625,9 @@ class Proxy(proxy.Proxy):
:rtype:
:class:`~openstack.baremetal.v1.deploy_templates.DeployTemplate`
"""
return self._get_resource(_deploytemplates.DeployTemplate,
deploy_template).patch(self, patch)
return self._get_resource(
_deploytemplates.DeployTemplate, deploy_template
).patch(self, patch)
def conductors(self, details=False, **query):
"""Retrieve a generator of conductors.
@ -1595,5 +1653,6 @@ class Proxy(proxy.Proxy):
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
conductor matching the name could be found.
"""
return self._get_with_fields(_conductor.Conductor,
conductor, fields=fields)
return self._get_with_fields(
_conductor.Conductor, conductor, fields=fields
)

View File

@ -32,7 +32,9 @@ class Allocation(_common.ListMixin, resource.Resource):
commit_jsonpatch = True
_query_mapping = resource.QueryParameters(
'node', 'resource_class', 'state',
'node',
'resource_class',
'state',
fields={'type': _common.fields_type},
)
@ -88,18 +90,20 @@ class Allocation(_common.ListMixin, resource.Resource):
return self
for count in utils.iterate_timeout(
timeout,
"Timeout waiting for the allocation %s" % self.id):
timeout, "Timeout waiting for the allocation %s" % self.id
):
self.fetch(session)
if self.state == 'error' and not ignore_error:
raise exceptions.ResourceFailure(
"Allocation %(allocation)s failed: %(error)s" %
{'allocation': self.id, 'error': self.last_error})
"Allocation %(allocation)s failed: %(error)s"
% {'allocation': self.id, 'error': self.last_error}
)
elif self.state != 'allocating':
return self
session.log.debug(
'Still waiting for the allocation %(allocation)s '
'to become active, the current state is %(state)s',
{'allocation': self.id, 'state': self.state})
{'allocation': self.id, 'state': self.state},
)

View File

@ -63,7 +63,8 @@ class Driver(resource.Resource):
#: Default management interface implementation.
#: Introduced in API microversion 1.30.
default_management_interface = resource.Body(
"default_management_interface")
"default_management_interface"
)
#: Default network interface implementation.
#: Introduced in API microversion 1.30.
default_network_interface = resource.Body("default_network_interface")
@ -101,7 +102,8 @@ class Driver(resource.Resource):
#: Enabled management interface implementations.
#: Introduced in API microversion 1.30.
enabled_management_interfaces = resource.Body(
"enabled_management_interfaces")
"enabled_management_interfaces"
)
#: Enabled network interface implementations.
#: Introduced in API microversion 1.30.
enabled_network_interfaces = resource.Body("enabled_network_interfaces")
@ -135,17 +137,18 @@ class Driver(resource.Resource):
"""
session = self._get_session(session)
request = self._prepare_request()
request.url = utils.urljoin(
request.url, 'vendor_passthru', 'methods')
request.url = utils.urljoin(request.url, 'vendor_passthru', 'methods')
response = session.get(request.url, headers=request.headers)
msg = ("Failed to list list vendor_passthru methods for {driver_name}"
.format(driver_name=self.name))
exceptions.raise_from_response(response, error_message=msg)
msg = "Failed to list list vendor_passthru methods for {driver_name}"
exceptions.raise_from_response(
response, error_message=msg.format(driver_name=self.name)
)
return response.json()
def call_vendor_passthru(self, session,
verb: str, method: str, body: dict = None):
def call_vendor_passthru(
self, session, verb: str, method: str, body: dict = None
):
"""Call a vendor specific passthru method
Contents of body are params passed to the hardware driver
@ -167,13 +170,18 @@ class Driver(resource.Resource):
session = self._get_session(session)
request = self._prepare_request()
request.url = utils.urljoin(
request.url, f'vendor_passthru?method={method}')
request.url, f'vendor_passthru?method={method}'
)
call = getattr(session, verb.lower())
response = call(
request.url, json=body, headers=request.headers,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
request.url,
json=body,
headers=request.headers,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
msg = ("Failed call to method {method} on driver {driver_name}"
.format(method=method, driver_name=self.name))
msg = "Failed call to method {method} on driver {driver_name}".format(
method=method, driver_name=self.name
)
exceptions.raise_from_response(response, error_message=msg)
return response

View File

@ -51,8 +51,9 @@ class PowerAction(enum.Enum):
"""Reboot the node using soft power off."""
class WaitResult(collections.namedtuple('WaitResult',
['success', 'failure', 'timeout'])):
class WaitResult(
collections.namedtuple('WaitResult', ['success', 'failure', 'timeout'])
):
"""A named tuple representing a result of waiting for several nodes.
Each component is a list of :class:`~openstack.baremetal.v1.node.Node`
@ -65,6 +66,7 @@ class WaitResult(collections.namedtuple('WaitResult',
:ivar ~.failure: a list of :class:`~openstack.baremetal.v1.node.Node`
objects that hit a failure.
"""
__slots__ = ()
@ -84,8 +86,12 @@ class Node(_common.ListMixin, resource.Resource):
commit_jsonpatch = True
_query_mapping = resource.QueryParameters(
'associated', 'conductor_group', 'driver', 'fault',
'provision_state', 'resource_class',
'associated',
'conductor_group',
'driver',
'fault',
'provision_state',
'resource_class',
fields={'type': _common.fields_type},
instance_id='instance_uuid',
is_maintenance='maintenance',
@ -292,21 +298,30 @@ class Node(_common.ListMixin, resource.Resource):
# Verify that the requested provision state is reachable with
# the API version we are going to use.
try:
microversion = _common.STATE_VERSIONS[
expected_provision_state]
microversion = _common.STATE_VERSIONS[expected_provision_state]
except KeyError:
raise ValueError(
"Node's provision_state must be one of %s for creation, "
"got %s" % (', '.join(_common.STATE_VERSIONS),
expected_provision_state))
"got %s"
% (
', '.join(_common.STATE_VERSIONS),
expected_provision_state,
)
)
else:
error_message = ("Cannot create a node with initial provision "
"state %s" % expected_provision_state)
error_message = (
"Cannot create a node with initial provision "
"state %s" % expected_provision_state
)
# Nodes cannot be created as available using new API versions
maximum = ('1.10' if expected_provision_state == 'available'
else None)
maximum = (
'1.10' if expected_provision_state == 'available' else None
)
microversion = self._assert_microversion_for(
session, 'create', microversion, maximum=maximum,
session,
'create',
microversion,
maximum=maximum,
error_message=error_message,
)
else:
@ -315,11 +330,14 @@ class Node(_common.ListMixin, resource.Resource):
# Ironic cannot set provision_state itself, so marking it as unchanged
self._clean_body_attrs({'provision_state'})
super(Node, self).create(session, *args, microversion=microversion,
**kwargs)
super(Node, self).create(
session, *args, microversion=microversion, **kwargs
)
if (expected_provision_state == 'manageable'
and self.provision_state != 'manageable'):
if (
expected_provision_state == 'manageable'
and self.provision_state != 'manageable'
):
# Manageable is not reachable directly
self.set_provision_state(session, 'manage', wait=True)
@ -334,17 +352,22 @@ class Node(_common.ListMixin, resource.Resource):
:return: This :class:`Node` instance.
"""
# These fields have to be set through separate API.
if ('maintenance_reason' in self._body.dirty
or 'maintenance' in self._body.dirty):
if (
'maintenance_reason' in self._body.dirty
or 'maintenance' in self._body.dirty
):
if not self.is_maintenance and self.maintenance_reason:
if 'maintenance' in self._body.dirty:
self.maintenance_reason = None
else:
raise ValueError('Maintenance reason cannot be set when '
'maintenance is False')
raise ValueError(
'Maintenance reason cannot be set when '
'maintenance is False'
)
if self.is_maintenance:
self._do_maintenance_action(
session, 'put', {'reason': self.maintenance_reason})
session, 'put', {'reason': self.maintenance_reason}
)
else:
# This corresponds to setting maintenance=False and
# maintenance_reason=None in the same request.
@ -358,9 +381,17 @@ class Node(_common.ListMixin, resource.Resource):
return super(Node, self).commit(session, *args, **kwargs)
def set_provision_state(self, session, target, config_drive=None,
clean_steps=None, rescue_password=None,
wait=False, timeout=None, deploy_steps=None):
def set_provision_state(
self,
session,
target,
config_drive=None,
clean_steps=None,
rescue_password=None,
wait=False,
timeout=None,
deploy_steps=None,
):
"""Run an action modifying this node's provision state.
This call is asynchronous, it will return success as soon as the Bare
@ -413,51 +444,65 @@ class Node(_common.ListMixin, resource.Resource):
body = {'target': target}
if config_drive:
if target not in ('active', 'rebuild'):
raise ValueError('Config drive can only be provided with '
'"active" and "rebuild" targets')
raise ValueError(
'Config drive can only be provided with '
'"active" and "rebuild" targets'
)
# Not a typo - ironic accepts "configdrive" (without underscore)
body['configdrive'] = config_drive
if clean_steps is not None:
if target != 'clean':
raise ValueError('Clean steps can only be provided with '
'"clean" target')
raise ValueError(
'Clean steps can only be provided with ' '"clean" target'
)
body['clean_steps'] = clean_steps
if deploy_steps is not None:
if target not in ('active', 'rebuild'):
raise ValueError('Deploy steps can only be provided with '
'"deploy" and "rebuild" target')
raise ValueError(
'Deploy steps can only be provided with '
'"deploy" and "rebuild" target'
)
body['deploy_steps'] = deploy_steps
if rescue_password is not None:
if target != 'rescue':
raise ValueError('Rescue password can only be provided with '
'"rescue" target')
raise ValueError(
'Rescue password can only be provided with '
'"rescue" target'
)
body['rescue_password'] = rescue_password
if wait:
try:
expected_state = _common.EXPECTED_STATES[target]
except KeyError:
raise ValueError('For target %s the expected state is not '
'known, cannot wait for it' % target)
raise ValueError(
'For target %s the expected state is not '
'known, cannot wait for it' % target
)
request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'states', 'provision')
response = session.put(
request.url, json=body,
headers=request.headers, microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
request.url,
json=body,
headers=request.headers,
microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
msg = ("Failed to set provision state for bare metal node {node} "
"to {target}".format(node=self.id, target=target))
msg = (
"Failed to set provision state for bare metal node {node} "
"to {target}".format(node=self.id, target=target)
)
exceptions.raise_from_response(response, error_message=msg)
if wait:
return self.wait_for_provision_state(session,
expected_state,
timeout=timeout)
return self.wait_for_provision_state(
session, expected_state, timeout=timeout
)
else:
return self.fetch(session)
@ -475,10 +520,11 @@ class Node(_common.ListMixin, resource.Resource):
:raises: :class:`~openstack.exceptions.ResourceTimeout` on timeout.
"""
for count in utils.iterate_timeout(
timeout,
"Timeout waiting for node %(node)s to reach "
"power state '%(state)s'" % {'node': self.id,
'state': expected_state}):
timeout,
"Timeout waiting for node %(node)s to reach "
"power state '%(state)s'"
% {'node': self.id, 'state': expected_state},
):
self.fetch(session)
if self.power_state == expected_state:
return self
@ -486,11 +532,16 @@ class Node(_common.ListMixin, resource.Resource):
session.log.debug(
'Still waiting for node %(node)s to reach power state '
'"%(target)s", the current state is "%(state)s"',
{'node': self.id, 'target': expected_state,
'state': self.power_state})
{
'node': self.id,
'target': expected_state,
'state': self.power_state,
},
)
def wait_for_provision_state(self, session, expected_state, timeout=None,
abort_on_failed_state=True):
def wait_for_provision_state(
self, session, expected_state, timeout=None, abort_on_failed_state=True
):
"""Wait for the node to reach the expected state.
:param session: The session to use for making this request.
@ -510,20 +561,26 @@ class Node(_common.ListMixin, resource.Resource):
:raises: :class:`~openstack.exceptions.ResourceTimeout` on timeout.
"""
for count in utils.iterate_timeout(
timeout,
"Timeout waiting for node %(node)s to reach "
"target state '%(state)s'" % {'node': self.id,
'state': expected_state}):
timeout,
"Timeout waiting for node %(node)s to reach "
"target state '%(state)s'"
% {'node': self.id, 'state': expected_state},
):
self.fetch(session)
if self._check_state_reached(session, expected_state,
abort_on_failed_state):
if self._check_state_reached(
session, expected_state, abort_on_failed_state
):
return self
session.log.debug(
'Still waiting for node %(node)s to reach state '
'"%(target)s", the current state is "%(state)s"',
{'node': self.id, 'target': expected_state,
'state': self.provision_state})
{
'node': self.id,
'target': expected_state,
'state': self.provision_state,
},
)
def wait_for_reservation(self, session, timeout=None):
"""Wait for a lock on the node to be released.
@ -552,9 +609,9 @@ class Node(_common.ListMixin, resource.Resource):
return self
for count in utils.iterate_timeout(
timeout,
"Timeout waiting for the lock to be released on node %s" %
self.id):
timeout,
"Timeout waiting for the lock to be released on node %s" % self.id,
):
self.fetch(session)
if self.reservation is None:
return self
@ -562,10 +619,12 @@ class Node(_common.ListMixin, resource.Resource):
session.log.debug(
'Still waiting for the lock to be released on node '
'%(node)s, currently locked by conductor %(host)s',
{'node': self.id, 'host': self.reservation})
{'node': self.id, 'host': self.reservation},
)
def _check_state_reached(self, session, expected_state,
abort_on_failed_state=True):
def _check_state_reached(
self, session, expected_state, abort_on_failed_state=True
):
"""Wait for the node to reach the expected state.
:param session: The session to use for making this request.
@ -581,29 +640,39 @@ class Node(_common.ListMixin, resource.Resource):
reaches an error state and ``abort_on_failed_state`` is ``True``.
"""
# NOTE(dtantsur): microversion 1.2 changed None to available
if (self.provision_state == expected_state
or (expected_state == 'available'
and self.provision_state is None)):
if self.provision_state == expected_state or (
expected_state == 'available' and self.provision_state is None
):
return True
elif not abort_on_failed_state:
return False
if (self.provision_state.endswith(' failed')
or self.provision_state == 'error'):
if (
self.provision_state.endswith(' failed')
or self.provision_state == 'error'
):
raise exceptions.ResourceFailure(
"Node %(node)s reached failure state \"%(state)s\"; "
"the last error is %(error)s" %
{'node': self.id, 'state': self.provision_state,
'error': self.last_error})
"the last error is %(error)s"
% {
'node': self.id,
'state': self.provision_state,
'error': self.last_error,
}
)
# Special case: a failure state for "manage" transition can be
# "enroll"
elif (expected_state == 'manageable'
and self.provision_state == 'enroll' and self.last_error):
elif (
expected_state == 'manageable'
and self.provision_state == 'enroll'
and self.last_error
):
raise exceptions.ResourceFailure(
"Node %(node)s could not reach state manageable: "
"failed to verify management credentials; "
"the last error is %(error)s" %
{'node': self.id, 'error': self.last_error})
"the last error is %(error)s"
% {'node': self.id, 'error': self.last_error}
)
def inject_nmi(self, session):
"""Inject NMI.
@ -630,7 +699,7 @@ class Node(_common.ListMixin, resource.Resource):
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
msg = ("Failed to inject NMI to node {node}".format(node=self.id))
msg = "Failed to inject NMI to node {node}".format(node=self.id)
exceptions.raise_from_response(response, error_message=msg)
def set_power_state(self, session, target, wait=False, timeout=None):
@ -654,8 +723,10 @@ class Node(_common.ListMixin, resource.Resource):
try:
expected = _common.EXPECTED_POWER_STATES[target]
except KeyError:
raise ValueError("Cannot use target power state %s with wait, "
"the expected state is not known" % target)
raise ValueError(
"Cannot use target power state %s with wait, "
"the expected state is not known" % target
)
session = self._get_session(session)
@ -672,12 +743,17 @@ class Node(_common.ListMixin, resource.Resource):
request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'states', 'power')
response = session.put(
request.url, json=body,
headers=request.headers, microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
request.url,
json=body,
headers=request.headers,
microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
msg = ("Failed to set power state for bare metal node {node} "
"to {target}".format(node=self.id, target=target))
msg = (
"Failed to set power state for bare metal node {node} "
"to {target}".format(node=self.id, target=target)
)
exceptions.raise_from_response(response, error_message=msg)
if wait:
@ -704,8 +780,11 @@ class Node(_common.ListMixin, resource.Resource):
"""
session = self._get_session(session)
version = self._assert_microversion_for(
session, 'commit', _common.VIF_VERSION,
error_message=("Cannot use VIF attachment API"))
session,
'commit',
_common.VIF_VERSION,
error_message=("Cannot use VIF attachment API"),
)
request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'vifs')
@ -714,12 +793,16 @@ class Node(_common.ListMixin, resource.Resource):
if not retry_on_conflict:
retriable_status_codes = set(retriable_status_codes) - {409}
response = session.post(
request.url, json=body,
headers=request.headers, microversion=version,
retriable_status_codes=retriable_status_codes)
request.url,
json=body,
headers=request.headers,
microversion=version,
retriable_status_codes=retriable_status_codes,
)
msg = ("Failed to attach VIF {vif} to bare metal node {node}"
.format(node=self.id, vif=vif_id))
msg = "Failed to attach VIF {vif} to bare metal node {node}".format(
node=self.id, vif=vif_id
)
exceptions.raise_from_response(response, error_message=msg)
def detach_vif(self, session, vif_id, ignore_missing=True):
@ -742,23 +825,31 @@ class Node(_common.ListMixin, resource.Resource):
"""
session = self._get_session(session)
version = self._assert_microversion_for(
session, 'commit', _common.VIF_VERSION,
error_message=("Cannot use VIF attachment API"))
session,
'commit',
_common.VIF_VERSION,
error_message=("Cannot use VIF attachment API"),
)
request = self._prepare_request(requires_id=True)
request.url = utils.urljoin(request.url, 'vifs', vif_id)
response = session.delete(
request.url, headers=request.headers, microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
request.url,
headers=request.headers,
microversion=version,
retriable_status_codes=_common.RETRIABLE_STATUS_CODES,
)
if ignore_missing and response.status_code == 400:
session.log.debug(
'VIF %(vif)s was already removed from node %(node)s',
{'vif': vif_id, 'node': self.id})
{'vif': vif_id, 'node': self.id},
)
return False
msg = ("Failed to detach VIF {vif} from bare metal node {node}"
.format(node=self.id, vif=vif_id))
msg = "Failed to detach VIF {vif} from bare metal node {node}".format(
node=self.id, vif=vif_id
)
exceptions.raise_from_response(response, error_message=msg)
return True
@ -777,16 +868,21 @@ class Node(_common.ListMixin, resource.Resource):
"""
session = self._get_session(session)
version = self._assert_microversion_for(
session, 'fetch', _common.VIF_VERSION,
error_message=("Cannot use VIF attachment API"))
session,
'fetch',
_common.VIF_VERSION,
error_message=("Cannot use VIF attachment API"),
)
request = self._prepare_request(