示例#1
0
def _validate_image_info(ext, image_info=None, **kwargs):
    """Validates the image_info dictionary has all required information.

    :param ext: Object 'self'. Unused by this function directly, but left for
                compatibility with async_command validation.
    :param image_info: Image information dictionary.
    :param kwargs: Additional keyword arguments. Unused, but here for
                   compatibility with async_command validation.
    :raises: InvalidCommandParamsError if the data contained in image_info
             does not match type and key:value pair requirements and
             expectations.
    """
    image_info = image_info or {}

    for field in ['id', 'urls', 'checksum']:
        if field not in image_info:
            msg = 'Image is missing \'{0}\' field.'.format(field)
            raise errors.InvalidCommandParamsError(msg)

    if type(image_info['urls']) != list or not image_info['urls']:
        raise errors.InvalidCommandParamsError(
            'Image \'urls\' must be a list with at least one element.')

    if (not isinstance(image_info['checksum'], six.string_types)
            or not image_info['checksum']):
        raise errors.InvalidCommandParamsError(
            'Image \'checksum\' must be a non-empty string.')
def _write_partition_image(image, image_info, device):
    """Call disk_util to create partition and write the partition image."""
    node_uuid = image_info.get('node_uuid')
    preserve_ep = image_info['preserve_ephemeral']
    configdrive = image_info['configdrive']
    boot_option = image_info.get('boot_option', 'netboot')
    boot_mode = image_info.get('deploy_boot_mode', 'bios')
    disk_label = image_info.get('disk_label', 'msdos')
    image_mb = disk_utils.get_image_mb(image)
    root_mb = image_info['root_mb']
    if image_mb > int(root_mb):
        msg = ('Root partition is too small for requested image. Image '
               'virtual size: {0} MB, Root size: {1} MB').format(
                   image_mb, root_mb)
        raise errors.InvalidCommandParamsError(msg)
    try:
        return disk_utils.work_on_disk(device,
                                       root_mb,
                                       image_info['swap_mb'],
                                       image_info['ephemeral_mb'],
                                       image_info['ephemeral_format'],
                                       image,
                                       node_uuid,
                                       preserve_ephemeral=preserve_ep,
                                       configdrive=configdrive,
                                       boot_option=boot_option,
                                       boot_mode=boot_mode,
                                       disk_label=disk_label)
    except processutils.ProcessExecutionError as e:
        raise errors.ImageWriteError(device, e.exit_code, e.stdout, e.stderr)
示例#3
0
    def _run_shutdown_command(self, command):
        """Run the shutdown or reboot command

        :param command: A string having the command to be run.
        :raises: InvalidCommandParamsError if the passed command is not
            equal to poweroff or reboot.
        :raises: SystemRebootError if the command errors out with an
            unsuccessful exit code.
        """
        if command not in ('reboot', 'poweroff'):
            msg = (('Expected the command "poweroff" or "reboot" '
                    'but received "%s".') % command)
            raise errors.InvalidCommandParamsError(msg)
        try:
            self.sync()
        except errors.CommandExecutionError as e:
            LOG.warning('Failed to sync file system buffers: % s', e)
        try:
            _, stderr = utils.execute(command,
                                      use_standard_locale=True,
                                      check_exit_code=[0])
            if 'ignoring request.' in stderr:
                LOG.debug(
                    '%s command failed with error %s, '
                    'falling back to sysrq-trigger.', command, stderr)
                if command == 'poweroff':
                    utils.execute("echo o > /proc/sysrq-trigger", shell=True)
                elif command == 'reboot':
                    utils.execute("echo b > /proc/sysrq-trigger", shell=True)
        except processutils.ProcessExecutionError as e:
            raise errors.SystemRebootError(e.exit_code, e.stdout, e.stderr)
def _validate_image_info(ext, image_info=None, **kwargs):
    image_info = image_info or {}

    for field in ['id', 'urls', 'checksum']:
        if field not in image_info:
            msg = 'Image is missing \'{0}\' field.'.format(field)
            raise errors.InvalidCommandParamsError(msg)

    if type(image_info['urls']) != list or not image_info['urls']:
        raise errors.InvalidCommandParamsError(
            'Image \'urls\' must be a list with at least one element.')

    if (not isinstance(image_info['checksum'], six.string_types)
            or not image_info['checksum']):
        raise errors.InvalidCommandParamsError(
            'Image \'checksum\' must be a non-empty string.')
示例#5
0
def _validate_files(from_properties, from_args):
    """Sanity check for files."""
    if not isinstance(from_properties, list):
        raise errors.InvalidCommandParamsError(
            "The `inject_files` node property must be a list, got %s" %
            type(from_properties).__name__)
    if not isinstance(from_args, list):
        raise errors.InvalidCommandParamsError(
            "The `files` argument must be a list, got %s" %
            type(from_args).__name__)

    files = from_properties + from_args
    failures = []

    for fl in files:
        unknown = set(fl) - {
            'path', 'partition', 'content', 'deleted', 'mode', 'dirmode',
            'owner', 'group'
        }
        if unknown:
            failures.append('unexpected fields in %s: %s' %
                            (fl, ', '.join(unknown)))

        if not fl.get('path'):
            failures.append('expected a path in %s' % fl)
        elif os.path.dirname(fl['path']) == '/' and not fl.get('partition'):
            failures.append('%s in root directory requires "partition"' % fl)
        elif fl['path'].endswith('/'):
            failures.append('directories not supported for %s' % fl)

        if fl.get('content') and fl.get('deleted'):
            failures.append('content cannot be used with deleted in %s' % fl)

        for field in ('owner', 'group', 'mode', 'dirmode'):
            if field in fl and type(fl[field]) is not int:
                failures.append('%s must be a number in %s' % (field, fl))

    if failures:
        raise errors.InvalidCommandParamsError(
            "Validation of files failed: %s" % '; '.join(failures))

    return files
示例#6
0
def _write_partition_image(image, image_info, device):
    """Call disk_util to create partition and write the partition image.

    :param image: Local path to image file to be written to the partition.
        If ``None``, the image is not populated.
    :param image_info: Image information dictionary.
    :param device: The device name, as a string, on which to store the image.
                   Example: '/dev/sda'

    :raises: InvalidCommandParamsError if the partition is too small for the
             provided image.
    :raises: ImageWriteError if writing the image to disk encounters any error.
    """
    # Retrieve the cached node as it has the latest information
    # and allows us to also sanity check the deployment so we don't end
    # up writing MBR when we're in UEFI mode.
    cached_node = hardware.get_cached_node()

    node_uuid = image_info.get('node_uuid')
    preserve_ep = image_info['preserve_ephemeral']
    configdrive = image_info['configdrive']
    boot_option = image_info.get('boot_option', 'local')
    boot_mode = utils.get_node_boot_mode(cached_node)
    disk_label = utils.get_partition_table_type_from_specs(cached_node)
    root_mb = image_info['root_mb']

    cpu_arch = hardware.dispatch_to_managers('get_cpus').architecture

    if image is not None:
        image_mb = disk_utils.get_image_mb(image)
        if image_mb > int(root_mb):
            msg = ('Root partition is too small for requested image. Image '
                   'virtual size: {} MB, Root size: {} MB').format(
                       image_mb, root_mb)
            raise errors.InvalidCommandParamsError(msg)

    try:
        return disk_utils.work_on_disk(device,
                                       root_mb,
                                       image_info['swap_mb'],
                                       image_info['ephemeral_mb'],
                                       image_info['ephemeral_format'],
                                       image,
                                       node_uuid,
                                       preserve_ephemeral=preserve_ep,
                                       configdrive=configdrive,
                                       boot_option=boot_option,
                                       boot_mode=boot_mode,
                                       disk_label=disk_label,
                                       cpu_arch=cpu_arch)
    except processutils.ProcessExecutionError as e:
        raise errors.ImageWriteError(device, e.exit_code, e.stdout, e.stderr)
示例#7
0
def _validate_image_info(ext, image_info=None, **kwargs):
    """Validates the image_info dictionary has all required information.

    :param ext: Object 'self'. Unused by this function directly, but left for
                compatibility with async_command validation.
    :param image_info: Image information dictionary.
    :param kwargs: Additional keyword arguments. Unused, but here for
                   compatibility with async_command validation.
    :raises: InvalidCommandParamsError if the data contained in image_info
             does not match type and key:value pair requirements and
             expectations.
    """
    image_info = image_info or {}

    md5sum_avail = False
    os_hash_checksum_avail = False

    for field in ['id', 'urls']:
        if field not in image_info:
            msg = 'Image is missing \'{}\' field.'.format(field)
            raise errors.InvalidCommandParamsError(msg)

    if type(image_info['urls']) != list or not image_info['urls']:
        raise errors.InvalidCommandParamsError(
            'Image \'urls\' must be a list with at least one element.')

    checksum = image_info.get('checksum')
    if checksum is not None:
        if (not isinstance(image_info['checksum'], str)
                or not image_info['checksum']):
            raise errors.InvalidCommandParamsError(
                'Image \'checksum\' must be a non-empty string.')
        md5sum_avail = True

    os_hash_algo = image_info.get('os_hash_algo')
    os_hash_value = image_info.get('os_hash_value')
    if os_hash_algo or os_hash_value:
        if (not isinstance(os_hash_algo, str)
                or not os_hash_algo):
            raise errors.InvalidCommandParamsError(
                'Image \'os_hash_algo\' must be a non-empty string.')
        if (not isinstance(os_hash_value, str)
                or not os_hash_value):
            raise errors.InvalidCommandParamsError(
                'Image \'os_hash_value\' must be a non-empty string.')
        os_hash_checksum_avail = True

    if not (md5sum_avail or os_hash_checksum_avail):
        raise errors.InvalidCommandParamsError(
            'Image checksum is not available, either the \'checksum\' field '
            'or the \'os_hash_algo\' and \'os_hash_value\' fields pair must '
            'be set for image verification.')
示例#8
0
def _write_partition_image(image, image_info, device):
    """Call disk_util to create partition and write the partition image.

    :param image: Local path to image file to be written to the partition.
    :param image_info: Image information dictionary.
    :param device: The device name, as a string, on which to store the image.
                   Example: '/dev/sda'

    :raises: InvalidCommandParamsError if the partition is too small for the
             provided image.
    :raises: ImageWriteError if writing the image to disk encounters any error.
    """
    node_uuid = image_info.get('node_uuid')
    preserve_ep = image_info['preserve_ephemeral']
    configdrive = image_info['configdrive']
    boot_option = image_info.get('boot_option', 'netboot')
    boot_mode = image_info.get('deploy_boot_mode', 'bios')
    disk_label = image_info.get('disk_label', 'msdos')
    image_mb = disk_utils.get_image_mb(image)
    root_mb = image_info['root_mb']

    cpu_arch = hardware.dispatch_to_managers('get_cpus').architecture

    if image_mb > int(root_mb):
        msg = ('Root partition is too small for requested image. Image '
               'virtual size: {} MB, Root size: {} MB').format(
                   image_mb, root_mb)
        raise errors.InvalidCommandParamsError(msg)
    try:
        return disk_utils.work_on_disk(device,
                                       root_mb,
                                       image_info['swap_mb'],
                                       image_info['ephemeral_mb'],
                                       image_info['ephemeral_format'],
                                       image,
                                       node_uuid,
                                       preserve_ephemeral=preserve_ep,
                                       configdrive=configdrive,
                                       boot_option=boot_option,
                                       boot_mode=boot_mode,
                                       disk_label=disk_label,
                                       cpu_arch=cpu_arch)
    except processutils.ProcessExecutionError as e:
        raise errors.ImageWriteError(device, e.exit_code, e.stdout, e.stderr)
示例#9
0
    def _run_shutdown_command(self, command):
        """Run the shutdown or reboot command

        :param command: A string having the command to be run.
        :raises: InvalidCommandParamsError if the passed command is not
            equal to poweroff or reboot.
        :raises: SystemRebootError if the command errors out with an
            unsuccessful exit code.
        """
        # TODO(TheJulia): When we have deploy/clean steps, we should remove
        # this upon shutdown. The clock sync deploy step can run before
        # completing other operations.
        self._sync_clock(ignore_errors=True)

        if command not in ('reboot', 'poweroff'):
            msg = (('Expected the command "poweroff" or "reboot" '
                    'but received "%s".') % command)
            raise errors.InvalidCommandParamsError(msg)
        try:
            self.sync()
        except errors.CommandExecutionError as e:
            LOG.warning('Failed to sync file system buffers: % s', e)

        try:
            _, stderr = utils.execute(command, use_standard_locale=True)
        except processutils.ProcessExecutionError as e:
            LOG.warning('%s command failed with error %s, '
                        'falling back to sysrq-trigger', command, e)
        else:
            if 'ignoring request' in stderr:
                LOG.warning('%s command has been ignored, '
                            'falling back to sysrq-trigger', command)
            else:
                return

        try:
            if command == 'poweroff':
                utils.execute("echo o > /proc/sysrq-trigger", shell=True)
            elif command == 'reboot':
                utils.execute("echo b > /proc/sysrq-trigger", shell=True)
        except processutils.ProcessExecutionError as e:
            raise errors.SystemRebootError(e.exit_code, e.stdout, e.stderr)
示例#10
0
def _find_and_mount_path(path, partition, root_dev):
    """Find the specified path on a device.

    Tries to find the suitable device for the file based on the ``path`` and
    ``partition``, mount the device and provides the actual full path.

    :param path: Path to the file to find.
    :param partition: Device to find the file on or None.
    :param root_dev: Root device from the hardware manager.
    :return: Context manager that yields the full path to the file.
    """
    path = os.path.normpath(path.strip('/'))  # to make path joining work
    if partition:
        try:
            part_num = int(partition)
        except ValueError:
            with ironic_utils.mounted(partition) as part_path:
                yield os.path.join(part_path, path)
        else:
            # TODO(dtantsur): switch to ironic-lib instead:
            # https://review.opendev.org/c/openstack/ironic-lib/+/774502
            part_template = '%s%s'
            if 'nvme' in root_dev:
                part_template = '%sp%s'
            part_dev = part_template % (root_dev, part_num)

            with ironic_utils.mounted(part_dev) as part_path:
                yield os.path.join(part_path, path)
    else:
        try:
            # This turns e.g. etc/sysctl.d/my.conf into etc + sysctl.d/my.conf
            detect_dir, rest_dir = path.split('/', 1)
        except ValueError:
            # Validation ensures that files in / have "partition" present,
            # checking here just in case.
            raise errors.InvalidCommandParamsError(
                "Invalid path %s, must be an absolute path to a file" % path)

        with find_partition_with_path(detect_dir, root_dev) as part_path:
            yield os.path.join(part_path, rest_dir)
示例#11
0
 def test_error_classes(self):
     cases = [
         (errors.InvalidContentError(DETAILS), SAME_DETAILS),
         (errors.NotFound(), SAME_CL_DETAILS),
         (errors.CommandExecutionError(DETAILS), SAME_DETAILS),
         (errors.InvalidCommandError(DETAILS), SAME_DETAILS),
         (errors.InvalidCommandParamsError(DETAILS), SAME_DETAILS),
         (errors.RequestedObjectNotFoundError('type_descr',
                                              'obj_id'), DIFF_CL_DETAILS),
         (errors.IronicAPIError(DETAILS), SAME_DETAILS),
         (errors.HeartbeatError(DETAILS), SAME_DETAILS),
         (errors.LookupNodeError(DETAILS), SAME_DETAILS),
         (errors.LookupAgentIPError(DETAILS), SAME_DETAILS),
         (errors.LookupAgentInterfaceError(DETAILS), SAME_DETAILS),
         (errors.ImageDownloadError('image_id', DETAILS), DIFF_CL_DETAILS),
         (errors.ImageChecksumError('image_id', '/foo/image_id',
                                    'incorrect',
                                    'correct'), DIFF_CL_DETAILS),
         (errors.ImageWriteError('device', 'exit_code', 'stdout',
                                 'stderr'), DIFF_CL_DETAILS),
         (errors.ConfigDriveTooLargeError('filename',
                                          'filesize'), DIFF_CL_DETAILS),
         (errors.ConfigDriveWriteError('device', 'exit_code', 'stdout',
                                       'stderr'), DIFF_CL_DETAILS),
         (errors.SystemRebootError('exit_code', 'stdout',
                                   'stderr'), DIFF_CL_DETAILS),
         (errors.BlockDeviceEraseError(DETAILS), SAME_DETAILS),
         (errors.BlockDeviceError(DETAILS), SAME_DETAILS),
         (errors.VirtualMediaBootError(DETAILS), SAME_DETAILS),
         (errors.UnknownNodeError(), DEFAULT_DETAILS),
         (errors.UnknownNodeError(DETAILS), SAME_DETAILS),
         (errors.HardwareManagerNotFound(), DEFAULT_DETAILS),
         (errors.HardwareManagerNotFound(DETAILS), SAME_DETAILS),
         (errors.HardwareManagerMethodNotFound('method'), DIFF_CL_DETAILS),
         (errors.IncompatibleHardwareMethodError(), DEFAULT_DETAILS),
         (errors.IncompatibleHardwareMethodError(DETAILS), SAME_DETAILS),
     ]
     for (obj, check_details) in cases:
         self._test_class(obj, check_details)
示例#12
0
 def check_cmd_presence(self, ext_obj, ext, cmd):
     if not (hasattr(ext_obj, 'execute') and hasattr(
             ext_obj, 'command_map') and cmd in ext_obj.command_map):
         raise errors.InvalidCommandParamsError(
             "Extension {} doesn't provide {} method".format(ext, cmd))
示例#13
0
def _fake_validator(ext, **kwargs):
    if not kwargs.get('is_valid', True):
        raise errors.InvalidCommandParamsError('error')