This patch is adding a function called parse_root_device_hints to the utils.py module. This function is responsible for parsing the root device hints dictionary from the node's properties attribute. Both Ironic and Ironic Python Agent project have similar functions so adding it to ironic-lib would make it easier to share code between both projects and fix bugs in only one place. Change-Id: Ida6d20d1fdb40e50fe33ffec1c953286d4cbc2b7 Partial-Bug: #1605631
224 lines
7.7 KiB
Python
224 lines
7.7 KiB
Python
# Copyright 2010 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# Copyright 2011 Justin Santa Barbara
|
|
# Copyright (c) 2012 NTT DOCOMO, INC.
|
|
# 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.
|
|
|
|
"""Utilities and helper functions."""
|
|
|
|
import copy
|
|
import errno
|
|
import logging
|
|
import os
|
|
|
|
from oslo_concurrency import processutils
|
|
from oslo_config import cfg
|
|
from oslo_utils import excutils
|
|
from oslo_utils import strutils
|
|
|
|
from ironic_lib.common.i18n import _
|
|
from ironic_lib.common.i18n import _LE
|
|
from ironic_lib.common.i18n import _LW
|
|
from ironic_lib import exception
|
|
|
|
utils_opts = [
|
|
cfg.StrOpt('root_helper',
|
|
default='sudo ironic-rootwrap /etc/ironic/rootwrap.conf',
|
|
help='Command that is prefixed to commands that are run as '
|
|
'root. If not specified, no commands are run as root.'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(utils_opts, group='ironic_lib')
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
VALID_ROOT_DEVICE_HINTS = set(('size', 'model', 'wwn', 'serial', 'vendor',
|
|
'wwn_with_extension', 'wwn_vendor_extension',
|
|
'name', 'rotational'))
|
|
|
|
|
|
def execute(*cmd, **kwargs):
|
|
"""Convenience wrapper around oslo's execute() method.
|
|
|
|
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: keyword-only argument. True | False.
|
|
Defaults to False. If set to True,
|
|
execute command with standard locale
|
|
added to environment variables.
|
|
:param log_stdout: keyword-only argument. True | False. 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
|
|
"""
|
|
|
|
use_standard_locale = kwargs.pop('use_standard_locale', False)
|
|
if use_standard_locale:
|
|
env = kwargs.pop('env_variables', os.environ.copy())
|
|
env['LC_ALL'] = 'C'
|
|
kwargs['env_variables'] = env
|
|
|
|
log_stdout = kwargs.pop('log_stdout', True)
|
|
|
|
# If root_helper config is not specified, no commands are run as root.
|
|
run_as_root = kwargs.get('run_as_root', False)
|
|
if run_as_root:
|
|
if not CONF.ironic_lib.root_helper:
|
|
kwargs['run_as_root'] = False
|
|
else:
|
|
kwargs['root_helper'] = CONF.ironic_lib.root_helper
|
|
|
|
result = processutils.execute(*cmd, **kwargs)
|
|
LOG.debug('Execution completed, command line is "%s"',
|
|
' '.join(map(str, cmd)))
|
|
if log_stdout:
|
|
LOG.debug('Command stdout is: "%s"' % result[0])
|
|
LOG.debug('Command stderr is: "%s"' % 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, run_as_root=True, 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(_LE('Failed to make file system. '
|
|
'File system %s is not supported.'), fs)
|
|
raise exception.FileSystemNotSupported(fs=fs)
|
|
else:
|
|
LOG.exception(_LE('Failed to create a file system '
|
|
'in %(path)s. Error: %(error)s'),
|
|
{'path': path, 'error': e})
|
|
|
|
|
|
def unlink_without_raise(path):
|
|
try:
|
|
os.unlink(path)
|
|
except OSError as e:
|
|
if e.errno == errno.ENOENT:
|
|
return
|
|
else:
|
|
LOG.warning(_LW("Failed to unlink %(path)s, error: %(e)s"),
|
|
{'path': path, 'e': e})
|
|
|
|
|
|
def dd(src, dst, *args):
|
|
"""Execute dd from src to dst.
|
|
|
|
:param src: the input file for dd command.
|
|
:param dst: the output file for dd command.
|
|
:param args: a tuple containing the arguments to be
|
|
passed to dd command.
|
|
:raises: processutils.ProcessExecutionError if it failed
|
|
to run the process.
|
|
"""
|
|
LOG.debug("Starting dd process.")
|
|
execute('dd', 'if=%s' % src, 'of=%s' % dst, *args,
|
|
use_standard_locale=True, run_as_root=True, check_exit_code=[0])
|
|
|
|
|
|
def is_http_url(url):
|
|
url = url.lower()
|
|
return url.startswith('http://') or url.startswith('https://')
|
|
|
|
|
|
def list_opts():
|
|
"""Entry point for oslo-config-generator."""
|
|
return [('ironic_lib', utils_opts)]
|
|
|
|
|
|
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) - 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)})
|
|
|
|
if 'size' in root_device:
|
|
try:
|
|
size = int(root_device['size'])
|
|
except ValueError:
|
|
raise ValueError(
|
|
_('Root device hint "size" is not an integer value. '
|
|
'Current value: %s') % root_device['size'])
|
|
|
|
if size <= 0:
|
|
raise ValueError(
|
|
_('Root device hint "size" should be a positive integer. '
|
|
'Current value: %d') % size)
|
|
|
|
root_device['size'] = size
|
|
|
|
if 'rotational' in root_device:
|
|
try:
|
|
root_device['rotational'] = strutils.bool_from_string(
|
|
root_device['rotational'], strict=True)
|
|
except ValueError:
|
|
raise ValueError(
|
|
_('Root device hint "rotational" is not a Boolean value. '
|
|
'Current value: %s') % root_device['rotational'])
|
|
|
|
return root_device
|