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)
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.')
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
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)
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.')
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)
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)
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)
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)
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))
def _fake_validator(ext, **kwargs): if not kwargs.get('is_valid', True): raise errors.InvalidCommandParamsError('error')