Remove dependency on ironic-lib
ironic-lib is being retired; this change imports any used code from ironic-lib and updates references. This contains some changes to how we throw exceptions; aligning ironic-lib code with IPA practice to have all exceptions be a RESTError. This also allows us to remove code around serializing ironic-lib exceptions. Change-Id: I137340ce6820c68d8e0f1a32668151bba7b1ddd7
This commit is contained in:
parent
06077cb88e
commit
8b18184e2d
ironic_python_agent
agent.pyburnin.pydevice_hints.pydisk_partitioner.pydisk_utils.pyencoding.pyerrors.py
requirements.txtextensions
hardware.pyhardware_managers/nvidia
inject_files.pyinspect.pyinspector.pymdns.pymetrics_lib
partition_utils.pyqemu_img.pyraid_utils.pytests/unit
base.py
utils.pyextensions
metrics_lib
test_agent.pytest_base.pytest_burnin.pytest_device_hints.pytest_disk_partitioner.pytest_disk_utils.pytest_encoding.pytest_hardware.pytest_inspector.pytest_mdns.pytest_partition_utils.pytest_qemu_img.pytest_raid_utils.pytest_utils.py@ -22,8 +22,6 @@ import time
|
|||||||
from urllib import parse as urlparse
|
from urllib import parse as urlparse
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
from ironic_lib import exception as lib_exc
|
|
||||||
from ironic_lib import mdns
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@ -36,6 +34,7 @@ from ironic_python_agent.extensions import base
|
|||||||
from ironic_python_agent import hardware
|
from ironic_python_agent import hardware
|
||||||
from ironic_python_agent import inspector
|
from ironic_python_agent import inspector
|
||||||
from ironic_python_agent import ironic_api_client
|
from ironic_python_agent import ironic_api_client
|
||||||
|
from ironic_python_agent import mdns
|
||||||
from ironic_python_agent import netutils
|
from ironic_python_agent import netutils
|
||||||
from ironic_python_agent import utils
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
@ -48,9 +47,6 @@ NETWORK_WAIT_TIMEOUT = 60
|
|||||||
# Time(in seconds) to wait before reattempt
|
# Time(in seconds) to wait before reattempt
|
||||||
NETWORK_WAIT_RETRY = 5
|
NETWORK_WAIT_RETRY = 5
|
||||||
|
|
||||||
cfg.CONF.import_group('metrics', 'ironic_lib.metrics_utils')
|
|
||||||
cfg.CONF.import_group('metrics_statsd', 'ironic_lib.metrics_statsd')
|
|
||||||
|
|
||||||
Host = collections.namedtuple('Host', ['hostname', 'port'])
|
Host = collections.namedtuple('Host', ['hostname', 'port'])
|
||||||
|
|
||||||
|
|
||||||
@ -213,7 +209,7 @@ class IronicPythonAgent(base.ExecuteCommandMixin):
|
|||||||
if (not api_url or api_url == 'mdns') and not standalone:
|
if (not api_url or api_url == 'mdns') and not standalone:
|
||||||
try:
|
try:
|
||||||
api_url, params = mdns.get_endpoint('baremetal')
|
api_url, params = mdns.get_endpoint('baremetal')
|
||||||
except lib_exc.ServiceLookupFailure:
|
except errors.ServiceLookupFailure:
|
||||||
if api_url:
|
if api_url:
|
||||||
# mDNS explicitly requested, report failure.
|
# mDNS explicitly requested, report failure.
|
||||||
raise
|
raise
|
||||||
|
@ -14,13 +14,13 @@ import json
|
|||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from ironic_lib import utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from tooz import coordination
|
from tooz import coordination
|
||||||
|
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import hardware
|
from ironic_python_agent import hardware
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
314
ironic_python_agent/device_hints.py
Normal file
314
ironic_python_agent/device_hints.py
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
# 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.
|
||||||
|
import copy
|
||||||
|
import re
|
||||||
|
from urllib import parse as urlparse
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import specs_matcher
|
||||||
|
from oslo_utils import strutils
|
||||||
|
from oslo_utils import units
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# A dictionary in the form {hint name: hint type}
|
||||||
|
VALID_ROOT_DEVICE_HINTS = {
|
||||||
|
'size': int, 'model': str, 'wwn': str, 'serial': str, 'vendor': str,
|
||||||
|
'wwn_with_extension': str, 'wwn_vendor_extension': str, 'name': str,
|
||||||
|
'rotational': bool, 'hctl': str, 'by_path': str,
|
||||||
|
}
|
||||||
|
|
||||||
|
ROOT_DEVICE_HINTS_GRAMMAR = specs_matcher.make_grammar()
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_hint_operator_and_values(hint_expression, hint_name):
|
||||||
|
"""Extract the operator and value(s) of a root device hint expression.
|
||||||
|
|
||||||
|
A root device hint expression could contain one or more values
|
||||||
|
depending on the operator. This method extracts the operator and
|
||||||
|
value(s) and returns a dictionary containing both.
|
||||||
|
|
||||||
|
:param hint_expression: The hint expression string containing value(s)
|
||||||
|
and operator (optionally).
|
||||||
|
:param hint_name: The name of the hint. Used for logging.
|
||||||
|
:raises: ValueError if the hint_expression is empty.
|
||||||
|
:returns: A dictionary containing:
|
||||||
|
|
||||||
|
:op: The operator. An empty string in case of None.
|
||||||
|
:values: A list of values stripped and converted to lowercase.
|
||||||
|
"""
|
||||||
|
expression = str(hint_expression).strip().lower()
|
||||||
|
if not expression:
|
||||||
|
raise ValueError(f'Root device hint {hint_name} expression is empty')
|
||||||
|
|
||||||
|
# parseString() returns a list of tokens which the operator (if
|
||||||
|
# present) is always the first element.
|
||||||
|
ast = ROOT_DEVICE_HINTS_GRAMMAR.parseString(expression)
|
||||||
|
if len(ast) <= 1:
|
||||||
|
# hint_expression had no operator
|
||||||
|
return {'op': '', 'values': [expression]}
|
||||||
|
|
||||||
|
op = ast[0]
|
||||||
|
return {'values': [v.strip() for v in re.split(op, expression) if v],
|
||||||
|
'op': op}
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_hint_expression(hint_expression, hint_name):
|
||||||
|
"""Normalize a string type hint expression.
|
||||||
|
|
||||||
|
A string-type hint expression contains one or more operators and
|
||||||
|
one or more values: [<op>] <value> [<op> <value>]*. This normalizes
|
||||||
|
the values by url-encoding white spaces and special characters. The
|
||||||
|
operators are not normalized. For example: the hint value of "<or>
|
||||||
|
foo bar <or> bar" will become "<or> foo%20bar <or> bar".
|
||||||
|
|
||||||
|
:param hint_expression: The hint expression string containing value(s)
|
||||||
|
and operator (optionally).
|
||||||
|
:param hint_name: The name of the hint. Used for logging.
|
||||||
|
:raises: ValueError if the hint_expression is empty.
|
||||||
|
:returns: A normalized string.
|
||||||
|
"""
|
||||||
|
hdict = _extract_hint_operator_and_values(hint_expression, hint_name)
|
||||||
|
result = hdict['op'].join([' %s ' % urlparse.quote(t)
|
||||||
|
for t in hdict['values']])
|
||||||
|
return (hdict['op'] + result).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _append_operator_to_hints(root_device):
|
||||||
|
"""Add an equal (s== or ==) operator to the hints.
|
||||||
|
|
||||||
|
For backwards compatibility, for root device hints where no operator
|
||||||
|
means equal, this method adds the equal operator to the hint. This is
|
||||||
|
needed when using oslo.utils.specs_matcher methods.
|
||||||
|
|
||||||
|
:param root_device: The root device hints dictionary.
|
||||||
|
"""
|
||||||
|
for name, expression in root_device.items():
|
||||||
|
# NOTE(lucasagomes): The specs_matcher from oslo.utils does not
|
||||||
|
# support boolean, so we don't need to append any operator
|
||||||
|
# for it.
|
||||||
|
if VALID_ROOT_DEVICE_HINTS[name] is bool:
|
||||||
|
continue
|
||||||
|
|
||||||
|
expression = str(expression)
|
||||||
|
ast = ROOT_DEVICE_HINTS_GRAMMAR.parseString(expression)
|
||||||
|
if len(ast) > 1:
|
||||||
|
continue
|
||||||
|
|
||||||
|
op = 's== %s' if VALID_ROOT_DEVICE_HINTS[name] is str else '== %s'
|
||||||
|
root_device[name] = op % expression
|
||||||
|
|
||||||
|
return root_device
|
||||||
|
|
||||||
|
|
||||||
|
def parse_root_device_hints(root_device):
|
||||||
|
"""Parse the root_device property of a node.
|
||||||
|
|
||||||
|
Parses and validates the root_device property of a node. These are
|
||||||
|
hints for how a node's root device is created. The 'size' hint
|
||||||
|
should be a positive integer. The 'rotational' hint should be a
|
||||||
|
Boolean value.
|
||||||
|
|
||||||
|
:param root_device: the root_device dictionary from the node's property.
|
||||||
|
:returns: a dictionary with the root device hints parsed or
|
||||||
|
None if there are no hints.
|
||||||
|
:raises: ValueError, if some information is invalid.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not root_device:
|
||||||
|
return
|
||||||
|
|
||||||
|
root_device = copy.deepcopy(root_device)
|
||||||
|
|
||||||
|
invalid_hints = set(root_device) - set(VALID_ROOT_DEVICE_HINTS)
|
||||||
|
if invalid_hints:
|
||||||
|
raise ValueError('The hints "%(invalid_hints)s" are invalid. '
|
||||||
|
'Valid hints are: "%(valid_hints)s"' %
|
||||||
|
{'invalid_hints': ', '.join(invalid_hints),
|
||||||
|
'valid_hints': ', '.join(VALID_ROOT_DEVICE_HINTS)})
|
||||||
|
|
||||||
|
for name, expression in root_device.items():
|
||||||
|
hint_type = VALID_ROOT_DEVICE_HINTS[name]
|
||||||
|
if hint_type is str:
|
||||||
|
if not isinstance(expression, str):
|
||||||
|
raise ValueError(
|
||||||
|
'Root device hint "%(name)s" is not a string value. '
|
||||||
|
'Hint expression: %(expression)s' %
|
||||||
|
{'name': name, 'expression': expression})
|
||||||
|
root_device[name] = _normalize_hint_expression(expression, name)
|
||||||
|
|
||||||
|
elif hint_type is int:
|
||||||
|
for v in _extract_hint_operator_and_values(expression,
|
||||||
|
name)['values']:
|
||||||
|
try:
|
||||||
|
integer = int(v)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
'Root device hint "%(name)s" is not an integer '
|
||||||
|
'value. Current value: %(expression)s' %
|
||||||
|
{'name': name, 'expression': expression})
|
||||||
|
|
||||||
|
if integer <= 0:
|
||||||
|
raise ValueError(
|
||||||
|
'Root device hint "%(name)s" should be a positive '
|
||||||
|
'integer. Current value: %(expression)s' %
|
||||||
|
{'name': name, 'expression': expression})
|
||||||
|
|
||||||
|
elif hint_type is bool:
|
||||||
|
try:
|
||||||
|
root_device[name] = strutils.bool_from_string(
|
||||||
|
expression, strict=True)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
'Root device hint "%(name)s" is not a Boolean value. '
|
||||||
|
'Current value: %(expression)s' %
|
||||||
|
{'name': name, 'expression': expression})
|
||||||
|
|
||||||
|
return _append_operator_to_hints(root_device)
|
||||||
|
|
||||||
|
|
||||||
|
def find_devices_by_hints(devices, root_device_hints):
|
||||||
|
"""Find all devices that match the root device hints.
|
||||||
|
|
||||||
|
Try to find devices that match the root device hints. In order
|
||||||
|
for a device to be matched it needs to satisfy all the given hints.
|
||||||
|
|
||||||
|
:param devices: A list of dictionaries representing the devices
|
||||||
|
containing one or more of the following keys:
|
||||||
|
|
||||||
|
:name: (String) The device name, e.g /dev/sda
|
||||||
|
:size: (Integer) Size of the device in *bytes*
|
||||||
|
:model: (String) Device model
|
||||||
|
:vendor: (String) Device vendor name
|
||||||
|
:serial: (String) Device serial number
|
||||||
|
:wwn: (String) Unique storage identifier
|
||||||
|
:wwn_with_extension: (String): Unique storage identifier with
|
||||||
|
the vendor extension appended
|
||||||
|
:wwn_vendor_extension: (String): United vendor storage identifier
|
||||||
|
:rotational: (Boolean) Whether it's a rotational device or
|
||||||
|
not. Useful to distinguish HDDs (rotational) and SSDs
|
||||||
|
(not rotational).
|
||||||
|
:hctl: (String): The SCSI address: Host, channel, target and lun.
|
||||||
|
For example: '1:0:0:0'.
|
||||||
|
:by_path: (String): The alternative device name,
|
||||||
|
e.g. /dev/disk/by-path/pci-0000:00
|
||||||
|
|
||||||
|
:param root_device_hints: A dictionary with the root device hints.
|
||||||
|
:raises: ValueError, if some information is invalid.
|
||||||
|
:returns: A generator with all matching devices as dictionaries.
|
||||||
|
"""
|
||||||
|
LOG.debug('Trying to find devices from "%(devs)s" that match the '
|
||||||
|
'device hints "%(hints)s"',
|
||||||
|
{'devs': ', '.join([d.get('name') for d in devices]),
|
||||||
|
'hints': root_device_hints})
|
||||||
|
parsed_hints = parse_root_device_hints(root_device_hints)
|
||||||
|
for dev in devices:
|
||||||
|
device_name = dev.get('name')
|
||||||
|
|
||||||
|
for hint in parsed_hints:
|
||||||
|
hint_type = VALID_ROOT_DEVICE_HINTS[hint]
|
||||||
|
device_value = dev.get(hint)
|
||||||
|
hint_value = parsed_hints[hint]
|
||||||
|
|
||||||
|
if hint_type is str:
|
||||||
|
try:
|
||||||
|
device_value = _normalize_hint_expression(device_value,
|
||||||
|
hint)
|
||||||
|
except ValueError:
|
||||||
|
LOG.warning(
|
||||||
|
'The attribute "%(attr)s" of the device "%(dev)s" '
|
||||||
|
'has an empty value. Skipping device.',
|
||||||
|
{'attr': hint, 'dev': device_name})
|
||||||
|
break
|
||||||
|
|
||||||
|
if hint == 'size':
|
||||||
|
# Since we don't support units yet we expect the size
|
||||||
|
# in GiB for now
|
||||||
|
device_value = device_value / units.Gi
|
||||||
|
|
||||||
|
LOG.debug('Trying to match the device hint "%(hint)s" '
|
||||||
|
'with a value of "%(hint_value)s" against the same '
|
||||||
|
'device\'s (%(dev)s) attribute with a value of '
|
||||||
|
'"%(dev_value)s"', {'hint': hint, 'dev': device_name,
|
||||||
|
'hint_value': hint_value,
|
||||||
|
'dev_value': device_value})
|
||||||
|
|
||||||
|
# NOTE(lucasagomes): Boolean hints are not supported by
|
||||||
|
# specs_matcher.match(), so we need to do the comparison
|
||||||
|
# ourselves
|
||||||
|
if hint_type is bool:
|
||||||
|
try:
|
||||||
|
device_value = strutils.bool_from_string(device_value,
|
||||||
|
strict=True)
|
||||||
|
except ValueError:
|
||||||
|
LOG.warning('The attribute "%(attr)s" (with value '
|
||||||
|
'"%(value)s") of device "%(dev)s" is not '
|
||||||
|
'a valid Boolean. Skipping device.',
|
||||||
|
{'attr': hint, 'value': device_value,
|
||||||
|
'dev': device_name})
|
||||||
|
break
|
||||||
|
if device_value == hint_value:
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif specs_matcher.match(device_value, hint_value):
|
||||||
|
continue
|
||||||
|
|
||||||
|
LOG.debug('The attribute "%(attr)s" (with value "%(value)s") '
|
||||||
|
'of device "%(dev)s" does not match the hint %(hint)s',
|
||||||
|
{'attr': hint, 'value': device_value,
|
||||||
|
'dev': device_name, 'hint': hint_value})
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
yield dev
|
||||||
|
|
||||||
|
|
||||||
|
def match_root_device_hints(devices, root_device_hints):
|
||||||
|
"""Try to find a device that matches the root device hints.
|
||||||
|
|
||||||
|
Try to find a device that matches the root device hints. In order
|
||||||
|
for a device to be matched it needs to satisfy all the given hints.
|
||||||
|
|
||||||
|
:param devices: A list of dictionaries representing the devices
|
||||||
|
containing one or more of the following keys:
|
||||||
|
|
||||||
|
:name: (String) The device name, e.g /dev/sda
|
||||||
|
:size: (Integer) Size of the device in *bytes*
|
||||||
|
:model: (String) Device model
|
||||||
|
:vendor: (String) Device vendor name
|
||||||
|
:serial: (String) Device serial number
|
||||||
|
:wwn: (String) Unique storage identifier
|
||||||
|
:wwn_with_extension: (String): Unique storage identifier with
|
||||||
|
the vendor extension appended
|
||||||
|
:wwn_vendor_extension: (String): United vendor storage identifier
|
||||||
|
:rotational: (Boolean) Whether it's a rotational device or
|
||||||
|
not. Useful to distinguish HDDs (rotational) and SSDs
|
||||||
|
(not rotational).
|
||||||
|
:hctl: (String): The SCSI address: Host, channel, target and lun.
|
||||||
|
For example: '1:0:0:0'.
|
||||||
|
:by_path: (String): The alternative device name,
|
||||||
|
e.g. /dev/disk/by-path/pci-0000:00
|
||||||
|
|
||||||
|
:param root_device_hints: A dictionary with the root device hints.
|
||||||
|
:raises: ValueError, if some information is invalid.
|
||||||
|
:returns: The first device to match all the hints or None.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
dev = next(find_devices_by_hints(devices, root_device_hints))
|
||||||
|
except StopIteration:
|
||||||
|
LOG.warning('No device found that matches the root device hints %s',
|
||||||
|
root_device_hints)
|
||||||
|
else:
|
||||||
|
LOG.info('Root device found! The device "%s" matches the root '
|
||||||
|
'device hints %s', dev, root_device_hints)
|
||||||
|
return dev
|
@ -22,10 +22,11 @@ https://opendev.org/openstack/ironic-lib/commit/42fa5d63861ba0f04b9a4f67212173d7
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from ironic_lib import exception
|
|
||||||
from ironic_lib import utils
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from ironic_python_agent import errors
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -116,7 +117,7 @@ class DiskPartitioner(object):
|
|||||||
try:
|
try:
|
||||||
from ironic_python_agent import disk_utils # circular dependency
|
from ironic_python_agent import disk_utils # circular dependency
|
||||||
disk_utils.wait_for_disk_to_become_available(self._device)
|
disk_utils.wait_for_disk_to_become_available(self._device)
|
||||||
except exception.IronicException as e:
|
except errors.RESTError as e:
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
('Disk partitioning failed on device %(device)s. '
|
('Disk partitioning failed on device %(device)s. '
|
||||||
'Error: %(error)s') % {'device': self._device, 'error': e})
|
'Error: %(error)s') % {'device': self._device, 'error': e})
|
||||||
|
@ -26,8 +26,6 @@ import re
|
|||||||
import stat
|
import stat
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from ironic_lib import exception
|
|
||||||
from ironic_lib import utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
@ -37,6 +35,7 @@ import tenacity
|
|||||||
from ironic_python_agent import disk_partitioner
|
from ironic_python_agent import disk_partitioner
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import qemu_img
|
from ironic_python_agent import qemu_img
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@ -364,7 +363,7 @@ def is_block_device(dev):
|
|||||||
msg = ("Unable to stat device %(dev)s after attempting to verify "
|
msg = ("Unable to stat device %(dev)s after attempting to verify "
|
||||||
"%(attempts)d times.") % {'dev': dev, 'attempts': attempts}
|
"%(attempts)d times.") % {'dev': dev, 'attempts': attempts}
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
|
|
||||||
|
|
||||||
def dd(src, dst, conv_flags=None):
|
def dd(src, dst, conv_flags=None):
|
||||||
@ -578,14 +577,12 @@ def destroy_disk_metadata(dev, node_uuid):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
wait_for_disk_to_become_available(dev)
|
wait_for_disk_to_become_available(dev)
|
||||||
except exception.IronicException as e:
|
except errors.RESTError as e:
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
_('Destroying metadata failed on device %(device)s. '
|
f'Destroying metadata failed on device {dev}s. Error: {e}')
|
||||||
'Error: %(error)s')
|
|
||||||
% {'device': dev, 'error': e})
|
|
||||||
|
|
||||||
LOG.info("Disk metadata on %(dev)s successfully destroyed for node "
|
LOG.info(f"Disk metadata on {dev} successfully destroyed for node "
|
||||||
"%(node)s", {'dev': dev, 'node': node_uuid})
|
f"{node_uuid}")
|
||||||
|
|
||||||
|
|
||||||
def _fix_gpt_structs(device, node_uuid):
|
def _fix_gpt_structs(device, node_uuid):
|
||||||
@ -608,7 +605,7 @@ def _fix_gpt_structs(device, node_uuid):
|
|||||||
'for node %(node)s. Error: %(error)s' %
|
'for node %(node)s. Error: %(error)s' %
|
||||||
{'disk': device, 'node': node_uuid, 'error': e})
|
{'disk': device, 'node': node_uuid, 'error': e})
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
|
|
||||||
|
|
||||||
def fix_gpt_partition(device, node_uuid):
|
def fix_gpt_partition(device, node_uuid):
|
||||||
@ -630,7 +627,7 @@ def fix_gpt_partition(device, node_uuid):
|
|||||||
'for node %(node)s. Error: %(error)s' %
|
'for node %(node)s. Error: %(error)s' %
|
||||||
{'disk': device, 'node': node_uuid, 'error': e})
|
{'disk': device, 'node': node_uuid, 'error': e})
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
|
|
||||||
|
|
||||||
def udev_settle():
|
def udev_settle():
|
||||||
@ -785,13 +782,13 @@ def wait_for_disk_to_become_available(device):
|
|||||||
retry(_wait_for_disk)()
|
retry(_wait_for_disk)()
|
||||||
except tenacity.RetryError:
|
except tenacity.RetryError:
|
||||||
if pids[0]:
|
if pids[0]:
|
||||||
raise exception.IronicException(
|
raise errors.DeviceNotFound(
|
||||||
('Processes with the following PIDs are holding '
|
('Processes with the following PIDs are holding '
|
||||||
'device %(device)s: %(pids)s. '
|
'device %(device)s: %(pids)s. '
|
||||||
'Timed out waiting for completion.')
|
'Timed out waiting for completion.')
|
||||||
% {'device': device, 'pids': ', '.join(pids[0])})
|
% {'device': device, 'pids': ', '.join(pids[0])})
|
||||||
else:
|
else:
|
||||||
raise exception.IronicException(
|
raise errors.DeviceNotFound(
|
||||||
('Fuser exited with "%(fuser_err)s" while checking '
|
('Fuser exited with "%(fuser_err)s" while checking '
|
||||||
'locks for device %(device)s. Timed out waiting for '
|
'locks for device %(device)s. Timed out waiting for '
|
||||||
'completion.') % {'device': device, 'fuser_err': stderr[0]})
|
'completion.') % {'device': device, 'fuser_err': stderr[0]})
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from ironic_lib import exception as lib_exc
|
|
||||||
|
|
||||||
|
|
||||||
class Serializable(object):
|
class Serializable(object):
|
||||||
"""Base class for things that can be serialized."""
|
"""Base class for things that can be serialized."""
|
||||||
@ -45,14 +43,6 @@ class SerializableComparable(Serializable):
|
|||||||
return self.serialize() != other.serialize()
|
return self.serialize() != other.serialize()
|
||||||
|
|
||||||
|
|
||||||
def serialize_lib_exc(exc):
|
|
||||||
"""Serialize an ironic-lib exception."""
|
|
||||||
return {'type': exc.__class__.__name__,
|
|
||||||
'code': exc.code,
|
|
||||||
'message': str(exc),
|
|
||||||
'details': ''}
|
|
||||||
|
|
||||||
|
|
||||||
class RESTJSONEncoder(json.JSONEncoder):
|
class RESTJSONEncoder(json.JSONEncoder):
|
||||||
"""A slightly customized JSON encoder."""
|
"""A slightly customized JSON encoder."""
|
||||||
def encode(self, o):
|
def encode(self, o):
|
||||||
@ -78,7 +68,5 @@ class RESTJSONEncoder(json.JSONEncoder):
|
|||||||
return o.serialize()
|
return o.serialize()
|
||||||
elif isinstance(o, uuid.UUID):
|
elif isinstance(o, uuid.UUID):
|
||||||
return str(o)
|
return str(o)
|
||||||
elif isinstance(o, lib_exc.IronicException):
|
|
||||||
return serialize_lib_exc(o)
|
|
||||||
else:
|
else:
|
||||||
return json.JSONEncoder.default(self, o)
|
return json.JSONEncoder.default(self, o)
|
||||||
|
@ -385,3 +385,35 @@ class InvalidImage(DeploymentError):
|
|||||||
|
|
||||||
def __init__(self, details=None):
|
def __init__(self, details=None):
|
||||||
super(InvalidImage, self).__init__(details)
|
super(InvalidImage, self).__init__(details)
|
||||||
|
|
||||||
|
|
||||||
|
class FileSystemNotSupported(RESTError):
|
||||||
|
"""Error raised when a file system is not supported."""
|
||||||
|
|
||||||
|
def __init__(self, fs):
|
||||||
|
details = (f"Failed to create a file system. File system {fs} is not "
|
||||||
|
"supported.")
|
||||||
|
self.message = details
|
||||||
|
super(RESTError, self).__init__(details)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidMetricConfig(RESTError):
|
||||||
|
"""Error raised when a metric config is invalid."""
|
||||||
|
|
||||||
|
message = "Invalid value for metrics config option."
|
||||||
|
|
||||||
|
|
||||||
|
class MetricsNotSupported(RESTError):
|
||||||
|
"""Error raised when a metrics action is not supported."""
|
||||||
|
|
||||||
|
message = ("Metrics action is not supported. You may need to "
|
||||||
|
"adjust the [metrics] section in ironic.conf.")
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceLookupFailure(RESTError):
|
||||||
|
"""Error raised when an mdns service lookup fails."""
|
||||||
|
|
||||||
|
def __init__(self, service="unknown"):
|
||||||
|
details = f"Cannot find {service} service through multicast."
|
||||||
|
self.message = details
|
||||||
|
super(RESTError, self).__init__(details)
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from ironic_lib import exception as il_exc
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
@ -76,7 +75,7 @@ class CleanExtension(base.BaseAgentExtension):
|
|||||||
try:
|
try:
|
||||||
result = hardware.dispatch_to_managers(step['step'], node, ports,
|
result = hardware.dispatch_to_managers(step['step'], node, ports,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
except (errors.RESTError, il_exc.IronicException):
|
except errors.RESTError:
|
||||||
LOG.exception('Error performing clean step %s', step['step'])
|
LOG.exception('Error performing clean step %s', step['step'])
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from ironic_lib import exception as il_exc
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
@ -76,7 +75,7 @@ class DeployExtension(base.BaseAgentExtension):
|
|||||||
try:
|
try:
|
||||||
result = hardware.dispatch_to_managers(step['step'], node, ports,
|
result = hardware.dispatch_to_managers(step['step'], node, ports,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
except (errors.RESTError, il_exc.IronicException):
|
except errors.RESTError:
|
||||||
LOG.exception('Error performing deploy step %s', step['step'])
|
LOG.exception('Error performing deploy step %s', step['step'])
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from ironic_lib import exception as il_exc
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
@ -76,7 +75,7 @@ class ServiceExtension(base.BaseAgentExtension):
|
|||||||
try:
|
try:
|
||||||
result = hardware.dispatch_to_managers(step['step'], node, ports,
|
result = hardware.dispatch_to_managers(step['step'], node, ports,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
except (errors.RESTError, il_exc.IronicException):
|
except errors.RESTError:
|
||||||
LOG.exception('Error performing service step %s', step['step'])
|
LOG.exception('Error performing service step %s', step['step'])
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -19,7 +19,6 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
from urllib import parse as urlparse
|
from urllib import parse as urlparse
|
||||||
|
|
||||||
from ironic_lib import exception
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@ -402,7 +401,7 @@ def _write_image(image_info, device, configdrive=None):
|
|||||||
'totaltime': totaltime})
|
'totaltime': totaltime})
|
||||||
try:
|
try:
|
||||||
disk_utils.fix_gpt_partition(device, node_uuid=None)
|
disk_utils.fix_gpt_partition(device, node_uuid=None)
|
||||||
except exception.InstanceDeployFailure:
|
except errors.DeploymentError:
|
||||||
# Note: the catch internal to the helper method logs any errors.
|
# Note: the catch internal to the helper method logs any errors.
|
||||||
pass
|
pass
|
||||||
return uuids
|
return uuids
|
||||||
@ -796,14 +795,14 @@ def _validate_partitioning(device):
|
|||||||
processutils.ProcessExecutionError, OSError) as e:
|
processutils.ProcessExecutionError, OSError) as e:
|
||||||
msg = ("Unable to find a valid partition table on the disk after "
|
msg = ("Unable to find a valid partition table on the disk after "
|
||||||
f"writing the image. The image may be corrupted. Error: {e}")
|
f"writing the image. The image may be corrupted. Error: {e}")
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
|
|
||||||
# Check if there is at least one partition in the partition table after
|
# Check if there is at least one partition in the partition table after
|
||||||
# deploy
|
# deploy
|
||||||
if not nparts:
|
if not nparts:
|
||||||
msg = ("No partitions found on the device {} after writing "
|
msg = ("No partitions found on the device {} after writing "
|
||||||
"the image.".format(device))
|
"the image.".format(device))
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
|
|
||||||
|
|
||||||
class StandbyExtension(base.BaseAgentExtension):
|
class StandbyExtension(base.BaseAgentExtension):
|
||||||
@ -890,7 +889,7 @@ class StandbyExtension(base.BaseAgentExtension):
|
|||||||
# Fix any gpt partition
|
# Fix any gpt partition
|
||||||
try:
|
try:
|
||||||
disk_utils.fix_gpt_partition(device, node_uuid=None)
|
disk_utils.fix_gpt_partition(device, node_uuid=None)
|
||||||
except exception.InstanceDeployFailure:
|
except errors.DeploymentError:
|
||||||
# Note: the catch internal to the helper method logs any errors.
|
# Note: the catch internal to the helper method logs any errors.
|
||||||
pass
|
pass
|
||||||
# Fix the root partition UUID
|
# Fix the root partition UUID
|
||||||
|
@ -31,7 +31,6 @@ import string
|
|||||||
import time
|
import time
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from ironic_lib import utils as il_utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@ -42,6 +41,7 @@ import stevedore
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from ironic_python_agent import burnin
|
from ironic_python_agent import burnin
|
||||||
|
from ironic_python_agent import device_hints
|
||||||
from ironic_python_agent import disk_utils
|
from ironic_python_agent import disk_utils
|
||||||
from ironic_python_agent import efi_utils
|
from ironic_python_agent import efi_utils
|
||||||
from ironic_python_agent import encoding
|
from ironic_python_agent import encoding
|
||||||
@ -129,9 +129,9 @@ def _load_ipmi_modules():
|
|||||||
This is required to be called at least once before attempting to use
|
This is required to be called at least once before attempting to use
|
||||||
ipmitool or related tools.
|
ipmitool or related tools.
|
||||||
"""
|
"""
|
||||||
il_utils.try_execute('modprobe', 'ipmi_msghandler')
|
utils.try_execute('modprobe', 'ipmi_msghandler')
|
||||||
il_utils.try_execute('modprobe', 'ipmi_devintf')
|
utils.try_execute('modprobe', 'ipmi_devintf')
|
||||||
il_utils.try_execute('modprobe', 'ipmi_si')
|
utils.try_execute('modprobe', 'ipmi_si')
|
||||||
|
|
||||||
|
|
||||||
def _load_multipath_modules():
|
def _load_multipath_modules():
|
||||||
@ -150,18 +150,18 @@ def _load_multipath_modules():
|
|||||||
# which is not *really* required.. at least *shouldn't* be.
|
# which is not *really* required.. at least *shouldn't* be.
|
||||||
# WARNING(TheJulia): This command explicitly replaces local
|
# WARNING(TheJulia): This command explicitly replaces local
|
||||||
# configuration.
|
# configuration.
|
||||||
il_utils.try_execute('/usr/sbin/mpathconf', '--enable',
|
utils.try_execute('/usr/sbin/mpathconf', '--enable',
|
||||||
'--find_multipaths', 'yes',
|
'--find_multipaths', 'yes',
|
||||||
'--with_module', 'y',
|
'--with_module', 'y',
|
||||||
'--with_multipathd', 'y')
|
'--with_multipathd', 'y')
|
||||||
else:
|
else:
|
||||||
# Ensure modules are loaded. Configuration is not required
|
# Ensure modules are loaded. Configuration is not required
|
||||||
# and implied based upon compiled in defaults.
|
# and implied based upon compiled in defaults.
|
||||||
# NOTE(TheJulia): Debian/Ubuntu specifically just document
|
# NOTE(TheJulia): Debian/Ubuntu specifically just document
|
||||||
# using `multipath -t` output to start a new configuration
|
# using `multipath -t` output to start a new configuration
|
||||||
# file, if needed.
|
# file, if needed.
|
||||||
il_utils.try_execute('modprobe', 'dm_multipath')
|
utils.try_execute('modprobe', 'dm_multipath')
|
||||||
il_utils.try_execute('modprobe', 'multipath')
|
utils.try_execute('modprobe', 'multipath')
|
||||||
|
|
||||||
|
|
||||||
def _check_for_iscsi():
|
def _check_for_iscsi():
|
||||||
@ -173,13 +173,13 @@ def _check_for_iscsi():
|
|||||||
- If no connection is detected we simply return.
|
- If no connection is detected we simply return.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
il_utils.execute('iscsistart', '-f')
|
utils.execute('iscsistart', '-f')
|
||||||
except (processutils.ProcessExecutionError, EnvironmentError) as e:
|
except (processutils.ProcessExecutionError, EnvironmentError) as e:
|
||||||
LOG.debug("No iscsi connection detected. Skipping iscsi. "
|
LOG.debug("No iscsi connection detected. Skipping iscsi. "
|
||||||
"Error: %s", e)
|
"Error: %s", e)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
il_utils.execute('iscsistart', '-b')
|
utils.execute('iscsistart', '-b')
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.warning("Something went wrong executing 'iscsistart -b' "
|
LOG.warning("Something went wrong executing 'iscsistart -b' "
|
||||||
"Error: %s", e)
|
"Error: %s", e)
|
||||||
@ -192,8 +192,8 @@ def _get_md_uuid(raid_device):
|
|||||||
:returns: A string containing the UUID of an md device.
|
:returns: A string containing the UUID of an md device.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
out, _ = il_utils.execute('mdadm', '--detail', raid_device,
|
out, _ = utils.execute('mdadm', '--detail', raid_device,
|
||||||
use_standard_locale=True)
|
use_standard_locale=True)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.warning('Could not get the details of %(dev)s: %(err)s',
|
LOG.warning('Could not get the details of %(dev)s: %(err)s',
|
||||||
{'dev': raid_device, 'err': e})
|
{'dev': raid_device, 'err': e})
|
||||||
@ -224,12 +224,12 @@ def _enable_multipath():
|
|||||||
# the multipathd version in case multipathd is already running.
|
# the multipathd version in case multipathd is already running.
|
||||||
# The safest way to start multipathd is to expect OS error in addition
|
# The safest way to start multipathd is to expect OS error in addition
|
||||||
# to the execution error and handle both as inconsequential.
|
# to the execution error and handle both as inconsequential.
|
||||||
il_utils.try_execute('multipathd')
|
utils.try_execute('multipathd')
|
||||||
# This is mainly to get the system to actually do the needful and
|
# This is mainly to get the system to actually do the needful and
|
||||||
# identify/enumerate paths by combining what it can detect and what
|
# identify/enumerate paths by combining what it can detect and what
|
||||||
# it already knows. This may be useful, and in theory this should be
|
# it already knows. This may be useful, and in theory this should be
|
||||||
# logged in the IPA log should it be needed.
|
# logged in the IPA log should it be needed.
|
||||||
il_utils.execute('multipath', '-ll')
|
utils.execute('multipath', '-ll')
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
LOG.warning('Attempted to determine if multipath tools were present. '
|
LOG.warning('Attempted to determine if multipath tools were present. '
|
||||||
'Not detected. Error recorded: %s', e)
|
'Not detected. Error recorded: %s', e)
|
||||||
@ -251,7 +251,7 @@ def _get_multipath_parent_device(device):
|
|||||||
# Explicitly run the check as regardless of if the device is mpath or
|
# Explicitly run the check as regardless of if the device is mpath or
|
||||||
# not, multipath tools when using list always exits with a return
|
# not, multipath tools when using list always exits with a return
|
||||||
# code of 0.
|
# code of 0.
|
||||||
il_utils.execute('multipath', '-c', check_device)
|
utils.execute('multipath', '-c', check_device)
|
||||||
# path check with return an exit code of 1 if you send it a multipath
|
# path check with return an exit code of 1 if you send it a multipath
|
||||||
# device mapper device, like dm-0.
|
# device mapper device, like dm-0.
|
||||||
# NOTE(TheJulia): -ll is supposed to load from all available
|
# NOTE(TheJulia): -ll is supposed to load from all available
|
||||||
@ -259,7 +259,7 @@ def _get_multipath_parent_device(device):
|
|||||||
# that. That being said, it has been about a decade since I was
|
# that. That being said, it has been about a decade since I was
|
||||||
# running multipath tools on SAN connected gear, so my memory is
|
# running multipath tools on SAN connected gear, so my memory is
|
||||||
# definitely fuzzy.
|
# definitely fuzzy.
|
||||||
out, _ = il_utils.execute('multipath', '-ll', check_device)
|
out, _ = utils.execute('multipath', '-ll', check_device)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
# FileNotFoundError if the utility does not exist.
|
# FileNotFoundError if the utility does not exist.
|
||||||
# -1 return code if the device is not valid.
|
# -1 return code if the device is not valid.
|
||||||
@ -318,8 +318,8 @@ def get_component_devices(raid_device):
|
|||||||
ignore_raid=True))
|
ignore_raid=True))
|
||||||
for bdev in block_devices:
|
for bdev in block_devices:
|
||||||
try:
|
try:
|
||||||
out, _ = il_utils.execute('mdadm', '--examine', bdev.name,
|
out, _ = utils.execute('mdadm', '--examine', bdev.name,
|
||||||
use_standard_locale=True)
|
use_standard_locale=True)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
if "No md superblock detected" in str(e):
|
if "No md superblock detected" in str(e):
|
||||||
# actually not a component device
|
# actually not a component device
|
||||||
@ -366,8 +366,8 @@ def get_holder_disks(raid_device):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
out, _ = il_utils.execute('mdadm', '--detail', raid_device,
|
out, _ = utils.execute('mdadm', '--detail', raid_device,
|
||||||
use_standard_locale=True)
|
use_standard_locale=True)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.warning('Could not get holder disks of %(dev)s: %(err)s',
|
LOG.warning('Could not get holder disks of %(dev)s: %(err)s',
|
||||||
{'dev': raid_device, 'err': e})
|
{'dev': raid_device, 'err': e})
|
||||||
@ -411,7 +411,7 @@ def is_md_device(raid_device):
|
|||||||
:returns: True if the device is an md device, False otherwise.
|
:returns: True if the device is an md device, False otherwise.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
il_utils.execute('mdadm', '--detail', raid_device)
|
utils.execute('mdadm', '--detail', raid_device)
|
||||||
LOG.debug("%s is an md device", raid_device)
|
LOG.debug("%s is an md device", raid_device)
|
||||||
return True
|
return True
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@ -434,9 +434,9 @@ def md_restart(raid_device):
|
|||||||
try:
|
try:
|
||||||
LOG.debug('Restarting software RAID device %s', raid_device)
|
LOG.debug('Restarting software RAID device %s', raid_device)
|
||||||
component_devices = get_component_devices(raid_device)
|
component_devices = get_component_devices(raid_device)
|
||||||
il_utils.execute('mdadm', '--stop', raid_device)
|
utils.execute('mdadm', '--stop', raid_device)
|
||||||
il_utils.execute('mdadm', '--assemble', raid_device,
|
utils.execute('mdadm', '--assemble', raid_device,
|
||||||
*component_devices)
|
*component_devices)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
error_msg = ('Could not restart md device %(dev)s: %(err)s' %
|
error_msg = ('Could not restart md device %(dev)s: %(err)s' %
|
||||||
{'dev': raid_device, 'err': e})
|
{'dev': raid_device, 'err': e})
|
||||||
@ -451,8 +451,8 @@ def md_get_raid_devices():
|
|||||||
devices
|
devices
|
||||||
"""
|
"""
|
||||||
# Note(Boushra): mdadm output is similar to lsblk, but not
|
# Note(Boushra): mdadm output is similar to lsblk, but not
|
||||||
# identical; do not use il_utils.parse_device_tags
|
# identical; do not use utils.parse_device_tags
|
||||||
report = il_utils.execute('mdadm', '--examine', '--scan')[0]
|
report = utils.execute('mdadm', '--examine', '--scan')[0]
|
||||||
lines = report.splitlines()
|
lines = report.splitlines()
|
||||||
result = {}
|
result = {}
|
||||||
for line in lines:
|
for line in lines:
|
||||||
@ -470,7 +470,7 @@ def _md_scan_and_assemble():
|
|||||||
This call does not fail if no md devices are present.
|
This call does not fail if no md devices are present.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
il_utils.execute('mdadm', '--assemble', '--scan', '--verbose')
|
utils.execute('mdadm', '--assemble', '--scan', '--verbose')
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
LOG.warning('mdadm has not been found, RAID devices will not be '
|
LOG.warning('mdadm has not been found, RAID devices will not be '
|
||||||
'supported')
|
'supported')
|
||||||
@ -548,9 +548,9 @@ def list_all_block_devices(block_type='disk',
|
|||||||
"Cause: %(error)s", {'path': disk_by_path_dir, 'error': e})
|
"Cause: %(error)s", {'path': disk_by_path_dir, 'error': e})
|
||||||
|
|
||||||
columns = utils.LSBLK_COLUMNS
|
columns = utils.LSBLK_COLUMNS
|
||||||
report = il_utils.execute('lsblk', '-bia', '--json',
|
report = utils.execute('lsblk', '-bia', '--json',
|
||||||
'-o{}'.format(','.join(columns)),
|
'-o{}'.format(','.join(columns)),
|
||||||
check_exit_code=[0])[0]
|
check_exit_code=[0])[0]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
report_json = json.loads(report)
|
report_json = json.loads(report)
|
||||||
@ -1380,7 +1380,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
if self._lshw_cache:
|
if self._lshw_cache:
|
||||||
return self._lshw_cache
|
return self._lshw_cache
|
||||||
|
|
||||||
out, _e = il_utils.execute('lshw', '-quiet', '-json', log_stdout=False)
|
out, _e = utils.execute('lshw', '-quiet', '-json', log_stdout=False)
|
||||||
out = json.loads(out)
|
out = json.loads(out)
|
||||||
# Depending on lshw version, output might be a list, starting with
|
# Depending on lshw version, output might be a list, starting with
|
||||||
# https://github.com/lyonel/lshw/commit/135a853c60582b14c5b67e5cd988a8062d9896f4 # noqa
|
# https://github.com/lyonel/lshw/commit/135a853c60582b14c5b67e5cd988a8062d9896f4 # noqa
|
||||||
@ -1498,7 +1498,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stdout, _ = il_utils.execute('biosdevname', '-i', interface_name)
|
stdout, _ = utils.execute('biosdevname', '-i', interface_name)
|
||||||
return stdout.rstrip('\n')
|
return stdout.rstrip('\n')
|
||||||
except OSError:
|
except OSError:
|
||||||
if not WARN_BIOSDEVNAME_NOT_FOUND:
|
if not WARN_BIOSDEVNAME_NOT_FOUND:
|
||||||
@ -1614,7 +1614,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
return cpus
|
return cpus
|
||||||
|
|
||||||
def get_cpus(self):
|
def get_cpus(self):
|
||||||
lines = il_utils.execute('lscpu')[0]
|
lines = utils.execute('lscpu')[0]
|
||||||
cpu_info = self.create_cpu_info_dict(lines)
|
cpu_info = self.create_cpu_info_dict(lines)
|
||||||
|
|
||||||
# NOTE(adamcarthur) Kept this assuming it was added as a fallback
|
# NOTE(adamcarthur) Kept this assuming it was added as a fallback
|
||||||
@ -1708,7 +1708,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
for hint in skip_list_hints:
|
for hint in skip_list_hints:
|
||||||
if 'volume_name' in hint:
|
if 'volume_name' in hint:
|
||||||
continue
|
continue
|
||||||
found_devs = il_utils.find_devices_by_hints(serialized_devs, hint)
|
found_devs = device_hints.find_devices_by_hints(serialized_devs,
|
||||||
|
hint)
|
||||||
excluded_devs = {dev['name'] for dev in found_devs}
|
excluded_devs = {dev['name'] for dev in found_devs}
|
||||||
skipped_devices = excluded_devs.difference(skip_list)
|
skipped_devices = excluded_devs.difference(skip_list)
|
||||||
skip_list = skip_list.union(excluded_devs)
|
skip_list = skip_list.union(excluded_devs)
|
||||||
@ -1769,8 +1770,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
tmp_ser_dev['serial'] = serial
|
tmp_ser_dev['serial'] = serial
|
||||||
serialized_devs.append(tmp_ser_dev)
|
serialized_devs.append(tmp_ser_dev)
|
||||||
try:
|
try:
|
||||||
device = il_utils.match_root_device_hints(serialized_devs,
|
device = device_hints.match_root_device_hints(
|
||||||
root_device_hints)
|
serialized_devs, root_device_hints)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
# NOTE(lucasagomes): Just playing on the safe side
|
# NOTE(lucasagomes): Just playing on the safe side
|
||||||
# here, this exception should never be raised because
|
# here, this exception should never be raised because
|
||||||
@ -2097,7 +2098,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
args += ('--verbose', '--iterations', str(npasses), block_device.name)
|
args += ('--verbose', '--iterations', str(npasses), block_device.name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
il_utils.execute(*args)
|
utils.execute(*args)
|
||||||
except (processutils.ProcessExecutionError, OSError) as e:
|
except (processutils.ProcessExecutionError, OSError) as e:
|
||||||
LOG.error("Erasing block device %(dev)s failed with error %(err)s",
|
LOG.error("Erasing block device %(dev)s failed with error %(err)s",
|
||||||
{'dev': block_device.name, 'err': e})
|
{'dev': block_device.name, 'err': e})
|
||||||
@ -2130,8 +2131,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
try:
|
try:
|
||||||
# Don't use the '--nodeps' of lsblk to also catch the
|
# Don't use the '--nodeps' of lsblk to also catch the
|
||||||
# parent device of partitions which are RAID members.
|
# parent device of partitions which are RAID members.
|
||||||
out, _ = il_utils.execute('lsblk', '--fs', '--noheadings',
|
out, _ = utils.execute('lsblk', '--fs', '--noheadings',
|
||||||
block_device.name)
|
block_device.name)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.warning("Could not determine if %(name)s is a RAID member: "
|
LOG.warning("Could not determine if %(name)s is a RAID member: "
|
||||||
"%(err)s",
|
"%(err)s",
|
||||||
@ -2172,7 +2173,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _get_ata_security_lines(self, block_device):
|
def _get_ata_security_lines(self, block_device):
|
||||||
output = il_utils.execute('hdparm', '-I', block_device.name)[0]
|
output = utils.execute('hdparm', '-I', block_device.name)[0]
|
||||||
|
|
||||||
if '\nSecurity: ' not in output:
|
if '\nSecurity: ' not in output:
|
||||||
return []
|
return []
|
||||||
@ -2205,9 +2206,9 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
# instead of `scsi` or `sat` as smartctl will not be able to read
|
# instead of `scsi` or `sat` as smartctl will not be able to read
|
||||||
# a bridged device that it doesn't understand, and accordingly
|
# a bridged device that it doesn't understand, and accordingly
|
||||||
# return an error code.
|
# return an error code.
|
||||||
output = il_utils.execute('smartctl', '-d', 'ata',
|
output = utils.execute('smartctl', '-d', 'ata',
|
||||||
block_device.name, '-g', 'security',
|
block_device.name, '-g', 'security',
|
||||||
check_exit_code=[0, 127])[0]
|
check_exit_code=[0, 127])[0]
|
||||||
if 'Unavailable' in output:
|
if 'Unavailable' in output:
|
||||||
# Smartctl is reporting it is unavailable, lets return false.
|
# Smartctl is reporting it is unavailable, lets return false.
|
||||||
LOG.debug('Smartctl has reported that security is '
|
LOG.debug('Smartctl has reported that security is '
|
||||||
@ -2241,9 +2242,9 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
if 'not locked' in security_lines:
|
if 'not locked' in security_lines:
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
il_utils.execute('hdparm', '--user-master', 'u',
|
utils.execute('hdparm', '--user-master', 'u',
|
||||||
'--security-unlock', password,
|
'--security-unlock', password,
|
||||||
block_device.name)
|
block_device.name)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.info('Security unlock failed for device '
|
LOG.info('Security unlock failed for device '
|
||||||
'%(name)s using password "%(password)s": %(err)s',
|
'%(name)s using password "%(password)s": %(err)s',
|
||||||
@ -2289,9 +2290,9 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
# SEC1. Try to transition to SEC5 by setting empty user
|
# SEC1. Try to transition to SEC5 by setting empty user
|
||||||
# password.
|
# password.
|
||||||
try:
|
try:
|
||||||
il_utils.execute('hdparm', '--user-master', 'u',
|
utils.execute('hdparm', '--user-master', 'u',
|
||||||
'--security-set-pass', 'NULL',
|
'--security-set-pass', 'NULL',
|
||||||
block_device.name)
|
block_device.name)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
error_msg = ('Security password set failed for device '
|
error_msg = ('Security password set failed for device '
|
||||||
'{name}: {err}'
|
'{name}: {err}'
|
||||||
@ -2304,8 +2305,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
erase_option += '-enhanced'
|
erase_option += '-enhanced'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
il_utils.execute('hdparm', '--user-master', 'u', erase_option,
|
utils.execute('hdparm', '--user-master', 'u', erase_option,
|
||||||
'NULL', block_device.name)
|
'NULL', block_device.name)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
# NOTE(TheJulia): Attempt unlock to allow fallback to shred
|
# NOTE(TheJulia): Attempt unlock to allow fallback to shred
|
||||||
# to occur, otherwise shred will fail as well, as the security
|
# to occur, otherwise shred will fail as well, as the security
|
||||||
@ -2350,8 +2351,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
try:
|
try:
|
||||||
LOG.debug("Attempting to fetch NVMe capabilities for device %s",
|
LOG.debug("Attempting to fetch NVMe capabilities for device %s",
|
||||||
block_device.name)
|
block_device.name)
|
||||||
nvme_info, _e = il_utils.execute('nvme', 'id-ctrl',
|
nvme_info, _e = utils.execute('nvme', 'id-ctrl',
|
||||||
block_device.name, '-o', 'json')
|
block_device.name, '-o', 'json')
|
||||||
nvme_info = json.loads(nvme_info)
|
nvme_info = json.loads(nvme_info)
|
||||||
|
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
@ -2393,8 +2394,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
try:
|
try:
|
||||||
LOG.debug("Attempting to nvme-format %s using secure format mode "
|
LOG.debug("Attempting to nvme-format %s using secure format mode "
|
||||||
"(ses) %s", block_device.name, format_mode)
|
"(ses) %s", block_device.name, format_mode)
|
||||||
il_utils.execute('nvme', 'format', block_device.name, '-s',
|
utils.execute('nvme', 'format', block_device.name, '-s',
|
||||||
format_mode, '-f')
|
format_mode, '-f')
|
||||||
LOG.info("nvme-cli format for device %s (ses= %s ) completed "
|
LOG.info("nvme-cli format for device %s (ses= %s ) completed "
|
||||||
"successfully.", block_device.name, format_mode)
|
"successfully.", block_device.name, format_mode)
|
||||||
return True
|
return True
|
||||||
@ -2418,7 +2419,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
# different types of communication media and protocols and
|
# different types of communication media and protocols and
|
||||||
# effectively used
|
# effectively used
|
||||||
for channel in range(1, 12):
|
for channel in range(1, 12):
|
||||||
out, e = il_utils.execute(
|
out, e = utils.execute(
|
||||||
"ipmitool lan print {} | awk '/IP Address[ \\t]*:/"
|
"ipmitool lan print {} | awk '/IP Address[ \\t]*:/"
|
||||||
" {{print $4}}'".format(channel), shell=True)
|
" {{print $4}}'".format(channel), shell=True)
|
||||||
if e.startswith("Invalid channel"):
|
if e.startswith("Invalid channel"):
|
||||||
@ -2459,7 +2460,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
# different types of communication media and protocols and
|
# different types of communication media and protocols and
|
||||||
# effectively used
|
# effectively used
|
||||||
for channel in range(1, 12):
|
for channel in range(1, 12):
|
||||||
out, e = il_utils.execute(
|
out, e = utils.execute(
|
||||||
"ipmitool lan print {} | awk '/(IP|MAC) Address[ \\t]*:/"
|
"ipmitool lan print {} | awk '/(IP|MAC) Address[ \\t]*:/"
|
||||||
" {{print $4}}'".format(channel), shell=True)
|
" {{print $4}}'".format(channel), shell=True)
|
||||||
if e.startswith("Invalid channel"):
|
if e.startswith("Invalid channel"):
|
||||||
@ -2474,7 +2475,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
|
|
||||||
if ip == "0.0.0.0":
|
if ip == "0.0.0.0":
|
||||||
# Check if we have IPv6 address configured
|
# Check if we have IPv6 address configured
|
||||||
out, e = il_utils.execute(
|
out, e = utils.execute(
|
||||||
"ipmitool lan6 print {} | awk '/^IPv6"
|
"ipmitool lan6 print {} | awk '/^IPv6"
|
||||||
" (Dynamic|Static) Address [0-9]+:/"
|
" (Dynamic|Static) Address [0-9]+:/"
|
||||||
" {{in_section=1; next}} /^IPv6 / {{in_section=0}}"
|
" {{in_section=1; next}} /^IPv6 / {{in_section=0}}"
|
||||||
@ -2536,7 +2537,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
cmd = "ipmitool lan6 print {} {}_addr".format(
|
cmd = "ipmitool lan6 print {} {}_addr".format(
|
||||||
channel, 'dynamic' if dynamic else 'static')
|
channel, 'dynamic' if dynamic else 'static')
|
||||||
try:
|
try:
|
||||||
out, exc = il_utils.execute(cmd, shell=True)
|
out, exc = utils.execute(cmd, shell=True)
|
||||||
except processutils.ProcessExecutionError:
|
except processutils.ProcessExecutionError:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -2566,7 +2567,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
# different types of communication media and protocols and
|
# different types of communication media and protocols and
|
||||||
# effectively used
|
# effectively used
|
||||||
for channel in range(1, 12):
|
for channel in range(1, 12):
|
||||||
addr_mode, e = il_utils.execute(
|
addr_mode, e = utils.execute(
|
||||||
r"ipmitool lan6 print {} enables | "
|
r"ipmitool lan6 print {} enables | "
|
||||||
r"awk '/IPv6\/IPv4 Addressing Enables[ \t]*:/"
|
r"awk '/IPv6\/IPv4 Addressing Enables[ \t]*:/"
|
||||||
r"{{print $NF}}'".format(channel), shell=True)
|
r"{{print $NF}}'".format(channel), shell=True)
|
||||||
@ -2860,8 +2861,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
for raid_device in raid_devices:
|
for raid_device in raid_devices:
|
||||||
device = raid_device.name
|
device = raid_device.name
|
||||||
try:
|
try:
|
||||||
il_utils.execute('mdadm', '--examine',
|
utils.execute('mdadm', '--examine',
|
||||||
device, use_standard_locale=True)
|
device, use_standard_locale=True)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
if "No md superblock detected" in str(e):
|
if "No md superblock detected" in str(e):
|
||||||
continue
|
continue
|
||||||
@ -2988,9 +2989,9 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
{'dev': device, 'str': start_str,
|
{'dev': device, 'str': start_str,
|
||||||
'end': end_str})
|
'end': end_str})
|
||||||
|
|
||||||
il_utils.execute('parted', device, '-s', '-a',
|
utils.execute('parted', device, '-s', '-a',
|
||||||
'optimal', '--', 'mkpart', 'primary',
|
'optimal', '--', 'mkpart', 'primary',
|
||||||
start_str, end_str)
|
start_str, end_str)
|
||||||
|
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
msg = "Failed to create partitions on {}: {}".format(
|
msg = "Failed to create partitions on {}: {}".format(
|
||||||
@ -3025,8 +3026,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def _scan_raids():
|
def _scan_raids():
|
||||||
il_utils.execute('mdadm', '--assemble', '--scan',
|
utils.execute('mdadm', '--assemble', '--scan',
|
||||||
check_exit_code=False)
|
check_exit_code=False)
|
||||||
raid_devices = list_all_block_devices(block_type='raid',
|
raid_devices = list_all_block_devices(block_type='raid',
|
||||||
ignore_raid=False,
|
ignore_raid=False,
|
||||||
ignore_empty=False)
|
ignore_empty=False)
|
||||||
@ -3098,12 +3099,12 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
if not do_not_delete:
|
if not do_not_delete:
|
||||||
# Remove md devices.
|
# Remove md devices.
|
||||||
try:
|
try:
|
||||||
il_utils.execute('wipefs', '-af', raid_device.name)
|
utils.execute('wipefs', '-af', raid_device.name)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.warning('Failed to wipefs %(device)s: %(err)s',
|
LOG.warning('Failed to wipefs %(device)s: %(err)s',
|
||||||
{'device': raid_device.name, 'err': e})
|
{'device': raid_device.name, 'err': e})
|
||||||
try:
|
try:
|
||||||
il_utils.execute('mdadm', '--stop', raid_device.name)
|
utils.execute('mdadm', '--stop', raid_device.name)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.warning('Failed to stop %(device)s: %(err)s',
|
LOG.warning('Failed to stop %(device)s: %(err)s',
|
||||||
{'device': raid_device.name, 'err': e})
|
{'device': raid_device.name, 'err': e})
|
||||||
@ -3111,9 +3112,9 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
# Remove md metadata from component devices.
|
# Remove md metadata from component devices.
|
||||||
for component_device in component_devices:
|
for component_device in component_devices:
|
||||||
try:
|
try:
|
||||||
il_utils.execute('mdadm', '--examine',
|
utils.execute('mdadm', '--examine',
|
||||||
component_device,
|
component_device,
|
||||||
use_standard_locale=True)
|
use_standard_locale=True)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
if "No md superblock detected" in str(e):
|
if "No md superblock detected" in str(e):
|
||||||
# actually not a component device
|
# actually not a component device
|
||||||
@ -3125,8 +3126,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
|
|
||||||
LOG.debug('Deleting md superblock on %s', component_device)
|
LOG.debug('Deleting md superblock on %s', component_device)
|
||||||
try:
|
try:
|
||||||
il_utils.execute('mdadm', '--zero-superblock',
|
utils.execute('mdadm', '--zero-superblock',
|
||||||
component_device)
|
component_device)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.warning('Failed to remove superblock from'
|
LOG.warning('Failed to remove superblock from'
|
||||||
'%(device)s: %(err)s',
|
'%(device)s: %(err)s',
|
||||||
@ -3184,8 +3185,8 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
if blk.name in do_not_delete_disks:
|
if blk.name in do_not_delete_disks:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
il_utils.execute('mdadm', '--examine', blk.name,
|
utils.execute('mdadm', '--examine', blk.name,
|
||||||
use_standard_locale=True)
|
use_standard_locale=True)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
if "No md superblock detected" in str(e):
|
if "No md superblock detected" in str(e):
|
||||||
# actually not a component device
|
# actually not a component device
|
||||||
@ -3195,7 +3196,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
{'name': blk.name, 'err': e})
|
{'name': blk.name, 'err': e})
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
il_utils.execute('mdadm', '--zero-superblock', blk.name)
|
utils.execute('mdadm', '--zero-superblock', blk.name)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.warning('Failed to remove superblock from'
|
LOG.warning('Failed to remove superblock from'
|
||||||
'%(device)s: %(err)s',
|
'%(device)s: %(err)s',
|
||||||
@ -3214,14 +3215,14 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
'%(parts)s', {'dev': holder_disk,
|
'%(parts)s', {'dev': holder_disk,
|
||||||
'parts': del_list})
|
'parts': del_list})
|
||||||
for part in del_list:
|
for part in del_list:
|
||||||
il_utils.execute('parted', holder_disk, 'rm', part)
|
utils.execute('parted', holder_disk, 'rm', part)
|
||||||
else:
|
else:
|
||||||
LOG.warning('Holder disk %(dev)s contains only logical '
|
LOG.warning('Holder disk %(dev)s contains only logical '
|
||||||
'disk(s) on the skip list', holder_disk)
|
'disk(s) on the skip list', holder_disk)
|
||||||
continue
|
continue
|
||||||
LOG.info('Removing partitions on holder disk %s', holder_disk)
|
LOG.info('Removing partitions on holder disk %s', holder_disk)
|
||||||
try:
|
try:
|
||||||
il_utils.execute('wipefs', '-af', holder_disk)
|
utils.execute('wipefs', '-af', holder_disk)
|
||||||
except processutils.ProcessExecutionError as e:
|
except processutils.ProcessExecutionError as e:
|
||||||
LOG.warning('Failed to remove partitions on %s: %s',
|
LOG.warning('Failed to remove partitions on %s: %s',
|
||||||
holder_disk, e)
|
holder_disk, e)
|
||||||
@ -3412,7 +3413,7 @@ class GenericHardwareManager(HardwareManager):
|
|||||||
def _collect_udev(io_dict):
|
def _collect_udev(io_dict):
|
||||||
"""Collect device properties from udev."""
|
"""Collect device properties from udev."""
|
||||||
try:
|
try:
|
||||||
out, _e = il_utils.execute('lsblk', '-no', 'KNAME')
|
out, _e = utils.execute('lsblk', '-no', 'KNAME')
|
||||||
except processutils.ProcessExecutionError as exc:
|
except processutils.ProcessExecutionError as exc:
|
||||||
LOG.warning('Could not list block devices: %s', exc)
|
LOG.warning('Could not list block devices: %s', exc)
|
||||||
return
|
return
|
||||||
@ -3772,9 +3773,9 @@ def safety_check_block_device(node, device):
|
|||||||
if not di_info.get('wipe_special_filesystems', True):
|
if not di_info.get('wipe_special_filesystems', True):
|
||||||
return
|
return
|
||||||
lsblk_ids = ['UUID', 'PTUUID', 'PARTTYPE', 'PARTUUID']
|
lsblk_ids = ['UUID', 'PTUUID', 'PARTTYPE', 'PARTUUID']
|
||||||
report = il_utils.execute('lsblk', '-bia', '--json',
|
report = utils.execute('lsblk', '-bia', '--json',
|
||||||
'-o{}'.format(','.join(lsblk_ids)),
|
'-o{}'.format(','.join(lsblk_ids)),
|
||||||
device, check_exit_code=[0])[0]
|
device, check_exit_code=[0])[0]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
report_json = json.loads(report)
|
report_json = json.loads(report)
|
||||||
|
@ -20,12 +20,12 @@ from urllib import error as urlError
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from urllib import request
|
from urllib import request
|
||||||
|
|
||||||
from ironic_lib.exception import IronicException
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
|
|
||||||
|
from ironic_python_agent.errors import RESTError
|
||||||
from ironic_python_agent import utils
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
@ -113,45 +113,45 @@ def check_prereq():
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
class InvalidFirmwareImageConfig(IronicException):
|
class InvalidFirmwareImageConfig(RESTError):
|
||||||
_msg_fmt = 'Invalid firmware image config: %(error_msg)s'
|
_msg_fmt = 'Invalid firmware image config: %(error_msg)s'
|
||||||
|
|
||||||
|
|
||||||
class InvalidFirmwareSettingsConfig(IronicException):
|
class InvalidFirmwareSettingsConfig(RESTError):
|
||||||
_msg_fmt = 'Invalid firmware settings config: %(error_msg)s'
|
_msg_fmt = 'Invalid firmware settings config: %(error_msg)s'
|
||||||
|
|
||||||
|
|
||||||
class MismatchChecksumError(IronicException):
|
class MismatchChecksumError(RESTError):
|
||||||
_msg_fmt = 'Mismatch Checksum for the firmware image: %(error_msg)s'
|
_msg_fmt = 'Mismatch Checksum for the firmware image: %(error_msg)s'
|
||||||
|
|
||||||
|
|
||||||
class MismatchComponentFlavor(IronicException):
|
class MismatchComponentFlavor(RESTError):
|
||||||
_msg_fmt = 'Mismatch Component Flavor: %(error_msg)s'
|
_msg_fmt = 'Mismatch Component Flavor: %(error_msg)s'
|
||||||
|
|
||||||
|
|
||||||
class MismatchFWVersion(IronicException):
|
class MismatchFWVersion(RESTError):
|
||||||
_msg_fmt = 'Mismatch Firmware version: %(error_msg)s'
|
_msg_fmt = 'Mismatch Firmware version: %(error_msg)s'
|
||||||
|
|
||||||
|
|
||||||
class DuplicateComponentFlavor(IronicException):
|
class DuplicateComponentFlavor(RESTError):
|
||||||
_msg_fmt = ('Duplicate Component Flavor for the firmware image: '
|
_msg_fmt = ('Duplicate Component Flavor for the firmware image: '
|
||||||
'%(error_msg)s')
|
'%(error_msg)s')
|
||||||
|
|
||||||
|
|
||||||
class DuplicateDeviceID(IronicException):
|
class DuplicateDeviceID(RESTError):
|
||||||
_msg_fmt = ('Duplicate Device ID for firmware settings: '
|
_msg_fmt = ('Duplicate Device ID for firmware settings: '
|
||||||
'%(error_msg)s')
|
'%(error_msg)s')
|
||||||
|
|
||||||
|
|
||||||
class UnSupportedConfigByMstflintPackage(IronicException):
|
class UnSupportedConfigByMstflintPackage(RESTError):
|
||||||
_msg_fmt = 'Unsupported config by mstflint package: %(error_msg)s'
|
_msg_fmt = 'Unsupported config by mstflint package: %(error_msg)s'
|
||||||
|
|
||||||
|
|
||||||
class UnSupportedConfigByFW(IronicException):
|
class UnSupportedConfigByFW(RESTError):
|
||||||
_msg_fmt = 'Unsupported config by Firmware: %(error_msg)s'
|
_msg_fmt = 'Unsupported config by Firmware: %(error_msg)s'
|
||||||
|
|
||||||
|
|
||||||
class InvalidURLScheme(IronicException):
|
class InvalidURLScheme(RESTError):
|
||||||
_msg_fmt = 'Invalid URL Scheme: %(error_msg)s'
|
_msg_fmt = 'Invalid URL Scheme: %(error_msg)s'
|
||||||
|
|
||||||
|
|
||||||
@ -453,7 +453,7 @@ class NvidiaNicFirmwareBinary(object):
|
|||||||
err = 'Firmware URL scheme %s is not supported.' \
|
err = 'Firmware URL scheme %s is not supported.' \
|
||||||
'The supported firmware URL schemes are' \
|
'The supported firmware URL schemes are' \
|
||||||
'(http://, https://, file://)' % url_scheme
|
'(http://, https://, file://)' % url_scheme
|
||||||
raise InvalidURLScheme(error_msg=err)
|
raise InvalidURLScheme(details=err)
|
||||||
|
|
||||||
def _get_info(self):
|
def _get_info(self):
|
||||||
"""Get firmware information from firmware binary image
|
"""Get firmware information from firmware binary image
|
||||||
@ -488,7 +488,7 @@ class NvidiaNicFirmwareBinary(object):
|
|||||||
err = 'The provided psid %s does not match the image psid %s' % \
|
err = 'The provided psid %s does not match the image psid %s' % \
|
||||||
(self.psid, image_psid)
|
(self.psid, image_psid)
|
||||||
LOG.error(err)
|
LOG.error(err)
|
||||||
raise MismatchComponentFlavor(error_msg=err)
|
raise MismatchComponentFlavor(details=err)
|
||||||
|
|
||||||
def _validate_image_firmware_version(self):
|
def _validate_image_firmware_version(self):
|
||||||
"""Validate that the provided firmware version same as the version
|
"""Validate that the provided firmware version same as the version
|
||||||
@ -502,7 +502,7 @@ class NvidiaNicFirmwareBinary(object):
|
|||||||
err = 'The provided firmware version %s does not match ' \
|
err = 'The provided firmware version %s does not match ' \
|
||||||
'image firmware version %s' % (self.version, image_version)
|
'image firmware version %s' % (self.version, image_version)
|
||||||
LOG.error(err)
|
LOG.error(err)
|
||||||
raise MismatchFWVersion(error_msg=err)
|
raise MismatchFWVersion(details=err)
|
||||||
|
|
||||||
def _validate_image_checksum(self):
|
def _validate_image_checksum(self):
|
||||||
"""Validate the provided checksum with the calculated one of the
|
"""Validate the provided checksum with the calculated one of the
|
||||||
@ -516,7 +516,7 @@ class NvidiaNicFirmwareBinary(object):
|
|||||||
err = 'Mismatch provided checksum %s for image %s' % (
|
err = 'Mismatch provided checksum %s for image %s' % (
|
||||||
self.checksum, self.url)
|
self.checksum, self.url)
|
||||||
LOG.error(err)
|
LOG.error(err)
|
||||||
raise MismatchChecksumError(error_msg=err)
|
raise MismatchChecksumError(details=err)
|
||||||
|
|
||||||
|
|
||||||
class NvidiaFirmwareImages(object):
|
class NvidiaFirmwareImages(object):
|
||||||
@ -545,7 +545,7 @@ class NvidiaFirmwareImages(object):
|
|||||||
'url, checksum, checksumType, componentFlavor, ' \
|
'url, checksum, checksumType, componentFlavor, ' \
|
||||||
'version' % image
|
'version' % image
|
||||||
LOG.error(err)
|
LOG.error(err)
|
||||||
raise InvalidFirmwareImageConfig(error_msg=err)
|
raise InvalidFirmwareImageConfig(details=err)
|
||||||
|
|
||||||
def filter_images(self, psids_list):
|
def filter_images(self, psids_list):
|
||||||
"""Filter firmware images according to the system nics PSIDs,
|
"""Filter firmware images according to the system nics PSIDs,
|
||||||
@ -564,7 +564,7 @@ class NvidiaFirmwareImages(object):
|
|||||||
err = 'Duplicate componentFlavor %s' % \
|
err = 'Duplicate componentFlavor %s' % \
|
||||||
image['componentFlavor']
|
image['componentFlavor']
|
||||||
LOG.error(err)
|
LOG.error(err)
|
||||||
raise DuplicateComponentFlavor(error_msg=err)
|
raise DuplicateComponentFlavor(details=err)
|
||||||
else:
|
else:
|
||||||
self.filtered_images_psid_dict[
|
self.filtered_images_psid_dict[
|
||||||
image.get('componentFlavor')] = image
|
image.get('componentFlavor')] = image
|
||||||
@ -727,13 +727,13 @@ class NvidiaNicConfig(object):
|
|||||||
'please update to the latest mstflint package.' % key
|
'please update to the latest mstflint package.' % key
|
||||||
|
|
||||||
LOG.error(err)
|
LOG.error(err)
|
||||||
raise UnSupportedConfigByMstflintPackage(error_msg=err)
|
raise UnSupportedConfigByMstflintPackage(details=err)
|
||||||
|
|
||||||
if not self._param_supp_by_fw(key):
|
if not self._param_supp_by_fw(key):
|
||||||
err = 'Configuraiton %s for device %s is not supported with ' \
|
err = 'Configuraiton %s for device %s is not supported with ' \
|
||||||
'current fw' % (key, self.nvidia_dev.dev_pci)
|
'current fw' % (key, self.nvidia_dev.dev_pci)
|
||||||
LOG.error(err)
|
LOG.error(err)
|
||||||
raise UnSupportedConfigByFW(error_msg=err)
|
raise UnSupportedConfigByFW(details=err)
|
||||||
|
|
||||||
def set_config(self):
|
def set_config(self):
|
||||||
"""Set device configurations
|
"""Set device configurations
|
||||||
@ -826,14 +826,14 @@ class NvidiaNicsConfig(object):
|
|||||||
err = 'duplicate settings for device ID %s ' % \
|
err = 'duplicate settings for device ID %s ' % \
|
||||||
setting.get('deviceID')
|
setting.get('deviceID')
|
||||||
LOG.error(err)
|
LOG.error(err)
|
||||||
raise DuplicateDeviceID(error_msg=err)
|
raise DuplicateDeviceID(details=err)
|
||||||
elif setting.get('deviceID'):
|
elif setting.get('deviceID'):
|
||||||
LOG.debug('There are no devices with ID %s on the system',
|
LOG.debug('There are no devices with ID %s on the system',
|
||||||
setting.get('deviceID'))
|
setting.get('deviceID'))
|
||||||
else:
|
else:
|
||||||
err = 'There is no deviceID provided for this settings'
|
err = 'There is no deviceID provided for this settings'
|
||||||
LOG.error(err)
|
LOG.error(err)
|
||||||
raise InvalidFirmwareSettingsConfig(error_msg=err)
|
raise InvalidFirmwareSettingsConfig(details=err)
|
||||||
|
|
||||||
def prepare_nvidia_nic_config(self):
|
def prepare_nvidia_nic_config(self):
|
||||||
"""Expand the settings map per devices PCI and create a list
|
"""Expand the settings map per devices PCI and create a list
|
||||||
@ -908,7 +908,7 @@ def update_nvidia_nic_firmware_image(images):
|
|||||||
"""
|
"""
|
||||||
if not type(images) is list:
|
if not type(images) is list:
|
||||||
err = 'The images must be a list of images, %s' % images
|
err = 'The images must be a list of images, %s' % images
|
||||||
raise InvalidFirmwareImageConfig(error_msg=err)
|
raise InvalidFirmwareImageConfig(details=err)
|
||||||
check_prereq()
|
check_prereq()
|
||||||
nvidia_fw_images = NvidiaFirmwareImages(images)
|
nvidia_fw_images = NvidiaFirmwareImages(images)
|
||||||
nvidia_fw_images.validate_images_schema()
|
nvidia_fw_images.validate_images_schema()
|
||||||
@ -926,7 +926,7 @@ def update_nvidia_nic_firmware_settings(settings):
|
|||||||
"""
|
"""
|
||||||
if not type(settings) is list:
|
if not type(settings) is list:
|
||||||
err = 'The settings must be list of settings, %s' % settings
|
err = 'The settings must be list of settings, %s' % settings
|
||||||
raise InvalidFirmwareSettingsConfig(error_msg=err)
|
raise InvalidFirmwareSettingsConfig(details=err)
|
||||||
check_prereq()
|
check_prereq()
|
||||||
nvidia_nics = NvidiaNics()
|
nvidia_nics = NvidiaNics()
|
||||||
nvidia_nics.discover()
|
nvidia_nics.discover()
|
||||||
|
@ -16,7 +16,6 @@ import base64
|
|||||||
import contextlib
|
import contextlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ironic_lib import utils as ironic_utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@ -89,7 +88,7 @@ def _inject_one(node, ports, fl, root_dev, http_get):
|
|||||||
with _find_and_mount_path(fl['path'], fl.get('partition'),
|
with _find_and_mount_path(fl['path'], fl.get('partition'),
|
||||||
root_dev) as path:
|
root_dev) as path:
|
||||||
if fl.get('deleted'):
|
if fl.get('deleted'):
|
||||||
ironic_utils.unlink_without_raise(path)
|
utils.unlink_without_raise(path)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -15,7 +15,6 @@ import random
|
|||||||
import select
|
import select
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from ironic_lib import exception
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
@ -90,7 +89,7 @@ class IronicInspection(threading.Thread):
|
|||||||
interval = min(interval * self.backoff_factor,
|
interval = min(interval * self.backoff_factor,
|
||||||
self.max_delay)
|
self.max_delay)
|
||||||
|
|
||||||
except exception.ServiceLookupFailure as e:
|
except errors.ServiceLookupFailure as e:
|
||||||
# Likely a mDNS lookup failure. We should
|
# Likely a mDNS lookup failure. We should
|
||||||
# keep retrying.
|
# keep retrying.
|
||||||
LOG.error('Error looking up introspection '
|
LOG.error('Error looking up introspection '
|
||||||
|
@ -18,7 +18,6 @@ import json
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from ironic_lib import mdns
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
@ -31,6 +30,7 @@ from ironic_python_agent import config
|
|||||||
from ironic_python_agent import encoding
|
from ironic_python_agent import encoding
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import hardware
|
from ironic_python_agent import hardware
|
||||||
|
from ironic_python_agent import mdns
|
||||||
from ironic_python_agent import utils
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
|
211
ironic_python_agent/mdns.py
Normal file
211
ironic_python_agent/mdns.py
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Multicast DNS implementation for API discovery.
|
||||||
|
|
||||||
|
This implementation follows RFC 6763 as clarified by the API SIG guideline
|
||||||
|
https://review.opendev.org/651222.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_config import types as cfg_types
|
||||||
|
import zeroconf
|
||||||
|
|
||||||
|
from ironic_python_agent import errors
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
|
opts = [
|
||||||
|
cfg.IntOpt('lookup_attempts',
|
||||||
|
min=1, default=3,
|
||||||
|
help='Number of attempts to lookup a service.'),
|
||||||
|
cfg.Opt('params',
|
||||||
|
# This is required for values that contain commas.
|
||||||
|
type=cfg_types.Dict(cfg_types.String(quotes=True)),
|
||||||
|
default={},
|
||||||
|
help='Additional parameters to pass for the registered '
|
||||||
|
'service.'),
|
||||||
|
cfg.ListOpt('interfaces',
|
||||||
|
help='List of IP addresses of interfaces to use for mDNS. '
|
||||||
|
'Defaults to all interfaces on the system.'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
opt_group = cfg.OptGroup(name='mdns', title='Options for multicast DNS client')
|
||||||
|
CONF.register_group(opt_group)
|
||||||
|
CONF.register_opts(opts, opt_group)
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_MDNS_DOMAIN = '_openstack._tcp.local.'
|
||||||
|
|
||||||
|
|
||||||
|
class Zeroconf(object):
|
||||||
|
"""Multicast DNS implementation client and server.
|
||||||
|
|
||||||
|
Uses threading internally, so there is no start method. It starts
|
||||||
|
automatically on creation.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
The underlying library does not yet support IPv6.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize and start the mDNS server."""
|
||||||
|
interfaces = (CONF.mdns.interfaces if CONF.mdns.interfaces
|
||||||
|
else zeroconf.InterfaceChoice.All)
|
||||||
|
# If interfaces are set, let zeroconf auto-detect the version
|
||||||
|
ip_version = None if CONF.mdns.interfaces else zeroconf.IPVersion.All
|
||||||
|
self._zc = zeroconf.Zeroconf(interfaces=interfaces,
|
||||||
|
ip_version=ip_version)
|
||||||
|
self._registered = []
|
||||||
|
|
||||||
|
def get_endpoint(self, service_type, skip_loopback=True,
|
||||||
|
skip_link_local=False):
|
||||||
|
"""Get an endpoint and its properties from mDNS.
|
||||||
|
|
||||||
|
If the requested endpoint is already in the built-in server cache, and
|
||||||
|
its TTL is not exceeded, the cached value is returned.
|
||||||
|
|
||||||
|
:param service_type: OpenStack service type.
|
||||||
|
:param skip_loopback: Whether to ignore loopback addresses.
|
||||||
|
:param skip_link_local: Whether to ignore link local V6 addresses.
|
||||||
|
:returns: tuple (endpoint URL, properties as a dict).
|
||||||
|
:raises: :exc:`.ServiceLookupFailure` if the service cannot be found.
|
||||||
|
"""
|
||||||
|
delay = 0.1
|
||||||
|
for attempt in range(CONF.mdns.lookup_attempts):
|
||||||
|
name = '%s.%s' % (service_type, _MDNS_DOMAIN)
|
||||||
|
info = self._zc.get_service_info(name, name)
|
||||||
|
if info is not None:
|
||||||
|
break
|
||||||
|
elif attempt == CONF.mdns.lookup_attempts - 1:
|
||||||
|
raise errors.ServiceLookupFailure(service=service_type)
|
||||||
|
else:
|
||||||
|
time.sleep(delay)
|
||||||
|
delay *= 2
|
||||||
|
|
||||||
|
all_addr = info.parsed_addresses()
|
||||||
|
|
||||||
|
# Try to find the first routable address
|
||||||
|
fallback = None
|
||||||
|
for addr in all_addr:
|
||||||
|
try:
|
||||||
|
loopback = ipaddress.ip_address(addr).is_loopback
|
||||||
|
except ValueError:
|
||||||
|
LOG.debug('Skipping invalid IP address %s', addr)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if loopback and skip_loopback:
|
||||||
|
LOG.debug('Skipping loopback IP address %s', addr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if utils.get_route_source(addr, skip_link_local):
|
||||||
|
address = addr
|
||||||
|
break
|
||||||
|
elif fallback is None:
|
||||||
|
fallback = addr
|
||||||
|
else:
|
||||||
|
if fallback is None:
|
||||||
|
raise errors.ServiceLookupFailure(
|
||||||
|
f'None of addresses {all_addr} for service %('
|
||||||
|
'{service_type} are valid')
|
||||||
|
else:
|
||||||
|
LOG.warning(f'None of addresses {all_addr} seem routable, '
|
||||||
|
f'using {fallback}')
|
||||||
|
address = fallback
|
||||||
|
|
||||||
|
properties = {}
|
||||||
|
for key, value in info.properties.items():
|
||||||
|
try:
|
||||||
|
if isinstance(key, bytes):
|
||||||
|
key = key.decode('utf-8')
|
||||||
|
except UnicodeError as exc:
|
||||||
|
raise errors.ServiceLookupFailure(
|
||||||
|
f'Invalid properties for service {service_type}. Cannot '
|
||||||
|
f'decode key {key!r}: {exc!r}')
|
||||||
|
try:
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
value = value.decode('utf-8')
|
||||||
|
except UnicodeError as exc:
|
||||||
|
LOG.debug('Cannot convert value %(value)r for key %(key)s '
|
||||||
|
'to string, assuming binary: %(exc)s',
|
||||||
|
{'key': key, 'value': value, 'exc': exc})
|
||||||
|
|
||||||
|
properties[key] = value
|
||||||
|
|
||||||
|
path = properties.pop('path', '')
|
||||||
|
protocol = properties.pop('protocol', None)
|
||||||
|
if not protocol:
|
||||||
|
if info.port == 80:
|
||||||
|
protocol = 'http'
|
||||||
|
else:
|
||||||
|
protocol = 'https'
|
||||||
|
|
||||||
|
if info.server.endswith('.local.'):
|
||||||
|
# Local hostname means that the catalog lists an IP address,
|
||||||
|
# so use it
|
||||||
|
host = address
|
||||||
|
if int(ipaddress.ip_address(host).version) == 6:
|
||||||
|
host = '[%s]' % host
|
||||||
|
else:
|
||||||
|
# Otherwise use the provided hostname.
|
||||||
|
host = info.server.rstrip('.')
|
||||||
|
|
||||||
|
return ('{proto}://{host}:{port}{path}'.format(proto=protocol,
|
||||||
|
host=host,
|
||||||
|
port=info.port,
|
||||||
|
path=path),
|
||||||
|
properties)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Shut down mDNS and unregister services.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
If another server is running for the same services, it will
|
||||||
|
re-register them immediately.
|
||||||
|
"""
|
||||||
|
for info in self._registered:
|
||||||
|
try:
|
||||||
|
self._zc.unregister_service(info)
|
||||||
|
except Exception:
|
||||||
|
LOG.exception('Cound not unregister mDNS service %s', info)
|
||||||
|
self._zc.close()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
|
def get_endpoint(service_type):
|
||||||
|
"""Get an endpoint and its properties from mDNS.
|
||||||
|
|
||||||
|
If the requested endpoint is already in the built-in server cache, and
|
||||||
|
its TTL is not exceeded, the cached value is returned.
|
||||||
|
|
||||||
|
:param service_type: OpenStack service type.
|
||||||
|
:returns: tuple (endpoint URL, properties as a dict).
|
||||||
|
:raises: :exc:`.ServiceLookupFailure` if the service cannot be found.
|
||||||
|
"""
|
||||||
|
with Zeroconf() as zc:
|
||||||
|
return zc.get_endpoint(service_type)
|
||||||
|
|
||||||
|
|
||||||
|
def list_opts():
|
||||||
|
"""Entry point for oslo-config-generator."""
|
||||||
|
return [('mdns', opts)]
|
@ -18,7 +18,7 @@ import functools
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from ironic_python_agent.metrics_lib import metrics_exception as exception
|
from ironic_python_agent import errors
|
||||||
|
|
||||||
|
|
||||||
class Timer(object):
|
class Timer(object):
|
||||||
@ -28,7 +28,7 @@ class Timer(object):
|
|||||||
context manager, and emits the time as the metric value. It is bound to
|
context manager, and emits the time as the metric value. It is bound to
|
||||||
this MetricLogger. For example::
|
this MetricLogger. For example::
|
||||||
|
|
||||||
from ironic_lib import metrics_utils
|
from ironic_python_agent.metrics_lib import metrics_utils
|
||||||
|
|
||||||
METRICS = metrics_utils.get_metrics_logger()
|
METRICS = metrics_utils.get_metrics_logger()
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ class Counter(object):
|
|||||||
context manager is executed. It is bound to this MetricLogger. For
|
context manager is executed. It is bound to this MetricLogger. For
|
||||||
example::
|
example::
|
||||||
|
|
||||||
from ironic_lib import metrics_utils
|
from ironic_python_agent.metrics_lib import metrics_utils
|
||||||
|
|
||||||
METRICS = metrics_utils.get_metrics_logger()
|
METRICS = metrics_utils.get_metrics_logger()
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ class Gauge(object):
|
|||||||
every time the method is executed. It is bound to this MetricLogger. For
|
every time the method is executed. It is bound to this MetricLogger. For
|
||||||
example::
|
example::
|
||||||
|
|
||||||
from ironic_lib import metrics_utils
|
from ironic_python_agent.metrics_lib import metrics_utils
|
||||||
|
|
||||||
METRICS = metrics_utils.get_metrics_logger()
|
METRICS = metrics_utils.get_metrics_logger()
|
||||||
|
|
||||||
@ -291,7 +291,7 @@ class MetricLogger(object, metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
def get_metrics_data(self):
|
def get_metrics_data(self):
|
||||||
"""Return the metrics collection, if available."""
|
"""Return the metrics collection, if available."""
|
||||||
raise exception.MetricsNotSupported()
|
raise errors.MetricsNotSupported()
|
||||||
|
|
||||||
|
|
||||||
class NoopMetricLogger(MetricLogger):
|
class NoopMetricLogger(MetricLogger):
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
# Copyright 2010 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Ironic base exception handling.
|
|
||||||
|
|
||||||
Includes decorator for re-raising Ironic-type exceptions.
|
|
||||||
|
|
||||||
SHOULD include dedicated exception logging.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ironic_lib.exception import IronicException
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidMetricConfig(IronicException):
|
|
||||||
_msg_fmt = "Invalid value for metrics config option: %(reason)s"
|
|
||||||
|
|
||||||
|
|
||||||
class MetricsNotSupported(IronicException):
|
|
||||||
_msg_fmt = ("Metrics action is not supported. You may need to "
|
|
||||||
"adjust the [metrics] section in ironic.conf.")
|
|
@ -15,9 +15,9 @@
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent.metrics_lib import metrics
|
from ironic_python_agent.metrics_lib import metrics
|
||||||
from ironic_python_agent.metrics_lib import metrics_collector
|
from ironic_python_agent.metrics_lib import metrics_collector
|
||||||
from ironic_python_agent.metrics_lib import metrics_exception as exception
|
|
||||||
from ironic_python_agent.metrics_lib import metrics_statsd
|
from ironic_python_agent.metrics_lib import metrics_statsd
|
||||||
|
|
||||||
metrics_opts = [
|
metrics_opts = [
|
||||||
@ -70,7 +70,7 @@ def get_metrics_logger(prefix='', backend=None, host=None, delimiter='.'):
|
|||||||
msg = ("This metric prefix (%s) is of unsupported type. "
|
msg = ("This metric prefix (%s) is of unsupported type. "
|
||||||
"Value should be a string or None"
|
"Value should be a string or None"
|
||||||
% str(prefix))
|
% str(prefix))
|
||||||
raise exception.InvalidMetricConfig(msg)
|
raise errors.InvalidMetricConfig(msg)
|
||||||
|
|
||||||
if CONF.metrics.prepend_host and host:
|
if CONF.metrics.prepend_host and host:
|
||||||
if CONF.metrics.prepend_host_reverse:
|
if CONF.metrics.prepend_host_reverse:
|
||||||
@ -99,4 +99,4 @@ def get_metrics_logger(prefix='', backend=None, host=None, delimiter='.'):
|
|||||||
msg = ("The backend is set to an unsupported type: "
|
msg = ("The backend is set to an unsupported type: "
|
||||||
"%s. Value should be 'noop' or 'statsd'."
|
"%s. Value should be 'noop' or 'statsd'."
|
||||||
% backend)
|
% backend)
|
||||||
raise exception.InvalidMetricConfig(msg)
|
raise errors.InvalidMetricConfig(msg)
|
||||||
|
@ -26,8 +26,6 @@ import shutil
|
|||||||
import stat
|
import stat
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from ironic_lib import exception
|
|
||||||
from ironic_lib import utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
@ -39,7 +37,7 @@ import requests
|
|||||||
from ironic_python_agent import disk_utils
|
from ironic_python_agent import disk_utils
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import hardware
|
from ironic_python_agent import hardware
|
||||||
from ironic_python_agent import utils as ipa_utils
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
LOG = log.getLogger()
|
LOG = log.getLogger()
|
||||||
@ -72,20 +70,20 @@ def get_configdrive(configdrive, node_uuid, tempdir=None):
|
|||||||
# Check if the configdrive option is a HTTP URL or the content directly
|
# Check if the configdrive option is a HTTP URL or the content directly
|
||||||
is_url = _is_http_url(configdrive)
|
is_url = _is_http_url(configdrive)
|
||||||
if is_url:
|
if is_url:
|
||||||
verify, cert = ipa_utils.get_ssl_client_options(CONF)
|
verify, cert = utils.get_ssl_client_options(CONF)
|
||||||
timeout = CONF.image_download_connection_timeout
|
timeout = CONF.image_download_connection_timeout
|
||||||
# TODO(dtantsur): support proxy parameters from instance_info
|
# TODO(dtantsur): support proxy parameters from instance_info
|
||||||
try:
|
try:
|
||||||
resp = requests.get(configdrive, verify=verify, cert=cert,
|
resp = requests.get(configdrive, verify=verify, cert=cert,
|
||||||
timeout=timeout)
|
timeout=timeout)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
"Can't download the configdrive content for node %(node)s "
|
"Can't download the configdrive content for node %(node)s "
|
||||||
"from '%(url)s'. Reason: %(reason)s" %
|
"from '%(url)s'. Reason: %(reason)s" %
|
||||||
{'node': node_uuid, 'url': configdrive, 'reason': e})
|
{'node': node_uuid, 'url': configdrive, 'reason': e})
|
||||||
|
|
||||||
if resp.status_code >= 400:
|
if resp.status_code >= 400:
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
"Can't download the configdrive content for node %(node)s "
|
"Can't download the configdrive content for node %(node)s "
|
||||||
"from '%(url)s'. Got status code %(code)s, response "
|
"from '%(url)s'. Got status code %(code)s, response "
|
||||||
"body %(body)s" %
|
"body %(body)s" %
|
||||||
@ -122,7 +120,7 @@ def get_configdrive(configdrive, node_uuid, tempdir=None):
|
|||||||
'cls': type(exc).__name__})
|
'cls': type(exc).__name__})
|
||||||
if is_url:
|
if is_url:
|
||||||
error_msg += ' Downloaded from "%s".' % configdrive
|
error_msg += ' Downloaded from "%s".' % configdrive
|
||||||
raise exception.InstanceDeployFailure(error_msg)
|
raise errors.DeploymentError(error_msg)
|
||||||
|
|
||||||
configdrive_mb = 0
|
configdrive_mb = 0
|
||||||
with gzip.GzipFile('configdrive', 'rb', fileobj=data) as gunzipped:
|
with gzip.GzipFile('configdrive', 'rb', fileobj=data) as gunzipped:
|
||||||
@ -131,7 +129,7 @@ def get_configdrive(configdrive, node_uuid, tempdir=None):
|
|||||||
except EnvironmentError as e:
|
except EnvironmentError as e:
|
||||||
# Delete the created file
|
# Delete the created file
|
||||||
utils.unlink_without_raise(configdrive_file.name)
|
utils.unlink_without_raise(configdrive_file.name)
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
'Encountered error while decompressing and writing '
|
'Encountered error while decompressing and writing '
|
||||||
'config drive for node %(node)s. Error: %(exc)s' %
|
'config drive for node %(node)s. Error: %(exc)s' %
|
||||||
{'node': node_uuid, 'exc': e})
|
{'node': node_uuid, 'exc': e})
|
||||||
@ -169,7 +167,7 @@ def get_labelled_partition(device_path, label, node_uuid):
|
|||||||
'for node %(node)s. Error: %(error)s' %
|
'for node %(node)s. Error: %(error)s' %
|
||||||
{'disk': device_path, 'node': node_uuid, 'error': e})
|
{'disk': device_path, 'node': node_uuid, 'error': e})
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
|
|
||||||
found_part = None
|
found_part = None
|
||||||
if output:
|
if output:
|
||||||
@ -178,7 +176,7 @@ def get_labelled_partition(device_path, label, node_uuid):
|
|||||||
if found_part:
|
if found_part:
|
||||||
found_2 = '/dev/%(part)s' % {'part': dev['NAME'].strip()}
|
found_2 = '/dev/%(part)s' % {'part': dev['NAME'].strip()}
|
||||||
found = [found_part, found_2]
|
found = [found_part, found_2]
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
'More than one partition with label "%(label)s" '
|
'More than one partition with label "%(label)s" '
|
||||||
'exists on device %(device)s for node %(node)s: '
|
'exists on device %(device)s for node %(node)s: '
|
||||||
'%(found)s.' %
|
'%(found)s.' %
|
||||||
@ -269,7 +267,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
|
|||||||
root_part = part_dict.get('root')
|
root_part = part_dict.get('root')
|
||||||
|
|
||||||
if not disk_utils.is_block_device(root_part):
|
if not disk_utils.is_block_device(root_part):
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
"Root device '%s' not found" % root_part)
|
"Root device '%s' not found" % root_part)
|
||||||
|
|
||||||
for part in ('swap', 'ephemeral', 'configdrive',
|
for part in ('swap', 'ephemeral', 'configdrive',
|
||||||
@ -279,7 +277,7 @@ def work_on_disk(dev, root_mb, swap_mb, ephemeral_mb, ephemeral_format,
|
|||||||
"%(node)s.", {'part': part, 'dev': part_device,
|
"%(node)s.", {'part': part, 'dev': part_device,
|
||||||
'node': node_uuid})
|
'node': node_uuid})
|
||||||
if part_device and not disk_utils.is_block_device(part_device):
|
if part_device and not disk_utils.is_block_device(part_device):
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
"'%(partition)s' device '%(part_device)s' not found" %
|
"'%(partition)s' device '%(part_device)s' not found" %
|
||||||
{'partition': part, 'part_device': part_device})
|
{'partition': part, 'part_device': part_device})
|
||||||
|
|
||||||
@ -370,7 +368,7 @@ def create_config_drive_partition(node_uuid, device, configdrive):
|
|||||||
|
|
||||||
confdrive_mb, confdrive_file = get_configdrive(configdrive, node_uuid)
|
confdrive_mb, confdrive_file = get_configdrive(configdrive, node_uuid)
|
||||||
if confdrive_mb > MAX_CONFIG_DRIVE_SIZE_MB:
|
if confdrive_mb > MAX_CONFIG_DRIVE_SIZE_MB:
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
'Config drive size exceeds maximum limit of 64MiB. '
|
'Config drive size exceeds maximum limit of 64MiB. '
|
||||||
'Size of the given config drive is %(size)d MiB for '
|
'Size of the given config drive is %(size)d MiB for '
|
||||||
'node %(node)s.'
|
'node %(node)s.'
|
||||||
@ -406,13 +404,13 @@ def create_config_drive_partition(node_uuid, device, configdrive):
|
|||||||
pp_count, lp_count = disk_utils.count_mbr_partitions(
|
pp_count, lp_count = disk_utils.count_mbr_partitions(
|
||||||
device)
|
device)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
'Failed to check the number of primary partitions '
|
'Failed to check the number of primary partitions '
|
||||||
'present on %(dev)s for node %(node)s. Error: '
|
'present on %(dev)s for node %(node)s. Error: '
|
||||||
'%(error)s' % {'dev': device, 'node': node_uuid,
|
'%(error)s' % {'dev': device, 'node': node_uuid,
|
||||||
'error': e})
|
'error': e})
|
||||||
if pp_count > 3:
|
if pp_count > 3:
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
'Config drive cannot be created for node %(node)s. '
|
'Config drive cannot be created for node %(node)s. '
|
||||||
'Disk (%(dev)s) uses MBR partitioning and already '
|
'Disk (%(dev)s) uses MBR partitioning and already '
|
||||||
'has %(parts)d primary partitions.'
|
'has %(parts)d primary partitions.'
|
||||||
@ -443,7 +441,7 @@ def create_config_drive_partition(node_uuid, device, configdrive):
|
|||||||
for part in disk_utils.list_partitions(device)}
|
for part in disk_utils.list_partitions(device)}
|
||||||
new_part = set(new_parts) - set(cur_parts)
|
new_part = set(new_parts) - set(cur_parts)
|
||||||
if len(new_part) != 1:
|
if len(new_part) != 1:
|
||||||
raise exception.InstanceDeployFailure(
|
raise errors.DeploymentError(
|
||||||
'Disk partitioning failed on device %(device)s. '
|
'Disk partitioning failed on device %(device)s. '
|
||||||
'Unable to retrieve config drive partition '
|
'Unable to retrieve config drive partition '
|
||||||
'information.' % {'device': device})
|
'information.' % {'device': device})
|
||||||
@ -460,7 +458,7 @@ def create_config_drive_partition(node_uuid, device, configdrive):
|
|||||||
'disk': device, 'node': node_uuid,
|
'disk': device, 'node': node_uuid,
|
||||||
'uuid': part_uuid}
|
'uuid': part_uuid}
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
|
|
||||||
disk_utils.udev_settle()
|
disk_utils.udev_settle()
|
||||||
|
|
||||||
@ -488,7 +486,7 @@ def create_config_drive_partition(node_uuid, device, configdrive):
|
|||||||
"copied onto partition %(part)s",
|
"copied onto partition %(part)s",
|
||||||
{'node': node_uuid, 'part': config_drive_part})
|
{'node': node_uuid, 'part': config_drive_part})
|
||||||
|
|
||||||
except exception.InstanceDeployFailure:
|
except errors.DeploymentError:
|
||||||
# Since we no longer have a final action on the decorator, we need
|
# Since we no longer have a final action on the decorator, we need
|
||||||
# to catch the failure, and still perform the cleanup.
|
# to catch the failure, and still perform the cleanup.
|
||||||
if confdrive_file:
|
if confdrive_file:
|
||||||
@ -500,7 +498,7 @@ def create_config_drive_partition(node_uuid, device, configdrive):
|
|||||||
'for node %(node)s. Error: %(error)s' %
|
'for node %(node)s. Error: %(error)s' %
|
||||||
{'disk': device, 'node': node_uuid, 'error': e})
|
{'disk': device, 'node': node_uuid, 'error': e})
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
# If the configdrive was requested make sure we delete the file
|
# If the configdrive was requested make sure we delete the file
|
||||||
# after copying the content to the partition
|
# after copying the content to the partition
|
||||||
|
|
||||||
@ -577,7 +575,7 @@ def _try_build_fat32_config_drive(partition, confdrive_file):
|
|||||||
'System. Due to the nature of configuration drive, it could '
|
'System. Due to the nature of configuration drive, it could '
|
||||||
'have been incorrectly formatted. Operator investigation is '
|
'have been incorrectly formatted. Operator investigation is '
|
||||||
'required. Error: {}'.format(str(e)))
|
'required. Error: {}'.format(str(e)))
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
finally:
|
finally:
|
||||||
utils.execute('umount', conf_drive_temp)
|
utils.execute('umount', conf_drive_temp)
|
||||||
utils.execute('umount', new_drive_temp)
|
utils.execute('umount', new_drive_temp)
|
||||||
@ -604,7 +602,7 @@ def _is_disk_larger_than_max_size(device, node_uuid):
|
|||||||
'Error: %(error)s' %
|
'Error: %(error)s' %
|
||||||
{'disk': device, 'node': node_uuid, 'error': e})
|
{'disk': device, 'node': node_uuid, 'error': e})
|
||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.InstanceDeployFailure(msg)
|
raise errors.DeploymentError(msg)
|
||||||
|
|
||||||
disksize_mb = int(disksize_bytes.strip()) // 1024 // 1024
|
disksize_mb = int(disksize_bytes.strip()) // 1024 // 1024
|
||||||
|
|
||||||
@ -617,7 +615,7 @@ def get_partition(device, uuid):
|
|||||||
{'dev': device, 'uuid': uuid})
|
{'dev': device, 'uuid': uuid})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ipa_utils.rescan_device(device)
|
utils.rescan_device(device)
|
||||||
lsblk, _ = utils.execute(
|
lsblk, _ = utils.execute(
|
||||||
'lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE,LABEL', device)
|
'lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE,LABEL', device)
|
||||||
if lsblk:
|
if lsblk:
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ironic_lib import utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import imageutils
|
from oslo_utils import imageutils
|
||||||
@ -21,6 +20,7 @@ from oslo_utils import units
|
|||||||
import tenacity
|
import tenacity
|
||||||
|
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Imported from ironic_lib/qemu-img.py from commit
|
Imported from ironic_lib/qemu-img.py from commit
|
||||||
|
@ -14,10 +14,10 @@ import copy
|
|||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
from ironic_lib import utils as il_utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from ironic_python_agent import device_hints
|
||||||
from ironic_python_agent import disk_utils
|
from ironic_python_agent import disk_utils
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import utils
|
from ironic_python_agent import utils
|
||||||
@ -55,7 +55,7 @@ def get_block_devices_for_raid(block_devices, logical_disks):
|
|||||||
matching = []
|
matching = []
|
||||||
for phys_disk in logical_disk['physical_disks']:
|
for phys_disk in logical_disk['physical_disks']:
|
||||||
candidates = [
|
candidates = [
|
||||||
dev['name'] for dev in il_utils.find_devices_by_hints(
|
dev['name'] for dev in device_hints.find_devices_by_hints(
|
||||||
serialized_devs, phys_disk)
|
serialized_devs, phys_disk)
|
||||||
]
|
]
|
||||||
if not candidates:
|
if not candidates:
|
||||||
@ -404,7 +404,7 @@ def prepare_boot_partitions_for_softraid(device, holders, efi_part,
|
|||||||
utils.execute('wipefs', '-a', efi_part)
|
utils.execute('wipefs', '-a', efi_part)
|
||||||
else:
|
else:
|
||||||
fslabel = 'efi-part'
|
fslabel = 'efi-part'
|
||||||
il_utils.mkfs(fs='vfat', path=md_device, label=fslabel)
|
utils.mkfs(fs='vfat', path=md_device, label=fslabel)
|
||||||
|
|
||||||
return md_device
|
return md_device
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import ironic_lib
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_config import fixture as config_fixture
|
from oslo_config import fixture as config_fixture
|
||||||
@ -28,6 +27,7 @@ from oslotest import base as test_base
|
|||||||
from ironic_python_agent import config
|
from ironic_python_agent import config
|
||||||
from ironic_python_agent.extensions import base as ext_base
|
from ironic_python_agent.extensions import base as ext_base
|
||||||
from ironic_python_agent import hardware
|
from ironic_python_agent import hardware
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@ -56,12 +56,11 @@ class IronicAgentTest(test_base.BaseTestCase):
|
|||||||
# mock it causes things to not work correctly. As doing an
|
# mock it causes things to not work correctly. As doing an
|
||||||
# autospec=True causes strangeness. By using a simple function we
|
# autospec=True causes strangeness. By using a simple function we
|
||||||
# can then mock it without issue.
|
# can then mock it without issue.
|
||||||
self.patch(ironic_lib.utils, 'execute', do_not_call)
|
self.patch(utils, 'execute', do_not_call)
|
||||||
self.patch(processutils, 'execute', do_not_call)
|
self.patch(processutils, 'execute', do_not_call)
|
||||||
self.patch(subprocess, 'call', do_not_call)
|
self.patch(subprocess, 'call', do_not_call)
|
||||||
self.patch(subprocess, 'check_call', do_not_call)
|
self.patch(subprocess, 'check_call', do_not_call)
|
||||||
self.patch(subprocess, 'check_output', do_not_call)
|
self.patch(subprocess, 'check_output', do_not_call)
|
||||||
# ironic_python_agent.utils.execute is an alias of ironic_lib one
|
|
||||||
|
|
||||||
ext_base._EXT_MANAGER = None
|
ext_base._EXT_MANAGER = None
|
||||||
hardware._CACHED_HW_INFO = None
|
hardware._CACHED_HW_INFO = None
|
||||||
@ -87,5 +86,5 @@ class IronicAgentTest(test_base.BaseTestCase):
|
|||||||
def do_not_call(*args, **kwargs):
|
def do_not_call(*args, **kwargs):
|
||||||
"""Helper function to raise an exception if it is called"""
|
"""Helper function to raise an exception if it is called"""
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Don't call ironic_lib.utils.execute() / "
|
"Don't call utils.execute() / "
|
||||||
"processutils.execute() or similar functions in tests!")
|
"processutils.execute() or similar functions in tests!")
|
||||||
|
@ -18,7 +18,6 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from ironic_lib import utils as ilib_utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
|
|
||||||
from ironic_python_agent import disk_utils
|
from ironic_python_agent import disk_utils
|
||||||
@ -30,13 +29,14 @@ from ironic_python_agent import partition_utils
|
|||||||
from ironic_python_agent import raid_utils
|
from ironic_python_agent import raid_utils
|
||||||
from ironic_python_agent.tests.unit import base
|
from ironic_python_agent.tests.unit import base
|
||||||
from ironic_python_agent.tests.unit.samples import hardware_samples as hws
|
from ironic_python_agent.tests.unit.samples import hardware_samples as hws
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
EFI_RESULT = ''.encode('utf-16')
|
EFI_RESULT = ''.encode('utf-16')
|
||||||
|
|
||||||
|
|
||||||
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
||||||
@mock.patch.object(ilib_utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
@mock.patch.object(tempfile, 'mkdtemp', lambda *_: '/tmp/fake-dir')
|
@mock.patch.object(tempfile, 'mkdtemp', lambda *_: '/tmp/fake-dir')
|
||||||
@mock.patch.object(shutil, 'rmtree', lambda *_: None)
|
@mock.patch.object(shutil, 'rmtree', lambda *_: None)
|
||||||
class TestImageExtension(base.IronicAgentTest):
|
class TestImageExtension(base.IronicAgentTest):
|
||||||
|
@ -17,7 +17,6 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from ironic_lib import exception
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
@ -338,7 +337,7 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
image_info = _build_fake_image_info()
|
image_info = _build_fake_image_info()
|
||||||
validate_mock.return_value = (image_info['disk_format'], 0)
|
validate_mock.return_value = (image_info['disk_format'], 0)
|
||||||
|
|
||||||
fix_gpt_mock.side_effect = exception.InstanceDeployFailure
|
fix_gpt_mock.side_effect = errors.DeploymentError
|
||||||
standby._write_image(image_info, device)
|
standby._write_image(image_info, device)
|
||||||
|
|
||||||
@mock.patch('ironic_python_agent.qemu_img.convert_image', autospec=True)
|
@mock.patch('ironic_python_agent.qemu_img.convert_image', autospec=True)
|
||||||
@ -896,7 +895,7 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
|
|
||||||
@mock.patch('ironic_python_agent.disk_utils.get_disk_identifier',
|
@mock.patch('ironic_python_agent.disk_utils.get_disk_identifier',
|
||||||
lambda dev: 'ROOT')
|
lambda dev: 'ROOT')
|
||||||
@mock.patch('ironic_lib.utils.execute', autospec=True)
|
@mock.patch('ironic_python_agent.utils.execute', autospec=True)
|
||||||
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch.object(partition_utils, 'create_config_drive_partition',
|
@mock.patch.object(partition_utils, 'create_config_drive_partition',
|
||||||
@ -947,7 +946,7 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
self.assertEqual({'root uuid': 'ROOT'},
|
self.assertEqual({'root uuid': 'ROOT'},
|
||||||
self.agent_extension.partition_uuids)
|
self.agent_extension.partition_uuids)
|
||||||
|
|
||||||
@mock.patch('ironic_lib.utils.execute', autospec=True)
|
@mock.patch('ironic_python_agent.utils.execute', autospec=True)
|
||||||
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch.object(partition_utils, 'create_config_drive_partition',
|
@mock.patch.object(partition_utils, 'create_config_drive_partition',
|
||||||
@ -1020,7 +1019,7 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
|
|
||||||
@mock.patch('ironic_python_agent.disk_utils.get_disk_identifier',
|
@mock.patch('ironic_python_agent.disk_utils.get_disk_identifier',
|
||||||
lambda dev: 'ROOT')
|
lambda dev: 'ROOT')
|
||||||
@mock.patch('ironic_lib.utils.execute', autospec=True)
|
@mock.patch('ironic_python_agent.utils.execute', autospec=True)
|
||||||
@mock.patch.object(partition_utils, 'create_config_drive_partition',
|
@mock.patch.object(partition_utils, 'create_config_drive_partition',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
||||||
@ -1112,7 +1111,7 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
|
|
||||||
@mock.patch('ironic_python_agent.disk_utils.get_disk_identifier',
|
@mock.patch('ironic_python_agent.disk_utils.get_disk_identifier',
|
||||||
side_effect=OSError, autospec=True)
|
side_effect=OSError, autospec=True)
|
||||||
@mock.patch('ironic_lib.utils.execute',
|
@mock.patch('ironic_python_agent.utils.execute',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -1164,7 +1163,7 @@ class TestStandbyExtension(base.IronicAgentTest):
|
|||||||
attempts=mock.ANY)
|
attempts=mock.ANY)
|
||||||
self.assertEqual({}, self.agent_extension.partition_uuids)
|
self.assertEqual({}, self.agent_extension.partition_uuids)
|
||||||
|
|
||||||
@mock.patch('ironic_lib.utils.execute', mock.Mock())
|
@mock.patch('ironic_python_agent.utils.execute', mock.Mock())
|
||||||
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
@mock.patch('ironic_python_agent.disk_utils.list_partitions',
|
||||||
lambda _dev: [mock.Mock()])
|
lambda _dev: [mock.Mock()])
|
||||||
@mock.patch('ironic_python_agent.disk_utils.get_disk_identifier',
|
@mock.patch('ironic_python_agent.disk_utils.get_disk_identifier',
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent.metrics_lib import metrics as metricslib
|
from ironic_python_agent.metrics_lib import metrics as metricslib
|
||||||
from ironic_python_agent.metrics_lib import metrics_exception as exception
|
|
||||||
from ironic_python_agent.metrics_lib import metrics_statsd
|
from ironic_python_agent.metrics_lib import metrics_statsd
|
||||||
from ironic_python_agent.metrics_lib import metrics_utils
|
from ironic_python_agent.metrics_lib import metrics_utils
|
||||||
from ironic_python_agent.tests.unit import base
|
from ironic_python_agent.tests.unit import base
|
||||||
@ -40,15 +40,15 @@ class TestGetLogger(base.IronicAgentTest):
|
|||||||
CONF.clear_override('backend', group='metrics')
|
CONF.clear_override('backend', group='metrics')
|
||||||
|
|
||||||
def test_nonexisting_backend(self):
|
def test_nonexisting_backend(self):
|
||||||
self.assertRaises(exception.InvalidMetricConfig,
|
self.assertRaises(errors.InvalidMetricConfig,
|
||||||
metrics_utils.get_metrics_logger, 'foo', 'test')
|
metrics_utils.get_metrics_logger, 'foo', 'test')
|
||||||
|
|
||||||
def test_numeric_prefix(self):
|
def test_numeric_prefix(self):
|
||||||
self.assertRaises(exception.InvalidMetricConfig,
|
self.assertRaises(errors.InvalidMetricConfig,
|
||||||
metrics_utils.get_metrics_logger, 1)
|
metrics_utils.get_metrics_logger, 1)
|
||||||
|
|
||||||
def test_numeric_list_prefix(self):
|
def test_numeric_list_prefix(self):
|
||||||
self.assertRaises(exception.InvalidMetricConfig,
|
self.assertRaises(errors.InvalidMetricConfig,
|
||||||
metrics_utils.get_metrics_logger, (1, 2))
|
metrics_utils.get_metrics_logger, (1, 2))
|
||||||
|
|
||||||
def test_default_prefix(self):
|
def test_default_prefix(self):
|
||||||
|
@ -19,7 +19,6 @@ import time
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
from unittest.mock import sentinel
|
from unittest.mock import sentinel
|
||||||
|
|
||||||
from ironic_lib import exception as lib_exc
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from stevedore import extension
|
from stevedore import extension
|
||||||
@ -323,7 +322,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
self.agent.heartbeater.start.assert_called_once_with()
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
self.assertTrue(CONF.md5_enabled)
|
self.assertTrue(CONF.md5_enabled)
|
||||||
|
|
||||||
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
@mock.patch('ironic_python_agent.mdns.get_endpoint', autospec=True)
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
mock.Mock())
|
mock.Mock())
|
||||||
@ -378,7 +377,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_dispatch.call_args_list)
|
mock_dispatch.call_args_list)
|
||||||
self.agent.heartbeater.start.assert_called_once_with()
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
|
|
||||||
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
@mock.patch('ironic_python_agent.mdns.get_endpoint', autospec=True)
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
mock.Mock())
|
mock.Mock())
|
||||||
@ -625,7 +624,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
self.agent.heartbeater.start.assert_called_once_with()
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
|
|
||||||
@mock.patch.object(hardware, '_enable_multipath', autospec=True)
|
@mock.patch.object(hardware, '_enable_multipath', autospec=True)
|
||||||
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
@mock.patch('ironic_python_agent.mdns.get_endpoint', autospec=True)
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
mock.Mock())
|
mock.Mock())
|
||||||
@ -644,7 +643,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_wait,
|
mock_wait,
|
||||||
mock_mdns,
|
mock_mdns,
|
||||||
mock_mpath):
|
mock_mpath):
|
||||||
mock_mdns.side_effect = lib_exc.ServiceLookupFailure()
|
mock_mdns.side_effect = errors.ServiceLookupFailure()
|
||||||
# If inspection_callback_url is configured and api_url is not when the
|
# If inspection_callback_url is configured and api_url is not when the
|
||||||
# agent starts, ensure that the inspection will be called and wsgi
|
# agent starts, ensure that the inspection will be called and wsgi
|
||||||
# server will work as usual. Also, make sure api_client and heartbeater
|
# server will work as usual. Also, make sure api_client and heartbeater
|
||||||
@ -684,7 +683,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
self.assertFalse(mock_dispatch.called)
|
self.assertFalse(mock_dispatch.called)
|
||||||
|
|
||||||
@mock.patch.object(hardware, '_enable_multipath', autospec=True)
|
@mock.patch.object(hardware, '_enable_multipath', autospec=True)
|
||||||
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
@mock.patch('ironic_python_agent.mdns.get_endpoint', autospec=True)
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
mock.Mock())
|
mock.Mock())
|
||||||
@ -703,7 +702,7 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_wait,
|
mock_wait,
|
||||||
mock_mdns,
|
mock_mdns,
|
||||||
mock_mpath):
|
mock_mpath):
|
||||||
mock_mdns.side_effect = lib_exc.ServiceLookupFailure()
|
mock_mdns.side_effect = errors.ServiceLookupFailure()
|
||||||
# If both api_url and inspection_callback_url are not configured when
|
# If both api_url and inspection_callback_url are not configured when
|
||||||
# the agent starts, ensure that the inspection will be skipped and wsgi
|
# the agent starts, ensure that the inspection will be skipped and wsgi
|
||||||
# server will work as usual. Also, make sure api_client and heartbeater
|
# server will work as usual. Also, make sure api_client and heartbeater
|
||||||
|
@ -14,20 +14,19 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import ironic_lib
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
|
|
||||||
from ironic_python_agent.tests.unit import base as ironic_agent_base
|
from ironic_python_agent.tests.unit import base
|
||||||
from ironic_python_agent import utils
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
class BlockExecuteTestCase(ironic_agent_base.IronicAgentTest):
|
class BlockExecuteTestCase(base.IronicAgentTest):
|
||||||
"""Test to ensure we block access to the 'execute' type functions"""
|
"""Test to ensure we block access to the 'execute' type functions"""
|
||||||
|
|
||||||
def test_exception_raised_for_execute(self):
|
def test_exception_raised_for_execute(self):
|
||||||
execute_functions = (ironic_lib.utils.execute, processutils.execute,
|
execute_functions = (processutils.execute, subprocess.call,
|
||||||
subprocess.call, subprocess.check_call,
|
subprocess.check_call, subprocess.check_output,
|
||||||
subprocess.check_output, utils.execute)
|
utils.execute)
|
||||||
|
|
||||||
for function_name in execute_functions:
|
for function_name in execute_functions:
|
||||||
exc = self.assertRaises(Exception, function_name, ["echo", "%s" % function_name]) # noqa
|
exc = self.assertRaises(Exception, function_name, ["echo", "%s" % function_name]) # noqa
|
||||||
@ -35,7 +34,7 @@ class BlockExecuteTestCase(ironic_agent_base.IronicAgentTest):
|
|||||||
# get H202 error in 'pep8' check.
|
# get H202 error in 'pep8' check.
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"Don't call ironic_lib.utils.execute() / "
|
"Don't call utils.execute() / "
|
||||||
"processutils.execute() or similar functions in tests!",
|
"processutils.execute() or similar functions in tests!",
|
||||||
"%s" % exc)
|
"%s" % exc)
|
||||||
|
|
||||||
@ -51,17 +50,14 @@ class BlockExecuteTestCase(ironic_agent_base.IronicAgentTest):
|
|||||||
self.assertEqual(2, mock_exec.call_count)
|
self.assertEqual(2, mock_exec.call_count)
|
||||||
|
|
||||||
|
|
||||||
class DontBlockExecuteTestCase(ironic_agent_base.IronicAgentTest):
|
class DontBlockExecuteTestCase(base.IronicAgentTest):
|
||||||
"""Ensure we can turn off blocking access to 'execute' type functions"""
|
"""Ensure we can turn off blocking access to 'execute' type functions"""
|
||||||
|
|
||||||
# Don't block the execute function
|
# Don't block the execute function
|
||||||
block_execute = False
|
block_execute = False
|
||||||
|
|
||||||
@mock.patch.object(ironic_lib.utils, "execute", autospec=True)
|
@mock.patch.object(utils, "execute", autospec=True)
|
||||||
def test_no_exception_raised_for_execute(self, mock_exec):
|
def test_no_exception_raised_for_execute(self, mock_exec):
|
||||||
# Make sure we can call utils.execute() even though we didn't mock it.
|
|
||||||
# We do mock ironic_lib.utils.execute() so we don't actually execute
|
|
||||||
# anything.
|
|
||||||
utils.execute("ls")
|
utils.execute("ls")
|
||||||
utils.execute("echo")
|
utils.execute("echo")
|
||||||
self.assertEqual(2, mock_exec.call_count)
|
self.assertEqual(2, mock_exec.call_count)
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from ironic_lib import utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from tooz import coordination
|
from tooz import coordination
|
||||||
|
|
||||||
@ -20,6 +19,7 @@ from ironic_python_agent import burnin
|
|||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import hardware
|
from ironic_python_agent import hardware
|
||||||
from ironic_python_agent.tests.unit import base
|
from ironic_python_agent.tests.unit import base
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
SMART_OUTPUT_JSON_COMPLETED = ("""
|
SMART_OUTPUT_JSON_COMPLETED = ("""
|
||||||
|
330
ironic_python_agent/tests/unit/test_device_hints.py
Normal file
330
ironic_python_agent/tests/unit/test_device_hints.py
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from ironic_python_agent import device_hints
|
||||||
|
from ironic_python_agent.tests.unit import base
|
||||||
|
|
||||||
|
|
||||||
|
class ParseRootDeviceTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_without_operators(self):
|
||||||
|
root_device = {
|
||||||
|
'wwn': '123456', 'model': 'FOO model', 'size': 12345,
|
||||||
|
'serial': 'foo-serial', 'vendor': 'foo VENDOR with space',
|
||||||
|
'name': '/dev/sda', 'wwn_with_extension': '123456111',
|
||||||
|
'wwn_vendor_extension': '111', 'rotational': True,
|
||||||
|
'hctl': '1:0:0:0', 'by_path': '/dev/disk/by-path/1:0:0:0'}
|
||||||
|
result = device_hints.parse_root_device_hints(root_device)
|
||||||
|
expected = {
|
||||||
|
'wwn': 's== 123456', 'model': 's== foo%20model',
|
||||||
|
'size': '== 12345', 'serial': 's== foo-serial',
|
||||||
|
'vendor': 's== foo%20vendor%20with%20space',
|
||||||
|
'name': 's== /dev/sda', 'wwn_with_extension': 's== 123456111',
|
||||||
|
'wwn_vendor_extension': 's== 111', 'rotational': True,
|
||||||
|
'hctl': 's== 1%3A0%3A0%3A0',
|
||||||
|
'by_path': 's== /dev/disk/by-path/1%3A0%3A0%3A0'}
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_with_operators(self):
|
||||||
|
root_device = {
|
||||||
|
'wwn': 's== 123456', 'model': 's== foo MODEL', 'size': '>= 12345',
|
||||||
|
'serial': 's!= foo-serial', 'vendor': 's== foo VENDOR with space',
|
||||||
|
'name': '<or> /dev/sda <or> /dev/sdb',
|
||||||
|
'wwn_with_extension': 's!= 123456111',
|
||||||
|
'wwn_vendor_extension': 's== 111', 'rotational': True,
|
||||||
|
'hctl': 's== 1:0:0:0', 'by_path': 's== /dev/disk/by-path/1:0:0:0'}
|
||||||
|
|
||||||
|
# Validate strings being normalized
|
||||||
|
expected = copy.deepcopy(root_device)
|
||||||
|
expected['model'] = 's== foo%20model'
|
||||||
|
expected['vendor'] = 's== foo%20vendor%20with%20space'
|
||||||
|
expected['hctl'] = 's== 1%3A0%3A0%3A0'
|
||||||
|
expected['by_path'] = 's== /dev/disk/by-path/1%3A0%3A0%3A0'
|
||||||
|
|
||||||
|
result = device_hints.parse_root_device_hints(root_device)
|
||||||
|
# The hints already contain the operators, make sure we keep it
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_string_compare_operator_name(self):
|
||||||
|
root_device = {'name': 's== /dev/sdb'}
|
||||||
|
# Validate strings being normalized
|
||||||
|
expected = copy.deepcopy(root_device)
|
||||||
|
result = device_hints.parse_root_device_hints(root_device)
|
||||||
|
# The hints already contain the operators, make sure we keep it
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_no_hints(self):
|
||||||
|
result = device_hints.parse_root_device_hints({})
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_convert_size(self):
|
||||||
|
for size in (12345, '12345'):
|
||||||
|
result = device_hints.parse_root_device_hints({'size': size})
|
||||||
|
self.assertEqual({'size': '== 12345'}, result)
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_size(self):
|
||||||
|
for value in ('not-int', -123, 0):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'size': value})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_int_or(self):
|
||||||
|
expr = '<or> 123 <or> 456 <or> 789'
|
||||||
|
result = device_hints.parse_root_device_hints({'size': expr})
|
||||||
|
self.assertEqual({'size': expr}, result)
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_int_or_invalid(self):
|
||||||
|
expr = '<or> 123 <or> non-int <or> 789'
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'size': expr})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_string_or_space(self):
|
||||||
|
expr = '<or> foo <or> foo bar <or> bar'
|
||||||
|
expected = '<or> foo <or> foo%20bar <or> bar'
|
||||||
|
result = device_hints.parse_root_device_hints({'model': expr})
|
||||||
|
self.assertEqual({'model': expected}, result)
|
||||||
|
|
||||||
|
def _parse_root_device_hints_convert_rotational(self, values,
|
||||||
|
expected_value):
|
||||||
|
for value in values:
|
||||||
|
result = device_hints.parse_root_device_hints(
|
||||||
|
{'rotational': value})
|
||||||
|
self.assertEqual({'rotational': expected_value}, result)
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_convert_rotational(self):
|
||||||
|
self._parse_root_device_hints_convert_rotational(
|
||||||
|
(True, 'true', 'on', 'y', 'yes'), True)
|
||||||
|
|
||||||
|
self._parse_root_device_hints_convert_rotational(
|
||||||
|
(False, 'false', 'off', 'n', 'no'), False)
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_rotational(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'rotational': 'not-bool'})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_wwn(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'wwn': 123})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_wwn_with_extension(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'wwn_with_extension': 123})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_wwn_vendor_extension(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'wwn_vendor_extension': 123})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_model(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'model': 123})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_serial(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'serial': 123})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_vendor(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'vendor': 123})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_name(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'name': 123})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_hctl(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'hctl': 123})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_invalid_by_path(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'by_path': 123})
|
||||||
|
|
||||||
|
def test_parse_root_device_hints_non_existent_hint(self):
|
||||||
|
self.assertRaises(ValueError, device_hints.parse_root_device_hints,
|
||||||
|
{'non-existent': 'foo'})
|
||||||
|
|
||||||
|
def test_extract_hint_operator_and_values_single_value(self):
|
||||||
|
expected = {'op': '>=', 'values': ['123']}
|
||||||
|
self.assertEqual(
|
||||||
|
expected, device_hints._extract_hint_operator_and_values(
|
||||||
|
'>= 123', 'size'))
|
||||||
|
|
||||||
|
def test_extract_hint_operator_and_values_multiple_values(self):
|
||||||
|
expected = {'op': '<or>', 'values': ['123', '456', '789']}
|
||||||
|
expr = '<or> 123 <or> 456 <or> 789'
|
||||||
|
self.assertEqual(
|
||||||
|
expected,
|
||||||
|
device_hints._extract_hint_operator_and_values(expr, 'size'))
|
||||||
|
|
||||||
|
def test_extract_hint_operator_and_values_multiple_values_space(self):
|
||||||
|
expected = {'op': '<or>', 'values': ['foo', 'foo bar', 'bar']}
|
||||||
|
expr = '<or> foo <or> foo bar <or> bar'
|
||||||
|
self.assertEqual(
|
||||||
|
expected,
|
||||||
|
device_hints._extract_hint_operator_and_values(expr, 'model'))
|
||||||
|
|
||||||
|
def test_extract_hint_operator_and_values_no_operator(self):
|
||||||
|
expected = {'op': '', 'values': ['123']}
|
||||||
|
self.assertEqual(
|
||||||
|
expected,
|
||||||
|
device_hints._extract_hint_operator_and_values('123', 'size'))
|
||||||
|
|
||||||
|
def test_extract_hint_operator_and_values_empty_value(self):
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError,
|
||||||
|
device_hints._extract_hint_operator_and_values, '', 'size')
|
||||||
|
|
||||||
|
def test_extract_hint_operator_and_values_integer(self):
|
||||||
|
expected = {'op': '', 'values': ['123']}
|
||||||
|
self.assertEqual(
|
||||||
|
expected,
|
||||||
|
device_hints._extract_hint_operator_and_values(123, 'size'))
|
||||||
|
|
||||||
|
def test__append_operator_to_hints(self):
|
||||||
|
root_device = {'serial': 'foo', 'size': 12345,
|
||||||
|
'model': 'foo model', 'rotational': True}
|
||||||
|
expected = {'serial': 's== foo', 'size': '== 12345',
|
||||||
|
'model': 's== foo model', 'rotational': True}
|
||||||
|
|
||||||
|
result = device_hints._append_operator_to_hints(root_device)
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_normalize_hint_expression_or(self):
|
||||||
|
expr = '<or> foo <or> foo bar <or> bar'
|
||||||
|
expected = '<or> foo <or> foo%20bar <or> bar'
|
||||||
|
result = device_hints._normalize_hint_expression(expr, 'model')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_normalize_hint_expression_in(self):
|
||||||
|
expr = '<in> foo <in> foo bar <in> bar'
|
||||||
|
expected = '<in> foo <in> foo%20bar <in> bar'
|
||||||
|
result = device_hints._normalize_hint_expression(expr, 'model')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_normalize_hint_expression_op_space(self):
|
||||||
|
expr = 's== test string with space'
|
||||||
|
expected = 's== test%20string%20with%20space'
|
||||||
|
result = device_hints._normalize_hint_expression(expr, 'model')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_normalize_hint_expression_op_no_space(self):
|
||||||
|
expr = 's!= SpongeBob'
|
||||||
|
expected = 's!= spongebob'
|
||||||
|
result = device_hints._normalize_hint_expression(expr, 'model')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_normalize_hint_expression_no_op_space(self):
|
||||||
|
expr = 'no operators'
|
||||||
|
expected = 'no%20operators'
|
||||||
|
result = device_hints._normalize_hint_expression(expr, 'model')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_normalize_hint_expression_no_op_no_space(self):
|
||||||
|
expr = 'NoSpace'
|
||||||
|
expected = 'nospace'
|
||||||
|
result = device_hints._normalize_hint_expression(expr, 'model')
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test_normalize_hint_expression_empty_value(self):
|
||||||
|
self.assertRaises(
|
||||||
|
ValueError, device_hints._normalize_hint_expression, '', 'size')
|
||||||
|
|
||||||
|
|
||||||
|
class MatchRootDeviceTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(MatchRootDeviceTestCase, self).setUp()
|
||||||
|
self.devices = [
|
||||||
|
{'name': '/dev/sda', 'size': 64424509440, 'model': 'ok model',
|
||||||
|
'serial': 'fakeserial'},
|
||||||
|
{'name': '/dev/sdb', 'size': 128849018880, 'model': 'big model',
|
||||||
|
'serial': 'veryfakeserial', 'rotational': 'yes'},
|
||||||
|
{'name': '/dev/sdc', 'size': 10737418240, 'model': 'small model',
|
||||||
|
'serial': 'veryveryfakeserial', 'rotational': False},
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_match_root_device_hints_one_hint(self):
|
||||||
|
root_device_hints = {'size': '>= 70'}
|
||||||
|
dev = device_hints.match_root_device_hints(
|
||||||
|
self.devices, root_device_hints)
|
||||||
|
self.assertEqual('/dev/sdb', dev['name'])
|
||||||
|
|
||||||
|
def test_match_root_device_hints_rotational(self):
|
||||||
|
root_device_hints = {'rotational': False}
|
||||||
|
dev = device_hints.match_root_device_hints(
|
||||||
|
self.devices, root_device_hints)
|
||||||
|
self.assertEqual('/dev/sdc', dev['name'])
|
||||||
|
|
||||||
|
def test_match_root_device_hints_rotational_convert_devices_bool(self):
|
||||||
|
root_device_hints = {'size': '>=100', 'rotational': True}
|
||||||
|
dev = device_hints.match_root_device_hints(
|
||||||
|
self.devices, root_device_hints)
|
||||||
|
self.assertEqual('/dev/sdb', dev['name'])
|
||||||
|
|
||||||
|
def test_match_root_device_hints_multiple_hints(self):
|
||||||
|
root_device_hints = {'size': '>= 50', 'model': 's==big model',
|
||||||
|
'serial': 's==veryfakeserial'}
|
||||||
|
dev = device_hints.match_root_device_hints(
|
||||||
|
self.devices, root_device_hints)
|
||||||
|
self.assertEqual('/dev/sdb', dev['name'])
|
||||||
|
|
||||||
|
def test_match_root_device_hints_multiple_hints2(self):
|
||||||
|
root_device_hints = {
|
||||||
|
'size': '<= 20',
|
||||||
|
'model': '<or> model 5 <or> foomodel <or> small model <or>',
|
||||||
|
'serial': 's== veryveryfakeserial'}
|
||||||
|
dev = device_hints.match_root_device_hints(
|
||||||
|
self.devices, root_device_hints)
|
||||||
|
self.assertEqual('/dev/sdc', dev['name'])
|
||||||
|
|
||||||
|
def test_match_root_device_hints_multiple_hints3(self):
|
||||||
|
root_device_hints = {'rotational': False, 'model': '<in> small'}
|
||||||
|
dev = device_hints.match_root_device_hints(
|
||||||
|
self.devices, root_device_hints)
|
||||||
|
self.assertEqual('/dev/sdc', dev['name'])
|
||||||
|
|
||||||
|
def test_match_root_device_hints_no_operators(self):
|
||||||
|
root_device_hints = {'size': '120', 'model': 'big model',
|
||||||
|
'serial': 'veryfakeserial'}
|
||||||
|
dev = device_hints.match_root_device_hints(
|
||||||
|
self.devices, root_device_hints)
|
||||||
|
self.assertEqual('/dev/sdb', dev['name'])
|
||||||
|
|
||||||
|
def test_match_root_device_hints_no_device_found(self):
|
||||||
|
root_device_hints = {'size': '>=50', 'model': 's==foo'}
|
||||||
|
dev = device_hints.match_root_device_hints(
|
||||||
|
self.devices, root_device_hints)
|
||||||
|
self.assertIsNone(dev)
|
||||||
|
|
||||||
|
def test_match_root_device_hints_empty_device_attribute(self):
|
||||||
|
empty_dev = [{'name': '/dev/sda', 'model': ' '}]
|
||||||
|
dev = device_hints.match_root_device_hints(
|
||||||
|
empty_dev, {'model': 'foo'})
|
||||||
|
self.assertIsNone(dev)
|
||||||
|
|
||||||
|
def test_find_devices_all(self):
|
||||||
|
root_device_hints = {'size': '>= 10'}
|
||||||
|
devs = list(device_hints.find_devices_by_hints(self.devices,
|
||||||
|
root_device_hints))
|
||||||
|
self.assertEqual(self.devices, devs)
|
||||||
|
|
||||||
|
def test_find_devices_none(self):
|
||||||
|
root_device_hints = {'size': '>= 100500'}
|
||||||
|
devs = list(device_hints.find_devices_by_hints(self.devices,
|
||||||
|
root_device_hints))
|
||||||
|
self.assertEqual([], devs)
|
||||||
|
|
||||||
|
def test_find_devices_name(self):
|
||||||
|
root_device_hints = {'name': 's== /dev/sda'}
|
||||||
|
devs = list(device_hints.find_devices_by_hints(self.devices,
|
||||||
|
root_device_hints))
|
||||||
|
self.assertEqual([self.devices[0]], devs)
|
@ -15,17 +15,16 @@
|
|||||||
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from ironic_lib import exception
|
|
||||||
from ironic_lib.tests import base
|
|
||||||
from ironic_lib import utils
|
|
||||||
|
|
||||||
from ironic_python_agent import disk_partitioner
|
from ironic_python_agent import disk_partitioner
|
||||||
|
from ironic_python_agent import errors
|
||||||
|
from ironic_python_agent.tests.unit import base
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
CONF = disk_partitioner.CONF
|
CONF = disk_partitioner.CONF
|
||||||
|
|
||||||
|
|
||||||
class DiskPartitionerTestCase(base.IronicLibTestCase):
|
class DiskPartitionerTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
def test_add_partition(self):
|
def test_add_partition(self):
|
||||||
dp = disk_partitioner.DiskPartitioner('/dev/fake')
|
dp = disk_partitioner.DiskPartitioner('/dev/fake')
|
||||||
@ -157,7 +156,7 @@ class DiskPartitionerTestCase(base.IronicLibTestCase):
|
|||||||
# Test as if the 'busybox' version of 'fuser' which does not have
|
# Test as if the 'busybox' version of 'fuser' which does not have
|
||||||
# stderr output
|
# stderr output
|
||||||
mock_utils_exc.return_value = ("10000 10001", '')
|
mock_utils_exc.return_value = ("10000 10001", '')
|
||||||
self.assertRaises(exception.InstanceDeployFailure, dp.commit)
|
self.assertRaises(errors.DeploymentError, dp.commit)
|
||||||
|
|
||||||
mock_disk_partitioner_exec.assert_called_once_with(
|
mock_disk_partitioner_exec.assert_called_once_with(
|
||||||
mock.ANY, 'mklabel', 'msdos',
|
mock.ANY, 'mklabel', 'msdos',
|
||||||
@ -190,7 +189,7 @@ class DiskPartitionerTestCase(base.IronicLibTestCase):
|
|||||||
mock_gp.return_value = fake_parts
|
mock_gp.return_value = fake_parts
|
||||||
mock_utils_exc.return_value = ('', "Specified filename /dev/fake"
|
mock_utils_exc.return_value = ('', "Specified filename /dev/fake"
|
||||||
" does not exist.")
|
" does not exist.")
|
||||||
self.assertRaises(exception.InstanceDeployFailure, dp.commit)
|
self.assertRaises(errors.DeploymentError, dp.commit)
|
||||||
|
|
||||||
mock_disk_partitioner_exec.assert_called_once_with(
|
mock_disk_partitioner_exec.assert_called_once_with(
|
||||||
mock.ANY, 'mklabel', 'msdos',
|
mock.ANY, 'mklabel', 'msdos',
|
||||||
|
@ -18,8 +18,6 @@ import os
|
|||||||
import stat
|
import stat
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from ironic_lib import exception
|
|
||||||
from ironic_lib import utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils.imageutils import format_inspector
|
from oslo_utils.imageutils import format_inspector
|
||||||
@ -27,9 +25,10 @@ from oslo_utils.imageutils import QemuImgInfo
|
|||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
|
|
||||||
from ironic_python_agent import disk_utils
|
from ironic_python_agent import disk_utils
|
||||||
from ironic_python_agent.errors import InvalidImage
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import qemu_img
|
from ironic_python_agent import qemu_img
|
||||||
from ironic_python_agent.tests.unit import base
|
from ironic_python_agent.tests.unit import base
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
@ -559,7 +558,7 @@ class OtherFunctionTestCase(base.IronicAgentTest):
|
|||||||
def test_is_block_device_raises(self, mock_os):
|
def test_is_block_device_raises(self, mock_os):
|
||||||
device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
|
device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
|
||||||
mock_os.side_effect = OSError
|
mock_os.side_effect = OSError
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
disk_utils.is_block_device, device)
|
disk_utils.is_block_device, device)
|
||||||
mock_os.assert_has_calls([mock.call(device)] * 3)
|
mock_os.assert_has_calls([mock.call(device)] * 3)
|
||||||
|
|
||||||
@ -569,7 +568,7 @@ class OtherFunctionTestCase(base.IronicAgentTest):
|
|||||||
group='disk_utils')
|
group='disk_utils')
|
||||||
device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
|
device = '/dev/disk/by-path/ip-1.2.3.4:5678-iscsi-iqn.fake-lun-9'
|
||||||
mock_os.side_effect = OSError
|
mock_os.side_effect = OSError
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
disk_utils.is_block_device, device)
|
disk_utils.is_block_device, device)
|
||||||
mock_os.assert_has_calls([mock.call(device)] * 2)
|
mock_os.assert_has_calls([mock.call(device)] * 2)
|
||||||
|
|
||||||
@ -656,7 +655,7 @@ Identified 1 problems!
|
|||||||
@mock.patch.object(disk_utils.LOG, 'error', autospec=True)
|
@mock.patch.object(disk_utils.LOG, 'error', autospec=True)
|
||||||
def test_fix_gpt_structs_exc(self, mock_log, mock_execute):
|
def test_fix_gpt_structs_exc(self, mock_log, mock_execute):
|
||||||
mock_execute.side_effect = processutils.ProcessExecutionError
|
mock_execute.side_effect = processutils.ProcessExecutionError
|
||||||
self.assertRaisesRegex(exception.InstanceDeployFailure,
|
self.assertRaisesRegex(errors.DeploymentError,
|
||||||
'Failed to fix GPT data structures on disk',
|
'Failed to fix GPT data structures on disk',
|
||||||
disk_utils._fix_gpt_structs,
|
disk_utils._fix_gpt_structs,
|
||||||
self.dev, self.node_uuid)
|
self.dev, self.node_uuid)
|
||||||
@ -854,7 +853,7 @@ class WaitForDisk(base.IronicAgentTest):
|
|||||||
side_effect=processutils.ProcessExecutionError(
|
side_effect=processutils.ProcessExecutionError(
|
||||||
stderr='fake'))
|
stderr='fake'))
|
||||||
def test_wait_for_disk_to_become_available_no_fuser(self, mock_exc):
|
def test_wait_for_disk_to_become_available_no_fuser(self, mock_exc):
|
||||||
self.assertRaises(exception.IronicException,
|
self.assertRaises(errors.RESTError,
|
||||||
disk_utils.wait_for_disk_to_become_available,
|
disk_utils.wait_for_disk_to_become_available,
|
||||||
'fake-dev')
|
'fake-dev')
|
||||||
fuser_cmd = ['fuser', 'fake-dev']
|
fuser_cmd = ['fuser', 'fake-dev']
|
||||||
@ -878,7 +877,7 @@ class WaitForDisk(base.IronicAgentTest):
|
|||||||
'holding device fake-dev: 15503, 3919, 15510, '
|
'holding device fake-dev: 15503, 3919, 15510, '
|
||||||
'15511. Timed out waiting for completion.')
|
'15511. Timed out waiting for completion.')
|
||||||
self.assertRaisesRegex(
|
self.assertRaisesRegex(
|
||||||
exception.IronicException,
|
errors.RESTError,
|
||||||
expected_error,
|
expected_error,
|
||||||
disk_utils.wait_for_disk_to_become_available,
|
disk_utils.wait_for_disk_to_become_available,
|
||||||
'fake-dev')
|
'fake-dev')
|
||||||
@ -903,7 +902,7 @@ class WaitForDisk(base.IronicAgentTest):
|
|||||||
'holding device fake-dev: 15503, 3919, 15510, '
|
'holding device fake-dev: 15503, 3919, 15510, '
|
||||||
'15511. Timed out waiting for completion.')
|
'15511. Timed out waiting for completion.')
|
||||||
self.assertRaisesRegex(
|
self.assertRaisesRegex(
|
||||||
exception.IronicException,
|
errors.RESTError,
|
||||||
expected_error,
|
expected_error,
|
||||||
disk_utils.wait_for_disk_to_become_available,
|
disk_utils.wait_for_disk_to_become_available,
|
||||||
'fake-dev')
|
'fake-dev')
|
||||||
@ -925,7 +924,7 @@ class WaitForDisk(base.IronicAgentTest):
|
|||||||
'locks for device fake-dev. Timed out waiting '
|
'locks for device fake-dev. Timed out waiting '
|
||||||
'for completion.')
|
'for completion.')
|
||||||
self.assertRaisesRegex(
|
self.assertRaisesRegex(
|
||||||
exception.IronicException,
|
errors.RESTError,
|
||||||
expected_error,
|
expected_error,
|
||||||
disk_utils.wait_for_disk_to_become_available,
|
disk_utils.wait_for_disk_to_become_available,
|
||||||
'fake-dev')
|
'fake-dev')
|
||||||
@ -999,7 +998,7 @@ class GetAndValidateImageFormat(base.IronicAgentTest):
|
|||||||
CONF.set_override('disable_deep_image_inspection', False)
|
CONF.set_override('disable_deep_image_inspection', False)
|
||||||
fmt = 'qcow3'
|
fmt = 'qcow3'
|
||||||
mock_ii.return_value = MockFormatInspectorCls(fmt, 0, True)
|
mock_ii.return_value = MockFormatInspectorCls(fmt, 0, True)
|
||||||
self.assertRaises(InvalidImage,
|
self.assertRaises(errors.InvalidImage,
|
||||||
disk_utils.get_and_validate_image_format,
|
disk_utils.get_and_validate_image_format,
|
||||||
'/fake/path', fmt)
|
'/fake/path', fmt)
|
||||||
mock_ii.assert_called_once_with('/fake/path')
|
mock_ii.assert_called_once_with('/fake/path')
|
||||||
@ -1010,7 +1009,7 @@ class GetAndValidateImageFormat(base.IronicAgentTest):
|
|||||||
CONF.set_override('disable_deep_image_inspection', False)
|
CONF.set_override('disable_deep_image_inspection', False)
|
||||||
fmt = 'qcow2'
|
fmt = 'qcow2'
|
||||||
mock_ii.return_value = MockFormatInspectorCls('qcow3', 0, True)
|
mock_ii.return_value = MockFormatInspectorCls('qcow3', 0, True)
|
||||||
self.assertRaises(InvalidImage,
|
self.assertRaises(errors.InvalidImage,
|
||||||
disk_utils.get_and_validate_image_format,
|
disk_utils.get_and_validate_image_format,
|
||||||
'/fake/path', fmt)
|
'/fake/path', fmt)
|
||||||
|
|
||||||
@ -1058,11 +1057,11 @@ class ImageInspectionTest(base.IronicAgentTest):
|
|||||||
def test_image_inspection_fail_safety_check(self, mock_fi):
|
def test_image_inspection_fail_safety_check(self, mock_fi):
|
||||||
inspector = MockFormatInspectorCls('qcow2', 0, False)
|
inspector = MockFormatInspectorCls('qcow2', 0, False)
|
||||||
mock_fi.return_value = inspector
|
mock_fi.return_value = inspector
|
||||||
self.assertRaises(InvalidImage, disk_utils._image_inspection,
|
self.assertRaises(errors.InvalidImage, disk_utils._image_inspection,
|
||||||
'/fake/path')
|
'/fake/path')
|
||||||
|
|
||||||
@mock.patch.object(format_inspector, 'detect_file_format', autospec=True)
|
@mock.patch.object(format_inspector, 'detect_file_format', autospec=True)
|
||||||
def test_image_inspection_fail_format_error(self, mock_fi):
|
def test_image_inspection_fail_format_error(self, mock_fi):
|
||||||
mock_fi.side_effect = format_inspector.ImageFormatError
|
mock_fi.side_effect = format_inspector.ImageFormatError
|
||||||
self.assertRaises(InvalidImage, disk_utils._image_inspection,
|
self.assertRaises(errors.InvalidImage, disk_utils._image_inspection,
|
||||||
'/fake/path')
|
'/fake/path')
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ironic_lib import exception as lib_exc
|
|
||||||
|
|
||||||
from ironic_python_agent import encoding
|
from ironic_python_agent import encoding
|
||||||
from ironic_python_agent.tests.unit import base
|
from ironic_python_agent.tests.unit import base
|
||||||
|
|
||||||
@ -73,10 +71,3 @@ class TestEncoder(base.IronicAgentTest):
|
|||||||
expected = {'jack': 'hello', 'jill': 'world'}
|
expected = {'jack': 'hello', 'jill': 'world'}
|
||||||
obj = SerializableTesting('hello', 'world')
|
obj = SerializableTesting('hello', 'world')
|
||||||
self.assertEqual(expected, json.loads(self.encoder.encode(obj)))
|
self.assertEqual(expected, json.loads(self.encoder.encode(obj)))
|
||||||
|
|
||||||
def test_ironic_lib(self):
|
|
||||||
obj = lib_exc.InstanceDeployFailure(reason='boom')
|
|
||||||
encoded = json.loads(self.encoder.encode(obj))
|
|
||||||
self.assertEqual(500, encoded['code'])
|
|
||||||
self.assertEqual('InstanceDeployFailure', encoded['type'])
|
|
||||||
self.assertIn('boom', encoded['message'])
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -114,7 +114,7 @@ class TestInspect(base.IronicAgentTest):
|
|||||||
mock_call.assert_not_called()
|
mock_call.assert_not_called()
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
@mock.patch('ironic_lib.mdns.get_endpoint', autospec=True)
|
@mock.patch('ironic_python_agent.mdns.get_endpoint', autospec=True)
|
||||||
def test_mdns(self, mock_mdns, mock_ext_mgr, mock_call):
|
def test_mdns(self, mock_mdns, mock_ext_mgr, mock_call):
|
||||||
CONF.set_override('inspection_callback_url', 'mdns')
|
CONF.set_override('inspection_callback_url', 'mdns')
|
||||||
mock_mdns.return_value = 'http://example', {
|
mock_mdns.return_value = 'http://example', {
|
||||||
|
234
ironic_python_agent/tests/unit/test_mdns.py
Normal file
234
ironic_python_agent/tests/unit/test_mdns.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import socket
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from ironic_python_agent import errors
|
||||||
|
from ironic_python_agent import mdns
|
||||||
|
from ironic_python_agent.tests.unit.base import IronicAgentTest
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('ironic_python_agent.utils.get_route_source', autospec=True)
|
||||||
|
@mock.patch('zeroconf.Zeroconf', autospec=True)
|
||||||
|
class GetEndpointTestCase(IronicAgentTest):
|
||||||
|
def test_simple(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
address=socket.inet_aton('192.168.1.1'),
|
||||||
|
port=80,
|
||||||
|
properties={},
|
||||||
|
**{'parsed_addresses.return_value': ['192.168.1.1']}
|
||||||
|
)
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('http://192.168.1.1:80', endp)
|
||||||
|
self.assertEqual({}, params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
mock_zc.return_value.close.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_v6(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
port=80,
|
||||||
|
properties={},
|
||||||
|
**{'parsed_addresses.return_value': ['::2']}
|
||||||
|
)
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('http://[::2]:80', endp)
|
||||||
|
self.assertEqual({}, params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
mock_zc.return_value.close.assert_called_once_with()
|
||||||
|
|
||||||
|
def test_skip_invalid(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
port=80,
|
||||||
|
properties={},
|
||||||
|
**{'parsed_addresses.return_value': ['::1', '::2', '::3']}
|
||||||
|
)
|
||||||
|
mock_route.side_effect = [None, '::4']
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('http://[::3]:80', endp)
|
||||||
|
self.assertEqual({}, params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
mock_zc.return_value.close.assert_called_once_with()
|
||||||
|
self.assertEqual(2, mock_route.call_count)
|
||||||
|
|
||||||
|
def test_fallback(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
port=80,
|
||||||
|
properties={},
|
||||||
|
**{'parsed_addresses.return_value': ['::2', '::3']}
|
||||||
|
)
|
||||||
|
mock_route.side_effect = [None, None]
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('http://[::2]:80', endp)
|
||||||
|
self.assertEqual({}, params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
mock_zc.return_value.close.assert_called_once_with()
|
||||||
|
self.assertEqual(2, mock_route.call_count)
|
||||||
|
|
||||||
|
def test_localhost_only(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
port=80,
|
||||||
|
properties={},
|
||||||
|
**{'parsed_addresses.return_value': ['::1']}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(errors.ServiceLookupFailure,
|
||||||
|
mdns.get_endpoint, 'baremetal')
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
mock_zc.return_value.close.assert_called_once_with()
|
||||||
|
self.assertFalse(mock_route.called)
|
||||||
|
|
||||||
|
def test_https(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
address=socket.inet_aton('192.168.1.1'),
|
||||||
|
port=443,
|
||||||
|
properties={},
|
||||||
|
**{'parsed_addresses.return_value': ['192.168.1.1']}
|
||||||
|
)
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('https://192.168.1.1:443', endp)
|
||||||
|
self.assertEqual({}, params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_custom_port_and_path(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
address=socket.inet_aton('192.168.1.1'),
|
||||||
|
port=8080,
|
||||||
|
properties={b'path': b'/baremetal'},
|
||||||
|
**{'parsed_addresses.return_value': ['192.168.1.1']}
|
||||||
|
)
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('https://192.168.1.1:8080/baremetal', endp)
|
||||||
|
self.assertEqual({}, params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_custom_port_path_and_protocol(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
address=socket.inet_aton('192.168.1.1'),
|
||||||
|
port=8080,
|
||||||
|
properties={b'path': b'/baremetal', b'protocol': b'http'},
|
||||||
|
**{'parsed_addresses.return_value': ['192.168.1.1']}
|
||||||
|
)
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('http://192.168.1.1:8080/baremetal', endp)
|
||||||
|
self.assertEqual({}, params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_params(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
address=socket.inet_aton('192.168.1.1'),
|
||||||
|
port=80,
|
||||||
|
properties={b'ipa_debug': True},
|
||||||
|
**{'parsed_addresses.return_value': ['192.168.1.1']}
|
||||||
|
)
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('http://192.168.1.1:80', endp)
|
||||||
|
self.assertEqual({'ipa_debug': True}, params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_binary_data(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
address=socket.inet_aton('192.168.1.1'),
|
||||||
|
port=80,
|
||||||
|
properties={b'ipa_debug': True, b'binary': b'\xe2\x28\xa1'},
|
||||||
|
**{'parsed_addresses.return_value': ['192.168.1.1']}
|
||||||
|
)
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('http://192.168.1.1:80', endp)
|
||||||
|
self.assertEqual({'ipa_debug': True, 'binary': b'\xe2\x28\xa1'},
|
||||||
|
params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invalid_key(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
address=socket.inet_aton('192.168.1.1'),
|
||||||
|
port=80,
|
||||||
|
properties={b'ipa_debug': True, b'\xc3\x28': b'value'},
|
||||||
|
**{'parsed_addresses.return_value': ['192.168.1.1']}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaisesRegex(errors.ServiceLookupFailure,
|
||||||
|
'Cannot decode key',
|
||||||
|
mdns.get_endpoint, 'baremetal')
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_server(self, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = mock.Mock(
|
||||||
|
address=socket.inet_aton('192.168.1.1'),
|
||||||
|
port=443,
|
||||||
|
server='openstack.example.com.',
|
||||||
|
properties={},
|
||||||
|
**{'parsed_addresses.return_value': ['192.168.1.1']}
|
||||||
|
)
|
||||||
|
|
||||||
|
endp, params = mdns.get_endpoint('baremetal')
|
||||||
|
self.assertEqual('https://openstack.example.com:443', endp)
|
||||||
|
self.assertEqual({}, params)
|
||||||
|
mock_zc.return_value.get_service_info.assert_called_once_with(
|
||||||
|
'baremetal._openstack._tcp.local.',
|
||||||
|
'baremetal._openstack._tcp.local.'
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('time.sleep', autospec=True)
|
||||||
|
def test_not_found(self, mock_sleep, mock_zc, mock_route):
|
||||||
|
mock_zc.return_value.get_service_info.return_value = None
|
||||||
|
|
||||||
|
self.assertRaisesRegex(errors.ServiceLookupFailure,
|
||||||
|
'baremetal service',
|
||||||
|
mdns.get_endpoint, 'baremetal')
|
||||||
|
self.assertEqual(CONF.mdns.lookup_attempts - 1, mock_sleep.call_count)
|
@ -15,8 +15,6 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from ironic_lib import exception
|
|
||||||
from ironic_lib import utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
import requests
|
import requests
|
||||||
@ -28,6 +26,7 @@ from ironic_python_agent import hardware
|
|||||||
from ironic_python_agent import partition_utils
|
from ironic_python_agent import partition_utils
|
||||||
from ironic_python_agent import qemu_img
|
from ironic_python_agent import qemu_img
|
||||||
from ironic_python_agent.tests.unit import base
|
from ironic_python_agent.tests.unit import base
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -114,7 +113,7 @@ class GetConfigdriveTestCase(base.IronicAgentTest):
|
|||||||
|
|
||||||
def test_get_configdrive_bad_url(self, mock_requests, mock_copy):
|
def test_get_configdrive_bad_url(self, mock_requests, mock_copy):
|
||||||
mock_requests.side_effect = requests.exceptions.RequestException
|
mock_requests.side_effect = requests.exceptions.RequestException
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
partition_utils.get_configdrive,
|
partition_utils.get_configdrive,
|
||||||
'http://1.2.3.4/cd', 'fake-node-uuid')
|
'http://1.2.3.4/cd', 'fake-node-uuid')
|
||||||
self.assertFalse(mock_copy.called)
|
self.assertFalse(mock_copy.called)
|
||||||
@ -122,13 +121,13 @@ class GetConfigdriveTestCase(base.IronicAgentTest):
|
|||||||
def test_get_configdrive_bad_status_code(self, mock_requests, mock_copy):
|
def test_get_configdrive_bad_status_code(self, mock_requests, mock_copy):
|
||||||
mock_requests.return_value = mock.MagicMock(text='Not found',
|
mock_requests.return_value = mock.MagicMock(text='Not found',
|
||||||
status_code=404)
|
status_code=404)
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
partition_utils.get_configdrive,
|
partition_utils.get_configdrive,
|
||||||
'http://1.2.3.4/cd', 'fake-node-uuid')
|
'http://1.2.3.4/cd', 'fake-node-uuid')
|
||||||
self.assertFalse(mock_copy.called)
|
self.assertFalse(mock_copy.called)
|
||||||
|
|
||||||
def test_get_configdrive_base64_error(self, mock_requests, mock_copy):
|
def test_get_configdrive_base64_error(self, mock_requests, mock_copy):
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
partition_utils.get_configdrive,
|
partition_utils.get_configdrive,
|
||||||
'malformed', 'fake-node-uuid')
|
'malformed', 'fake-node-uuid')
|
||||||
self.assertFalse(mock_copy.called)
|
self.assertFalse(mock_copy.called)
|
||||||
@ -139,7 +138,7 @@ class GetConfigdriveTestCase(base.IronicAgentTest):
|
|||||||
mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy',
|
mock_requests.return_value = mock.MagicMock(content='Zm9vYmFy',
|
||||||
status_code=200)
|
status_code=200)
|
||||||
mock_copy.side_effect = IOError
|
mock_copy.side_effect = IOError
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
partition_utils.get_configdrive,
|
partition_utils.get_configdrive,
|
||||||
'http://1.2.3.4/cd', 'fake-node-uuid')
|
'http://1.2.3.4/cd', 'fake-node-uuid')
|
||||||
mock_requests.assert_called_once_with('http://1.2.3.4/cd',
|
mock_requests.assert_called_once_with('http://1.2.3.4/cd',
|
||||||
@ -212,7 +211,7 @@ class GetLabelledPartitionTestCases(base.IronicAgentTest):
|
|||||||
'NAME="fake13" LABEL="%s"\n' %
|
'NAME="fake13" LABEL="%s"\n' %
|
||||||
(label, label))
|
(label, label))
|
||||||
mock_execute.side_effect = [(None, ''), (lsblk_output, '')]
|
mock_execute.side_effect = [(None, ''), (lsblk_output, '')]
|
||||||
self.assertRaisesRegex(exception.InstanceDeployFailure,
|
self.assertRaisesRegex(errors.DeploymentError,
|
||||||
'fake .*fake12 .*fake13',
|
'fake .*fake12 .*fake13',
|
||||||
partition_utils.get_labelled_partition,
|
partition_utils.get_labelled_partition,
|
||||||
self.dev, self.config_part_label,
|
self.dev, self.config_part_label,
|
||||||
@ -228,7 +227,7 @@ class GetLabelledPartitionTestCases(base.IronicAgentTest):
|
|||||||
@mock.patch.object(partition_utils.LOG, 'error', autospec=True)
|
@mock.patch.object(partition_utils.LOG, 'error', autospec=True)
|
||||||
def test_get_partition_exc(self, mock_log, mock_execute):
|
def test_get_partition_exc(self, mock_log, mock_execute):
|
||||||
mock_execute.side_effect = processutils.ProcessExecutionError
|
mock_execute.side_effect = processutils.ProcessExecutionError
|
||||||
self.assertRaisesRegex(exception.InstanceDeployFailure,
|
self.assertRaisesRegex(errors.DeploymentError,
|
||||||
'Failed to retrieve partition labels',
|
'Failed to retrieve partition labels',
|
||||||
partition_utils.get_labelled_partition,
|
partition_utils.get_labelled_partition,
|
||||||
self.dev, self.config_part_label,
|
self.dev, self.config_part_label,
|
||||||
@ -273,7 +272,7 @@ class IsDiskLargerThanMaxSizeTestCases(base.IronicAgentTest):
|
|||||||
@mock.patch.object(partition_utils.LOG, 'error', autospec=True)
|
@mock.patch.object(partition_utils.LOG, 'error', autospec=True)
|
||||||
def test_is_disk_larger_than_max_size_exc(self, mock_log, mock_execute):
|
def test_is_disk_larger_than_max_size_exc(self, mock_log, mock_execute):
|
||||||
mock_execute.side_effect = processutils.ProcessExecutionError
|
mock_execute.side_effect = processutils.ProcessExecutionError
|
||||||
self.assertRaisesRegex(exception.InstanceDeployFailure,
|
self.assertRaisesRegex(errors.DeploymentError,
|
||||||
'Failed to get size of disk',
|
'Failed to get size of disk',
|
||||||
partition_utils._is_disk_larger_than_max_size,
|
partition_utils._is_disk_larger_than_max_size,
|
||||||
self.dev, self.node_uuid)
|
self.dev, self.node_uuid)
|
||||||
@ -316,7 +315,7 @@ class WorkOnDiskTestCase(base.IronicAgentTest):
|
|||||||
|
|
||||||
def test_no_root_partition(self):
|
def test_no_root_partition(self):
|
||||||
self.mock_ibd.return_value = False
|
self.mock_ibd.return_value = False
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
partition_utils.work_on_disk, self.dev, self.root_mb,
|
partition_utils.work_on_disk, self.dev, self.root_mb,
|
||||||
self.swap_mb, self.ephemeral_mb,
|
self.swap_mb, self.ephemeral_mb,
|
||||||
self.ephemeral_format, self.image_path,
|
self.ephemeral_format, self.image_path,
|
||||||
@ -335,7 +334,7 @@ class WorkOnDiskTestCase(base.IronicAgentTest):
|
|||||||
self.mock_ibd.side_effect = iter([True, False])
|
self.mock_ibd.side_effect = iter([True, False])
|
||||||
calls = [mock.call(self.root_part),
|
calls = [mock.call(self.root_part),
|
||||||
mock.call(self.swap_part)]
|
mock.call(self.swap_part)]
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
partition_utils.work_on_disk, self.dev, self.root_mb,
|
partition_utils.work_on_disk, self.dev, self.root_mb,
|
||||||
self.swap_mb, self.ephemeral_mb,
|
self.swap_mb, self.ephemeral_mb,
|
||||||
self.ephemeral_format, self.image_path,
|
self.ephemeral_format, self.image_path,
|
||||||
@ -364,7 +363,7 @@ class WorkOnDiskTestCase(base.IronicAgentTest):
|
|||||||
calls = [mock.call(root_part),
|
calls = [mock.call(root_part),
|
||||||
mock.call(swap_part),
|
mock.call(swap_part),
|
||||||
mock.call(ephemeral_part)]
|
mock.call(ephemeral_part)]
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
partition_utils.work_on_disk, self.dev, self.root_mb,
|
partition_utils.work_on_disk, self.dev, self.root_mb,
|
||||||
self.swap_mb, ephemeral_mb, ephemeral_format,
|
self.swap_mb, ephemeral_mb, ephemeral_format,
|
||||||
self.image_path, self.node_uuid)
|
self.image_path, self.node_uuid)
|
||||||
@ -395,7 +394,7 @@ class WorkOnDiskTestCase(base.IronicAgentTest):
|
|||||||
calls = [mock.call(root_part),
|
calls = [mock.call(root_part),
|
||||||
mock.call(swap_part),
|
mock.call(swap_part),
|
||||||
mock.call(configdrive_part)]
|
mock.call(configdrive_part)]
|
||||||
self.assertRaises(exception.InstanceDeployFailure,
|
self.assertRaises(errors.DeploymentError,
|
||||||
partition_utils.work_on_disk, self.dev, self.root_mb,
|
partition_utils.work_on_disk, self.dev, self.root_mb,
|
||||||
self.swap_mb, self.ephemeral_mb,
|
self.swap_mb, self.ephemeral_mb,
|
||||||
self.ephemeral_format, self.image_path,
|
self.ephemeral_format, self.image_path,
|
||||||
@ -1031,7 +1030,7 @@ class CreateConfigDriveTestCases(base.IronicAgentTest):
|
|||||||
# 2 primary partitions, 0 logical partitions
|
# 2 primary partitions, 0 logical partitions
|
||||||
mock_count.return_value = (2, 0)
|
mock_count.return_value = (2, 0)
|
||||||
|
|
||||||
self.assertRaisesRegex(exception.InstanceDeployFailure,
|
self.assertRaisesRegex(errors.DeploymentError,
|
||||||
'Disk partitioning failed on device',
|
'Disk partitioning failed on device',
|
||||||
partition_utils.create_config_drive_partition,
|
partition_utils.create_config_drive_partition,
|
||||||
self.node_uuid, self.dev, config_url)
|
self.node_uuid, self.dev, config_url)
|
||||||
@ -1103,7 +1102,7 @@ class CreateConfigDriveTestCases(base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_execute.side_effect = processutils.ProcessExecutionError
|
mock_execute.side_effect = processutils.ProcessExecutionError
|
||||||
|
|
||||||
self.assertRaisesRegex(exception.InstanceDeployFailure,
|
self.assertRaisesRegex(errors.DeploymentError,
|
||||||
'Failed to create config drive on disk',
|
'Failed to create config drive on disk',
|
||||||
partition_utils.create_config_drive_partition,
|
partition_utils.create_config_drive_partition,
|
||||||
self.node_uuid, self.dev, config_url)
|
self.node_uuid, self.dev, config_url)
|
||||||
@ -1161,7 +1160,7 @@ class CreateConfigDriveTestCases(base.IronicAgentTest):
|
|||||||
# 4 primary partitions, 0 logical partitions
|
# 4 primary partitions, 0 logical partitions
|
||||||
mock_count.return_value = (4, 0)
|
mock_count.return_value = (4, 0)
|
||||||
|
|
||||||
self.assertRaisesRegex(exception.InstanceDeployFailure,
|
self.assertRaisesRegex(errors.DeploymentError,
|
||||||
'Config drive cannot be created for node',
|
'Config drive cannot be created for node',
|
||||||
partition_utils.create_config_drive_partition,
|
partition_utils.create_config_drive_partition,
|
||||||
self.node_uuid, self.dev, config_url)
|
self.node_uuid, self.dev, config_url)
|
||||||
@ -1191,7 +1190,7 @@ class CreateConfigDriveTestCases(base.IronicAgentTest):
|
|||||||
mock_get_configdrive.return_value = (configdrive_mb, configdrive_file)
|
mock_get_configdrive.return_value = (configdrive_mb, configdrive_file)
|
||||||
mock_get_labelled_partition.return_value = None
|
mock_get_labelled_partition.return_value = None
|
||||||
|
|
||||||
self.assertRaisesRegex(exception.InstanceDeployFailure,
|
self.assertRaisesRegex(errors.DeploymentError,
|
||||||
'Config drive size exceeds maximum limit',
|
'Config drive size exceeds maximum limit',
|
||||||
partition_utils.create_config_drive_partition,
|
partition_utils.create_config_drive_partition,
|
||||||
self.node_uuid, self.dev, config_url)
|
self.node_uuid, self.dev, config_url)
|
||||||
@ -1223,7 +1222,7 @@ class CreateConfigDriveTestCases(base.IronicAgentTest):
|
|||||||
mock_get_labelled_partition.return_value = None
|
mock_get_labelled_partition.return_value = None
|
||||||
mock_count.side_effect = ValueError('Booooom')
|
mock_count.side_effect = ValueError('Booooom')
|
||||||
|
|
||||||
self.assertRaisesRegex(exception.InstanceDeployFailure,
|
self.assertRaisesRegex(errors.DeploymentError,
|
||||||
'Failed to check the number of primary ',
|
'Failed to check the number of primary ',
|
||||||
partition_utils.create_config_drive_partition,
|
partition_utils.create_config_drive_partition,
|
||||||
self.node_uuid, self.dev, config_url)
|
self.node_uuid, self.dev, config_url)
|
||||||
@ -1501,7 +1500,7 @@ class TestConfigDriveTestRecovery(base.IronicAgentTest):
|
|||||||
mock_execute):
|
mock_execute):
|
||||||
mock_mkfs.side_effect = processutils.ProcessExecutionError('boom')
|
mock_mkfs.side_effect = processutils.ProcessExecutionError('boom')
|
||||||
self.assertRaisesRegex(
|
self.assertRaisesRegex(
|
||||||
exception.InstanceDeployFailure,
|
errors.DeploymentError,
|
||||||
'A failure occurred while attempting to format.*',
|
'A failure occurred while attempting to format.*',
|
||||||
partition_utils._try_build_fat32_config_drive,
|
partition_utils._try_build_fat32_config_drive,
|
||||||
self.fake_dev,
|
self.fake_dev,
|
||||||
@ -1516,3 +1515,14 @@ class TestConfigDriveTestRecovery(base.IronicAgentTest):
|
|||||||
mock_mkfs.assert_called_once_with(fs='vfat', path=self.fake_dev,
|
mock_mkfs.assert_called_once_with(fs='vfat', path=self.fake_dev,
|
||||||
label='CONFIG-2')
|
label='CONFIG-2')
|
||||||
mock_copy.assert_not_called()
|
mock_copy.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
class IsHttpUrlTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
|
def test__is_http_url(self):
|
||||||
|
self.assertTrue(partition_utils._is_http_url('http://127.0.0.1'))
|
||||||
|
self.assertTrue(partition_utils._is_http_url('https://127.0.0.1'))
|
||||||
|
self.assertTrue(partition_utils._is_http_url('HTTP://127.1.2.3'))
|
||||||
|
self.assertTrue(partition_utils._is_http_url('HTTPS://127.3.2.1'))
|
||||||
|
self.assertFalse(partition_utils._is_http_url('Zm9vYmFy'))
|
||||||
|
self.assertFalse(partition_utils._is_http_url('11111111'))
|
||||||
|
@ -13,20 +13,20 @@
|
|||||||
import os
|
import os
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from ironic_lib.tests import base
|
|
||||||
from ironic_lib import utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import imageutils
|
from oslo_utils import imageutils
|
||||||
|
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import qemu_img
|
from ironic_python_agent import qemu_img
|
||||||
|
from ironic_python_agent.tests.unit import base
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class ImageInfoTestCase(base.IronicLibTestCase):
|
class ImageInfoTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
@mock.patch.object(os.path, 'exists', return_value=False, autospec=True)
|
@mock.patch.object(os.path, 'exists', return_value=False, autospec=True)
|
||||||
def test_image_info_path_doesnt_exist_disabled(self, path_exists_mock):
|
def test_image_info_path_doesnt_exist_disabled(self, path_exists_mock):
|
||||||
@ -79,7 +79,7 @@ class ImageInfoTestCase(base.IronicLibTestCase):
|
|||||||
image_info_mock.assert_not_called()
|
image_info_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
class ConvertImageTestCase(base.IronicLibTestCase):
|
class ConvertImageTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
@mock.patch.object(utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
def test_convert_image_disabled(self, execute_mock):
|
def test_convert_image_disabled(self, execute_mock):
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from ironic_lib import utils as ilib_utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
|
|
||||||
from ironic_python_agent import disk_utils
|
from ironic_python_agent import disk_utils
|
||||||
@ -158,7 +157,7 @@ class TestRaidUtils(base.IronicAgentTest):
|
|||||||
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
|
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
|
||||||
return_value='/dev/md42')
|
return_value='/dev/md42')
|
||||||
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
||||||
@mock.patch.object(ilib_utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
@mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
|
@mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
|
||||||
def test_prepare_boot_partitions_for_softraid_uefi_gpt(
|
def test_prepare_boot_partitions_for_softraid_uefi_gpt(
|
||||||
self, mock_efi_part, mock_execute, mock_dispatch,
|
self, mock_efi_part, mock_execute, mock_dispatch,
|
||||||
@ -216,9 +215,9 @@ class TestRaidUtils(base.IronicAgentTest):
|
|||||||
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
|
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
|
||||||
return_value='/dev/md42')
|
return_value='/dev/md42')
|
||||||
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
||||||
@mock.patch.object(ilib_utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
@mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
|
@mock.patch.object(disk_utils, 'find_efi_partition', autospec=True)
|
||||||
@mock.patch.object(ilib_utils, 'mkfs', autospec=True)
|
@mock.patch.object(utils, 'mkfs', autospec=True)
|
||||||
def test_prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found(
|
def test_prepare_boot_partitions_for_softraid_uefi_gpt_esp_not_found(
|
||||||
self, mock_mkfs, mock_efi_part, mock_execute, mock_dispatch,
|
self, mock_mkfs, mock_efi_part, mock_execute, mock_dispatch,
|
||||||
mock_free_raid_device, mock_rescan, mock_find_esp):
|
mock_free_raid_device, mock_rescan, mock_find_esp):
|
||||||
@ -271,7 +270,7 @@ class TestRaidUtils(base.IronicAgentTest):
|
|||||||
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
|
@mock.patch.object(raid_utils, 'get_next_free_raid_device', autospec=True,
|
||||||
return_value='/dev/md42')
|
return_value='/dev/md42')
|
||||||
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
||||||
@mock.patch.object(ilib_utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
def test_prepare_boot_partitions_for_softraid_uefi_gpt_efi_provided(
|
def test_prepare_boot_partitions_for_softraid_uefi_gpt_efi_provided(
|
||||||
self, mock_execute, mock_dispatch, mock_free_raid_device,
|
self, mock_execute, mock_dispatch, mock_free_raid_device,
|
||||||
mock_rescan, mock_find_esp):
|
mock_rescan, mock_find_esp):
|
||||||
@ -321,7 +320,7 @@ class TestRaidUtils(base.IronicAgentTest):
|
|||||||
self.assertEqual(efi_part, '/dev/md42')
|
self.assertEqual(efi_part, '/dev/md42')
|
||||||
|
|
||||||
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
||||||
@mock.patch.object(ilib_utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
@mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True,
|
@mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True,
|
||||||
return_value='msdos')
|
return_value='msdos')
|
||||||
def test_prepare_boot_partitions_for_softraid_bios_msdos(
|
def test_prepare_boot_partitions_for_softraid_bios_msdos(
|
||||||
@ -339,7 +338,7 @@ class TestRaidUtils(base.IronicAgentTest):
|
|||||||
self.assertIsNone(efi_part)
|
self.assertIsNone(efi_part)
|
||||||
|
|
||||||
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
||||||
@mock.patch.object(ilib_utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
@mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True,
|
@mock.patch.object(disk_utils, 'get_partition_table_type', autospec=True,
|
||||||
return_value='gpt')
|
return_value='gpt')
|
||||||
def test_prepare_boot_partitions_for_softraid_bios_gpt(
|
def test_prepare_boot_partitions_for_softraid_bios_gpt(
|
||||||
|
@ -25,35 +25,20 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from ironic_lib import utils as ironic_utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
import requests
|
import requests
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
from ironic_python_agent import errors
|
from ironic_python_agent import errors
|
||||||
from ironic_python_agent import hardware
|
from ironic_python_agent import hardware
|
||||||
from ironic_python_agent.tests.unit import base as ironic_agent_base
|
from ironic_python_agent.tests.unit import base
|
||||||
from ironic_python_agent import utils
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
class ExecuteTestCase(ironic_agent_base.IronicAgentTest):
|
|
||||||
# This test case does call utils.execute(), so don't block access to the
|
|
||||||
# execute calls.
|
|
||||||
block_execute = False
|
|
||||||
|
|
||||||
# We do mock out the call to ironic_utils.execute() so we don't actually
|
|
||||||
# 'execute' anything, as utils.execute() calls ironic_utils.execute()
|
|
||||||
@mock.patch.object(ironic_utils, 'execute', autospec=True)
|
|
||||||
def test_execute(self, mock_execute):
|
|
||||||
utils.execute('/usr/bin/env', 'false', check_exit_code=False)
|
|
||||||
mock_execute.assert_called_once_with('/usr/bin/env', 'false',
|
|
||||||
check_exit_code=False)
|
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('shutil.rmtree', autospec=True)
|
@mock.patch('shutil.rmtree', autospec=True)
|
||||||
@mock.patch.object(ironic_utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
@mock.patch('tempfile.mkdtemp', autospec=True)
|
@mock.patch('tempfile.mkdtemp', autospec=True)
|
||||||
class MountedTestCase(ironic_agent_base.IronicAgentTest):
|
class MountedTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
def test_temporary(self, mock_temp, mock_execute, mock_rmtree):
|
def test_temporary(self, mock_temp, mock_execute, mock_rmtree):
|
||||||
with utils.mounted('/dev/fake') as path:
|
with utils.mounted('/dev/fake') as path:
|
||||||
@ -123,7 +108,7 @@ class MountedTestCase(ironic_agent_base.IronicAgentTest):
|
|||||||
self.assertFalse(mock_rmtree.called)
|
self.assertFalse(mock_rmtree.called)
|
||||||
|
|
||||||
|
|
||||||
class GetAgentParamsTestCase(ironic_agent_base.IronicAgentTest):
|
class GetAgentParamsTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
@mock.patch('oslo_log.log.getLogger', autospec=True)
|
@mock.patch('oslo_log.log.getLogger', autospec=True)
|
||||||
@mock.patch('builtins.open', autospec=True)
|
@mock.patch('builtins.open', autospec=True)
|
||||||
@ -352,7 +337,7 @@ class TestFailures(testtools.TestCase):
|
|||||||
self.assertRaisesRegex(FakeException, 'foo', f.raise_if_needed)
|
self.assertRaisesRegex(FakeException, 'foo', f.raise_if_needed)
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(ironic_agent_base.IronicAgentTest):
|
class TestUtils(base.IronicAgentTest):
|
||||||
|
|
||||||
def _get_journalctl_output(self, mock_execute, lines=None, units=None):
|
def _get_journalctl_output(self, mock_execute, lines=None, units=None):
|
||||||
contents = b'Krusty Krab'
|
contents = b'Krusty Krab'
|
||||||
@ -870,7 +855,7 @@ class TestRemoveKeys(testtools.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
@mock.patch.object(utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
class TestClockSyncUtils(ironic_agent_base.IronicAgentTest):
|
class TestClockSyncUtils(base.IronicAgentTest):
|
||||||
|
|
||||||
def test_determine_time_method_none(self, mock_execute):
|
def test_determine_time_method_none(self, mock_execute):
|
||||||
mock_execute.side_effect = OSError
|
mock_execute.side_effect = OSError
|
||||||
@ -1113,7 +1098,7 @@ class TestCopyConfigFromVmedia(testtools.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
@mock.patch.object(requests, 'get', autospec=True)
|
@mock.patch.object(requests, 'get', autospec=True)
|
||||||
class TestStreamingClient(ironic_agent_base.IronicAgentTest):
|
class TestStreamingClient(base.IronicAgentTest):
|
||||||
|
|
||||||
def test_ok(self, mock_get):
|
def test_ok(self, mock_get):
|
||||||
client = utils.StreamingClient()
|
client = utils.StreamingClient()
|
||||||
@ -1142,7 +1127,7 @@ class TestStreamingClient(ironic_agent_base.IronicAgentTest):
|
|||||||
self.assertEqual(2, mock_get.call_count)
|
self.assertEqual(2, mock_get.call_count)
|
||||||
|
|
||||||
|
|
||||||
class TestCheckVirtualMedia(ironic_agent_base.IronicAgentTest):
|
class TestCheckVirtualMedia(base.IronicAgentTest):
|
||||||
|
|
||||||
@mock.patch.object(utils, 'execute', autospec=True)
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
def test_check_vmedia_device(self, mock_execute):
|
def test_check_vmedia_device(self, mock_execute):
|
||||||
@ -1209,7 +1194,7 @@ class TestCheckVirtualMedia(ironic_agent_base.IronicAgentTest):
|
|||||||
'/dev/sdh')
|
'/dev/sdh')
|
||||||
|
|
||||||
|
|
||||||
class TestCheckEarlyLogging(ironic_agent_base.IronicAgentTest):
|
class TestCheckEarlyLogging(base.IronicAgentTest):
|
||||||
|
|
||||||
@mock.patch.object(utils, 'LOG', autospec=True)
|
@mock.patch.object(utils, 'LOG', autospec=True)
|
||||||
def test_early_logging_goes_to_logger(self, mock_log):
|
def test_early_logging_goes_to_logger(self, mock_log):
|
||||||
@ -1232,7 +1217,7 @@ class TestCheckEarlyLogging(ironic_agent_base.IronicAgentTest):
|
|||||||
info.assert_has_calls(expected_calls)
|
info.assert_has_calls(expected_calls)
|
||||||
|
|
||||||
|
|
||||||
class TestUnmountOfConfig(ironic_agent_base.IronicAgentTest):
|
class TestUnmountOfConfig(base.IronicAgentTest):
|
||||||
|
|
||||||
@mock.patch.object(utils, '_early_log', autospec=True)
|
@mock.patch.object(utils, '_early_log', autospec=True)
|
||||||
@mock.patch.object(os.path, 'ismount', autospec=True)
|
@mock.patch.object(os.path, 'ismount', autospec=True)
|
||||||
@ -1247,3 +1232,188 @@ class TestUnmountOfConfig(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_exec.assert_has_calls([
|
mock_exec.assert_has_calls([
|
||||||
mock.call('umount', '/mnt/config'),
|
mock.call('umount', '/mnt/config'),
|
||||||
mock.call('umount', '/mnt/config')])
|
mock.call('umount', '/mnt/config')])
|
||||||
|
|
||||||
|
|
||||||
|
class BareMetalUtilsTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
|
def test_unlink(self):
|
||||||
|
with mock.patch.object(os, "unlink", autospec=True) as unlink_mock:
|
||||||
|
unlink_mock.return_value = None
|
||||||
|
utils.unlink_without_raise("/fake/path")
|
||||||
|
unlink_mock.assert_called_once_with("/fake/path")
|
||||||
|
|
||||||
|
def test_unlink_ENOENT(self):
|
||||||
|
with mock.patch.object(os, "unlink", autospec=True) as unlink_mock:
|
||||||
|
unlink_mock.side_effect = OSError(errno.ENOENT)
|
||||||
|
utils.unlink_without_raise("/fake/path")
|
||||||
|
unlink_mock.assert_called_once_with("/fake/path")
|
||||||
|
|
||||||
|
|
||||||
|
class ExecuteTestCase(base.IronicAgentTest):
|
||||||
|
# Allow calls to utils.execute() and related functions
|
||||||
|
block_execute = False
|
||||||
|
|
||||||
|
@mock.patch.object(processutils, 'execute', autospec=True)
|
||||||
|
@mock.patch.object(os.environ, 'copy', return_value={}, autospec=True)
|
||||||
|
def test_execute_use_standard_locale_no_env_variables(self, env_mock,
|
||||||
|
execute_mock):
|
||||||
|
utils.execute('foo', use_standard_locale=True)
|
||||||
|
execute_mock.assert_called_once_with('foo',
|
||||||
|
env_variables={'LC_ALL': 'C'})
|
||||||
|
|
||||||
|
@mock.patch.object(processutils, 'execute', autospec=True)
|
||||||
|
def test_execute_use_standard_locale_with_env_variables(self,
|
||||||
|
execute_mock):
|
||||||
|
utils.execute('foo', use_standard_locale=True,
|
||||||
|
env_variables={'foo': 'bar'})
|
||||||
|
execute_mock.assert_called_once_with('foo',
|
||||||
|
env_variables={'LC_ALL': 'C',
|
||||||
|
'foo': 'bar'})
|
||||||
|
|
||||||
|
@mock.patch.object(processutils, 'execute', autospec=True)
|
||||||
|
def test_execute_not_use_standard_locale(self, execute_mock):
|
||||||
|
utils.execute('foo', use_standard_locale=False,
|
||||||
|
env_variables={'foo': 'bar'})
|
||||||
|
execute_mock.assert_called_once_with('foo',
|
||||||
|
env_variables={'foo': 'bar'})
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'LOG', autospec=True)
|
||||||
|
def _test_execute_with_log_stdout(self, log_mock, log_stdout=None):
|
||||||
|
with mock.patch.object(
|
||||||
|
processutils, 'execute', autospec=True) as execute_mock:
|
||||||
|
execute_mock.return_value = ('stdout', 'stderr')
|
||||||
|
if log_stdout is not None:
|
||||||
|
utils.execute('foo', log_stdout=log_stdout)
|
||||||
|
else:
|
||||||
|
utils.execute('foo')
|
||||||
|
execute_mock.assert_called_once_with('foo')
|
||||||
|
name, args, kwargs = log_mock.debug.mock_calls[0]
|
||||||
|
if log_stdout is False:
|
||||||
|
self.assertEqual(1, log_mock.debug.call_count)
|
||||||
|
self.assertNotIn('stdout', args[0])
|
||||||
|
else:
|
||||||
|
self.assertEqual(2, log_mock.debug.call_count)
|
||||||
|
self.assertIn('stdout', args[0])
|
||||||
|
|
||||||
|
def test_execute_with_log_stdout_default(self):
|
||||||
|
self._test_execute_with_log_stdout()
|
||||||
|
|
||||||
|
def test_execute_with_log_stdout_true(self):
|
||||||
|
self._test_execute_with_log_stdout(log_stdout=True)
|
||||||
|
|
||||||
|
def test_execute_with_log_stdout_false(self):
|
||||||
|
self._test_execute_with_log_stdout(log_stdout=False)
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'LOG', autospec=True)
|
||||||
|
@mock.patch.object(processutils, 'execute', autospec=True)
|
||||||
|
def test_execute_command_not_found(self, execute_mock, log_mock):
|
||||||
|
execute_mock.side_effect = FileNotFoundError
|
||||||
|
self.assertRaises(FileNotFoundError, utils.execute, 'foo')
|
||||||
|
execute_mock.assert_called_once_with('foo')
|
||||||
|
name, args, kwargs = log_mock.debug.mock_calls[0]
|
||||||
|
self.assertEqual(1, log_mock.debug.call_count)
|
||||||
|
self.assertIn('not found', args[0])
|
||||||
|
|
||||||
|
|
||||||
|
class MkfsTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
|
def test_mkfs(self, execute_mock):
|
||||||
|
utils.mkfs('ext4', '/my/block/dev')
|
||||||
|
utils.mkfs('msdos', '/my/msdos/block/dev')
|
||||||
|
utils.mkfs('swap', '/my/swap/block/dev')
|
||||||
|
|
||||||
|
expected = [mock.call('mkfs', '-t', 'ext4', '-F', '/my/block/dev',
|
||||||
|
use_standard_locale=True),
|
||||||
|
mock.call('mkfs', '-t', 'msdos', '/my/msdos/block/dev',
|
||||||
|
use_standard_locale=True),
|
||||||
|
mock.call('mkswap', '/my/swap/block/dev',
|
||||||
|
use_standard_locale=True)]
|
||||||
|
self.assertEqual(expected, execute_mock.call_args_list)
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
|
def test_mkfs_with_label(self, execute_mock):
|
||||||
|
utils.mkfs('ext4', '/my/block/dev', 'ext4-vol')
|
||||||
|
utils.mkfs('msdos', '/my/msdos/block/dev', 'msdos-vol')
|
||||||
|
utils.mkfs('swap', '/my/swap/block/dev', 'swap-vol')
|
||||||
|
|
||||||
|
expected = [mock.call('mkfs', '-t', 'ext4', '-F', '-L', 'ext4-vol',
|
||||||
|
'/my/block/dev',
|
||||||
|
use_standard_locale=True),
|
||||||
|
mock.call('mkfs', '-t', 'msdos', '-n', 'msdos-vol',
|
||||||
|
'/my/msdos/block/dev',
|
||||||
|
use_standard_locale=True),
|
||||||
|
mock.call('mkswap', '-L', 'swap-vol',
|
||||||
|
'/my/swap/block/dev',
|
||||||
|
use_standard_locale=True)]
|
||||||
|
self.assertEqual(expected, execute_mock.call_args_list)
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'execute', autospec=True,
|
||||||
|
side_effect=processutils.ProcessExecutionError(
|
||||||
|
stderr=os.strerror(errno.ENOENT)))
|
||||||
|
def test_mkfs_with_unsupported_fs(self, execute_mock):
|
||||||
|
self.assertRaises(errors.FileSystemNotSupported,
|
||||||
|
utils.mkfs, 'foo', '/my/block/dev')
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'execute', autospec=True,
|
||||||
|
side_effect=processutils.ProcessExecutionError(
|
||||||
|
stderr='fake'))
|
||||||
|
def test_mkfs_with_unexpected_error(self, execute_mock):
|
||||||
|
self.assertRaises(processutils.ProcessExecutionError, utils.mkfs,
|
||||||
|
'ext4', '/my/block/dev', 'ext4-vol')
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'execute', autospec=True)
|
||||||
|
class GetRouteSourceTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
|
def test_get_route_source_ipv4(self, mock_execute):
|
||||||
|
mock_execute.return_value = ('XXX src 1.2.3.4 XXX\n cache', None)
|
||||||
|
|
||||||
|
source = utils.get_route_source('XXX')
|
||||||
|
self.assertEqual('1.2.3.4', source)
|
||||||
|
|
||||||
|
def test_get_route_source_ipv6(self, mock_execute):
|
||||||
|
mock_execute.return_value = ('XXX src 1:2::3:4 metric XXX\n cache',
|
||||||
|
None)
|
||||||
|
|
||||||
|
source = utils.get_route_source('XXX')
|
||||||
|
self.assertEqual('1:2::3:4', source)
|
||||||
|
|
||||||
|
def test_get_route_source_ipv6_linklocal(self, mock_execute):
|
||||||
|
mock_execute.return_value = (
|
||||||
|
'XXX src fe80::1234:1234:1234:1234 metric XXX\n cache', None)
|
||||||
|
|
||||||
|
source = utils.get_route_source('XXX')
|
||||||
|
self.assertIsNone(source)
|
||||||
|
|
||||||
|
def test_get_route_source_ipv6_linklocal_allowed(self, mock_execute):
|
||||||
|
mock_execute.return_value = (
|
||||||
|
'XXX src fe80::1234:1234:1234:1234 metric XXX\n cache', None)
|
||||||
|
|
||||||
|
source = utils.get_route_source('XXX', ignore_link_local=False)
|
||||||
|
self.assertEqual('fe80::1234:1234:1234:1234', source)
|
||||||
|
|
||||||
|
def test_get_route_source_indexerror(self, mock_execute):
|
||||||
|
mock_execute.return_value = ('XXX src \n cache', None)
|
||||||
|
|
||||||
|
source = utils.get_route_source('XXX')
|
||||||
|
self.assertIsNone(source)
|
||||||
|
|
||||||
|
|
||||||
|
class ParseDeviceTagsTestCase(base.IronicAgentTest):
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
result = utils.parse_device_tags("\n\n")
|
||||||
|
self.assertEqual([], list(result))
|
||||||
|
|
||||||
|
def test_parse(self):
|
||||||
|
tags = """
|
||||||
|
PTUUID="00016a50" PTTYPE="dos" LABEL=""
|
||||||
|
TYPE="vfat" PART_ENTRY_SCHEME="gpt" PART_ENTRY_NAME="EFI System Partition"
|
||||||
|
"""
|
||||||
|
result = list(utils.parse_device_tags(tags))
|
||||||
|
self.assertEqual([
|
||||||
|
{'PTUUID': '00016a50', 'PTTYPE': 'dos', 'LABEL': ''},
|
||||||
|
{'TYPE': 'vfat', 'PART_ENTRY_SCHEME': 'gpt',
|
||||||
|
'PART_ENTRY_NAME': 'EFI System Partition'}
|
||||||
|
], result)
|
||||||
|
@ -19,9 +19,11 @@ import copy
|
|||||||
import errno
|
import errno
|
||||||
import glob
|
import glob
|
||||||
import io
|
import io
|
||||||
|
import ipaddress
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -29,11 +31,12 @@ import sys
|
|||||||
import tarfile
|
import tarfile
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
import warnings
|
||||||
|
|
||||||
from ironic_lib import utils as ironic_utils
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import excutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
import requests
|
import requests
|
||||||
import tenacity
|
import tenacity
|
||||||
@ -71,12 +74,135 @@ DEVICE_EXTRACTOR = re.compile(r'^(?:(.*\d)p|(.*\D))(?:\d+)$')
|
|||||||
_EARLY_LOG_BUFFER = []
|
_EARLY_LOG_BUFFER = []
|
||||||
|
|
||||||
|
|
||||||
def execute(*cmd, **kwargs):
|
def execute(*cmd, use_standard_locale=False, log_stdout=True, **kwargs):
|
||||||
"""Convenience wrapper around ironic_lib's execute() method.
|
"""Convenience wrapper around oslo's execute() method.
|
||||||
|
|
||||||
Executes and logs results from a system command.
|
Executes and logs results from a system command. See docs for
|
||||||
|
oslo_concurrency.processutils.execute for usage.
|
||||||
|
|
||||||
|
:param cmd: positional arguments to pass to processutils.execute()
|
||||||
|
:param use_standard_locale: Defaults to False. If set to True,
|
||||||
|
execute command with standard locale
|
||||||
|
added to environment variables.
|
||||||
|
:param log_stdout: Defaults to True. If set to True, logs the output.
|
||||||
|
:param kwargs: keyword arguments to pass to processutils.execute()
|
||||||
|
:returns: (stdout, stderr) from process execution
|
||||||
|
:raises: UnknownArgumentError on receiving unknown arguments
|
||||||
|
:raises: ProcessExecutionError
|
||||||
|
:raises: OSError
|
||||||
"""
|
"""
|
||||||
return ironic_utils.execute(*cmd, **kwargs)
|
if use_standard_locale:
|
||||||
|
env = kwargs.pop('env_variables', os.environ.copy())
|
||||||
|
env['LC_ALL'] = 'C'
|
||||||
|
kwargs['env_variables'] = env
|
||||||
|
|
||||||
|
if kwargs.pop('run_as_root', False):
|
||||||
|
warnings.warn("run_as_root is deprecated and has no effect",
|
||||||
|
DeprecationWarning)
|
||||||
|
|
||||||
|
def _log(stdout, stderr):
|
||||||
|
if log_stdout:
|
||||||
|
try:
|
||||||
|
LOG.debug('Command stdout is: "%s"', stdout)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
LOG.debug('stdout contains invalid UTF-8 characters')
|
||||||
|
stdout = (stdout.encode('utf8', 'surrogateescape')
|
||||||
|
.decode('utf8', 'ignore'))
|
||||||
|
LOG.debug('Command stdout is: "%s"', stdout)
|
||||||
|
try:
|
||||||
|
LOG.debug('Command stderr is: "%s"', stderr)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
LOG.debug('stderr contains invalid UTF-8 characters')
|
||||||
|
stderr = (stderr.encode('utf8', 'surrogateescape')
|
||||||
|
.decode('utf8', 'ignore'))
|
||||||
|
LOG.debug('Command stderr is: "%s"', stderr)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = processutils.execute(*cmd, **kwargs)
|
||||||
|
except FileNotFoundError:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
LOG.debug('Command not found: "%s"', ' '.join(map(str, cmd)))
|
||||||
|
except processutils.ProcessExecutionError as exc:
|
||||||
|
with excutils.save_and_reraise_exception():
|
||||||
|
_log(exc.stdout, exc.stderr)
|
||||||
|
else:
|
||||||
|
_log(result[0], result[1])
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def mkfs(fs, path, label=None):
|
||||||
|
"""Format a file or block device
|
||||||
|
|
||||||
|
:param fs: Filesystem type (examples include 'swap', 'ext3', 'ext4'
|
||||||
|
'btrfs', etc.)
|
||||||
|
:param path: Path to file or block device to format
|
||||||
|
:param label: Volume label to use
|
||||||
|
"""
|
||||||
|
if fs == 'swap':
|
||||||
|
args = ['mkswap']
|
||||||
|
else:
|
||||||
|
args = ['mkfs', '-t', fs]
|
||||||
|
# add -F to force no interactive execute on non-block device.
|
||||||
|
if fs in ('ext3', 'ext4'):
|
||||||
|
args.extend(['-F'])
|
||||||
|
if label:
|
||||||
|
if fs in ('msdos', 'vfat'):
|
||||||
|
label_opt = '-n'
|
||||||
|
else:
|
||||||
|
label_opt = '-L'
|
||||||
|
args.extend([label_opt, label])
|
||||||
|
args.append(path)
|
||||||
|
try:
|
||||||
|
execute(*args, use_standard_locale=True)
|
||||||
|
except processutils.ProcessExecutionError as e:
|
||||||
|
with excutils.save_and_reraise_exception() as ctx:
|
||||||
|
if os.strerror(errno.ENOENT) in e.stderr:
|
||||||
|
ctx.reraise = False
|
||||||
|
LOG.exception('Failed to make file system. '
|
||||||
|
'File system %s is not supported.', fs)
|
||||||
|
raise errors.FileSystemNotSupported(fs=fs)
|
||||||
|
else:
|
||||||
|
LOG.exception('Failed to create a file system '
|
||||||
|
'in %(path)s. Error: %(error)s',
|
||||||
|
{'path': path, 'error': e})
|
||||||
|
|
||||||
|
|
||||||
|
def try_execute(*cmd, **kwargs):
|
||||||
|
"""The same as execute but returns None on error.
|
||||||
|
|
||||||
|
Executes and logs results from a system command. See docs for
|
||||||
|
oslo_concurrency.processutils.execute for usage.
|
||||||
|
|
||||||
|
Instead of raising an exception on failure, this method simply
|
||||||
|
returns None in case of failure.
|
||||||
|
|
||||||
|
:param cmd: positional arguments to pass to processutils.execute()
|
||||||
|
:param kwargs: keyword arguments to pass to processutils.execute()
|
||||||
|
:raises: UnknownArgumentError on receiving unknown arguments
|
||||||
|
:returns: tuple of (stdout, stderr) or None in some error cases
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return execute(*cmd, **kwargs)
|
||||||
|
except (processutils.ProcessExecutionError, OSError) as e:
|
||||||
|
LOG.debug('Command failed: %s', e)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_device_tags(output):
|
||||||
|
"""Parse tags from the lsblk/blkid output.
|
||||||
|
|
||||||
|
Parses format KEY="VALUE" KEY2="VALUE2".
|
||||||
|
|
||||||
|
:return: a generator yielding dicts with information from each line.
|
||||||
|
"""
|
||||||
|
for line in output.strip().split('\n'):
|
||||||
|
if line.strip():
|
||||||
|
try:
|
||||||
|
yield {key: value for key, value in
|
||||||
|
(v.split('=', 1) for v in shlex.split(line))}
|
||||||
|
except ValueError as err:
|
||||||
|
raise ValueError(
|
||||||
|
_("Malformed blkid/lsblk output line '%(line)s': %(err)s")
|
||||||
|
% {'line': line, 'err': err})
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
@ -108,15 +234,15 @@ def mounted(source, dest=None, opts=None, fs_type=None,
|
|||||||
|
|
||||||
mounted = False
|
mounted = False
|
||||||
try:
|
try:
|
||||||
ironic_utils.execute("mount", source, dest, *params,
|
execute("mount", source, dest, *params,
|
||||||
attempts=mount_attempts, delay_on_retry=True)
|
attempts=mount_attempts, delay_on_retry=True)
|
||||||
mounted = True
|
mounted = True
|
||||||
yield dest
|
yield dest
|
||||||
finally:
|
finally:
|
||||||
if mounted:
|
if mounted:
|
||||||
try:
|
try:
|
||||||
ironic_utils.execute("umount", dest, attempts=umount_attempts,
|
execute("umount", dest, attempts=umount_attempts,
|
||||||
delay_on_retry=True)
|
delay_on_retry=True)
|
||||||
except (EnvironmentError,
|
except (EnvironmentError,
|
||||||
processutils.ProcessExecutionError) as exc:
|
processutils.ProcessExecutionError) as exc:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
@ -134,6 +260,16 @@ def mounted(source, dest=None, opts=None, fs_type=None,
|
|||||||
{'dest': dest, 'err': exc})
|
{'dest': dest, 'err': exc})
|
||||||
|
|
||||||
|
|
||||||
|
def unlink_without_raise(path):
|
||||||
|
try:
|
||||||
|
os.unlink(path)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
LOG.warning(f"Failed to unlink {path}, error: {e}")
|
||||||
|
|
||||||
|
|
||||||
def _read_params_from_file(filepath):
|
def _read_params_from_file(filepath):
|
||||||
"""Extract key=value pairs from a file.
|
"""Extract key=value pairs from a file.
|
||||||
|
|
||||||
@ -181,7 +317,7 @@ def _find_vmedia_device_by_labels(labels):
|
|||||||
_early_log('Was unable to execute the lsblk command. %s', e)
|
_early_log('Was unable to execute the lsblk command. %s', e)
|
||||||
return
|
return
|
||||||
|
|
||||||
for device in ironic_utils.parse_device_tags(lsblk_output):
|
for device in parse_device_tags(lsblk_output):
|
||||||
for label in labels:
|
for label in labels:
|
||||||
if label.upper() == device['LABEL'].upper():
|
if label.upper() == device['LABEL'].upper():
|
||||||
candidates.append(device['KNAME'])
|
candidates.append(device['KNAME'])
|
||||||
@ -299,7 +435,7 @@ def _check_vmedia_device(vmedia_device_file):
|
|||||||
'virtual media identification. %s', e)
|
'virtual media identification. %s', e)
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
for device in ironic_utils.parse_device_tags(output):
|
for device in parse_device_tags(output):
|
||||||
if device['TYPE'] == 'part':
|
if device['TYPE'] == 'part':
|
||||||
_early_log('Excluding device %s from virtual media'
|
_early_log('Excluding device %s from virtual media'
|
||||||
'consideration as it is a partition.',
|
'consideration as it is a partition.',
|
||||||
@ -1040,3 +1176,25 @@ def is_char_device(path):
|
|||||||
# Likely because of insufficient permission,
|
# Likely because of insufficient permission,
|
||||||
# race conditions or I/O related errors.
|
# race conditions or I/O related errors.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_route_source(dest, ignore_link_local=True):
|
||||||
|
"""Get the IP address to send packages to destination."""
|
||||||
|
try:
|
||||||
|
out, _err = execute('ip', 'route', 'get', dest)
|
||||||
|
except (EnvironmentError, processutils.ProcessExecutionError) as e:
|
||||||
|
LOG.warning('Cannot get route to host %(dest)s: %(err)s',
|
||||||
|
{'dest': dest, 'err': e})
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
source = out.strip().split('\n')[0].split('src')[1].split()[0]
|
||||||
|
if (ipaddress.ip_address(source).is_link_local
|
||||||
|
and ignore_link_local):
|
||||||
|
LOG.debug('Ignoring link-local source to %(dest)s: %(rec)s',
|
||||||
|
{'dest': dest, 'rec': out})
|
||||||
|
return
|
||||||
|
return source
|
||||||
|
except (IndexError, ValueError):
|
||||||
|
LOG.debug('No route to host %(dest)s, route record: %(rec)s',
|
||||||
|
{'dest': dest, 'rec': out})
|
||||||
|
@ -11,7 +11,7 @@ pyudev>=0.18 # LGPLv2.1+
|
|||||||
requests>=2.14.2 # Apache-2.0
|
requests>=2.14.2 # Apache-2.0
|
||||||
stevedore>=1.20.0 # Apache-2.0
|
stevedore>=1.20.0 # Apache-2.0
|
||||||
tenacity>=6.2.0 # Apache-2.0
|
tenacity>=6.2.0 # Apache-2.0
|
||||||
ironic-lib>=6.0.0 # Apache-2.0
|
|
||||||
Werkzeug>=2.0.0 # BSD License
|
Werkzeug>=2.0.0 # BSD License
|
||||||
cryptography>=2.3 # BSD/Apache-2.0
|
cryptography>=2.3 # BSD/Apache-2.0
|
||||||
tooz>=2.7.2 # Apache-2.0
|
tooz>=2.7.2 # Apache-2.0
|
||||||
|
zeroconf>=0.24.0 # LGPL
|
||||||
|
Loading…
x
Reference in New Issue
Block a user