Example #1
0
def sync_clock(ignore_errors=False):
    """Syncs the software clock of the system.

    This method syncs the system software clock if a NTP server
    was defined in the "[DEFAULT]ntp_server" configuration
    parameter. This method does NOT attempt to sync the hardware
    clock.

    It will try to use either ntpdate or chrony to sync the software
    clock of the system. If neither is found, an exception is raised.

    :param ignore_errors: Boolean value default False that allows for
                          the method to be called and ultimately not
                          raise an exception. This may be useful for
                          opportunistically attempting to sync the
                          system software clock.
    :raises: CommandExecutionError if an error is encountered while
             attempting to sync the software clock.
    """

    if not CONF.ntp_server:
        return

    method = determine_time_method()

    if method == 'ntpdate':
        try:
            execute('ntpdate', CONF.ntp_server)
            LOG.debug('Set software clock using ntpdate')
        except processutils.ProcessExecutionError as e:
            msg = ('Failed to sync with ntp server: '
                   '%s: %s' % (CONF.ntp_server, e))
            LOG.error(msg)
            if CONF.fail_if_clock_not_set or not ignore_errors:
                raise errors.CommandExecutionError(msg)
    elif method == 'chronyd':
        try:
            # stop chronyd, ignore if it ran before or not
            execute('chronyc', 'shutdown', check_exit_code=[0, 1])
            # force a time sync now
            query = "server " + CONF.ntp_server + " iburst"
            execute("chronyd -q \'%s\'" % query, shell=True)
            LOG.debug('Set software clock using chrony')
        except (processutils.ProcessExecutionError,
                errors.CommandExecutionError) as e:
            msg = ('Failed to sync time using chrony to ntp server: '
                   '%s: %s' % (CONF.ntp_server, e))
            LOG.error(msg)
            if CONF.fail_if_clock_not_set or not ignore_errors:
                raise errors.CommandExecutionError(msg)
    else:
        msg = ('Unable to sync clock, available methods of '
               '\'ntpdate\' or \'chrony\' not found.')
        LOG.error(msg)
        if CONF.fail_if_clock_not_set or not ignore_errors:
            raise errors.CommandExecutionError(msg)
Example #2
0
    def execute_command(self, command_name, **kwargs):
        """Execute an agent command."""
        with self.command_lock:
            extension_part, command_part = self.split_command(command_name)

            if len(self.command_results) > 0:
                last_command = list(self.command_results.values())[-1]
                if not last_command.is_done():
                    raise errors.CommandExecutionError('agent is busy')

            try:
                ext = self.get_extension(extension_part)
                result = ext.execute(command_part, **kwargs)
            except KeyError:
                # Extension Not found
                raise errors.RequestedObjectNotFoundError('Extension',
                                                          extension_part)
            except errors.InvalidContentError as e:
                # Any command may raise a InvalidContentError which will be
                # returned to the caller directly.
                raise e
            except Exception as e:
                # Other errors are considered command execution errors, and are
                # recorded as an
                result = SyncCommandResult(command_name,
                                           kwargs,
                                           False,
                                           six.text_type(e))

            self.command_results[result.id] = result
            return result
Example #3
0
    def run(self):
        """Run a command."""
        try:
            result = self.execute_method(**self.command_params)

            if isinstance(result, (bytes, six.text_type)):
                result = {'result': '{}: {}'.format(self.command_name, result)}
            LOG.info('Command: %(name)s, result: %(result)s', {
                'name': self.command_name,
                'result': result
            })
            with self.command_state_lock:
                self.command_result = result
                self.command_status = AgentCommandStatus.SUCCEEDED
        except errors.CleanVersionMismatch as e:
            with self.command_state_lock:
                self.command_error = e
                self.command_status = AgentCommandStatus.CLEAN_VERSION_MISMATCH
                self.command_result = None
            LOG.error('Clean version mismatch for command %s',
                      self.command_name)
        except Exception as e:
            LOG.exception('Command failed: %(name)s, error: %(err)s', {
                'name': self.command_name,
                'err': e
            })
            if not isinstance(e, errors.RESTError):
                e = errors.CommandExecutionError(str(e))

            with self.command_state_lock:
                self.command_error = e
                self.command_status = AgentCommandStatus.FAILED
        finally:
            if self.agent:
                self.agent.force_heartbeat()
Example #4
0
    def __call__(self, url):
        """Execute a GET request and start streaming.

        :param url: Target URL.
        :return: A generator yielding chunks of data.
        """
        @tenacity.retry(
            retry=tenacity.retry_if_exception_type(requests.ConnectionError),
            stop=tenacity.stop_after_attempt(
                CONF.image_download_connection_retries + 1),
            wait=tenacity.wait_fixed(
                CONF.image_download_connection_retry_interval),
            reraise=True)
        def _get_with_retries():
            return requests.get(url, verify=self.verify, cert=self.cert,
                                stream=True,
                                timeout=CONF.image_download_connection_timeout)

        try:
            with _get_with_retries() as resp:
                resp.raise_for_status()
                yield resp.iter_content(self._CHUNK_SIZE)
        except requests.RequestException as exc:
            raise errors.CommandExecutionError(
                "Unable to read data from %s: %s" % (url, exc))
Example #5
0
 def test_execute_command_other_exception(self):
     fake_ext = self.agent.ext_mgr['fake'].obj
     fake_ext.execute = mock.Mock()
     exc = errors.CommandExecutionError('foo bar baz')
     fake_ext.execute.side_effect = exc
     result = self.agent.execute_command('fake.sleep',
                                         sleep_info={"time": 1})
     self.assertEqual(base.AgentCommandStatus.FAILED, result.command_status)
     self.assertEqual(exc, result.command_error)
Example #6
0
 def start_flow(self, flow=None):
     for task in flow:
         for method, params in task.items():
             LOG.info("Executing method %s for now", method)
             result = self.execute_command(method, **params)
             result.join()
             LOG.info("%s method's execution is done", method)
             if result.command_status == base.AgentCommandStatus.FAILED:
                 raise errors.CommandExecutionError("%s was failed" %
                                                    method)
Example #7
0
def _get_partition(device, uuid):
    """Find the partition of a given device."""
    LOG.debug("Find the partition %(uuid)s on device %(dev)s", {
        'dev': device,
        'uuid': uuid
    })

    try:
        # Try to tell the kernel to re-read the partition table
        try:
            utils.execute('partx',
                          '-u',
                          device,
                          attempts=3,
                          delay_on_retry=True)
            utils.execute('udevadm', 'settle')
        except processutils.ProcessExecutionError:
            LOG.warning("Couldn't re-read the partition table "
                        "on device %s", device)

        report = utils.execute('lsblk', '-PbioKNAME,UUID,TYPE', device)[0]
        for line in report.split('\n'):
            part = {}
            # Split into KEY=VAL pairs
            vals = shlex.split(line)
            for key, val in (v.split('=', 1) for v in vals):
                part[key] = val.strip()
            # Ignore non partition
            if part.get('TYPE') != 'part':
                continue

            if part.get('UUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {
                              'uuid': uuid,
                              'dev': device
                          })
                return '/dev/' + part.get('KNAME')
        else:
            error_msg = ("No partition with UUID %(uuid)s found on "
                         "device %(dev)s" % {
                             'uuid': uuid,
                             'dev': device
                         })
            LOG.error(error_msg)
            raise errors.DeviceNotFound(error_msg)
    except processutils.ProcessExecutionError as e:
        error_msg = ('Finding the partition with UUID %(uuid)s on '
                     'device %(dev)s failed with %(err)s' % {
                         'uuid': uuid,
                         'dev': device,
                         'err': e
                     })
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
Example #8
0
    def sync(self):
        """Flush file system buffers forcing changed blocks to disk.

        :raises: CommandExecutionError if flushing file system buffers fails.
        """
        LOG.debug('Flushing file system buffers')
        try:
            utils.execute('sync')
        except processutils.ProcessExecutionError as e:
            error_msg = 'Flushing file system buffers failed. Error: %s' % e
            LOG.error(error_msg)
            raise errors.CommandExecutionError(error_msg)
Example #9
0
    def run(self):
        try:
            result = self.execute_method(**self.command_params)
            with self.command_state_lock:
                self.command_result = result
                self.command_status = AgentCommandStatus.SUCCEEDED

        except Exception as e:
            if not isinstance(e, errors.RESTError):
                e = errors.CommandExecutionError(str(e))

            with self.command_state_lock:
                self.command_error = e
                self.command_status = AgentCommandStatus.FAILED
Example #10
0
def _inject_one(node, ports, fl, root_dev, http_get):
    """Inject one file.

    :param node: A dictionary of the node object
    :param ports: A list of dictionaries containing information
                  of ports for the node
    :param fl: File information.
    :param root_dev: Root device used for the current node.
    :param http_get: Context manager to get HTTP URLs.
    """
    with _find_and_mount_path(fl['path'], fl.get('partition'),
                              root_dev) as path:
        if fl.get('deleted'):
            ironic_utils.unlink_without_raise(path)
            return

        try:
            dirpath = os.path.dirname(path)
            try:
                os.makedirs(dirpath)
            except FileExistsError:
                pass
            else:
                # Use chmod here and below to avoid relying on umask
                if fl.get('dirmode'):
                    os.chmod(dirpath, fl['dirmode'])

            content = fl['content']
            with open(path, 'wb') as fp:
                if '://' in content:
                    # Allow node-specific URLs to be used in a deploy template
                    url = content.format(node=node, ports=ports)
                    with http_get(url) as resp:
                        for chunk in resp:
                            fp.write(chunk)
                else:
                    fp.write(base64.b64decode(content))

            if fl.get('mode'):
                os.chmod(path, fl['mode'])

            if fl.get('owner') is not None or fl.get('group') is not None:
                # -1 means do not change
                os.chown(path, fl.get('owner', -1), fl.get('group', -1))
        except Exception as exc:
            LOG.exception('Failed to process file %s', fl)
            raise errors.CommandExecutionError(
                'Failed to process file %s. %s: %s' %
                (fl, type(exc).__class__, exc))
Example #11
0
def get_command_output(command):
    """Return the output of a given command.

    :param command: The command to be executed.
    :raises: CommandExecutionError if the execution of the command fails.
    :returns: A BytesIO string with the output.
    """
    try:
        out, _ = execute(*command, binary=True, log_stdout=False)
    except (processutils.ProcessExecutionError, OSError) as e:
        error_msg = ('Failed to get the output of the command "%(command)s". '
                     'Error: %(error)s' % {'command': command, 'error': e})
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
    return io.BytesIO(out)
Example #12
0
def create_partition_table(dev_name, partition_table_type):
    """Create a partition table on a disk using parted.

    :param dev_name: the disk where we want to create the partition table.
    :param partition_table_type: the type of partition table we want to
        create, for example gpt or msdos.
    :raises: CommandExecutionError if an error is encountered while
             attempting to create the partition table.
    """
    LOG.info("Creating partition table on %s", dev_name)
    try:
        execute('parted', dev_name, '-s', '--', 'mklabel',
                partition_table_type)
    except processutils.ProcessExecutionError as e:
        msg = "Failed to create partition table on {}: {}".format(dev_name, e)
        raise errors.CommandExecutionError(msg)
Example #13
0
    def run(self):
        """Run a command."""
        try:
            result = self.execute_method(**self.command_params)
            with self.command_state_lock:
                self.command_result = result
                self.command_status = AgentCommandStatus.SUCCEEDED
        except Exception as e:
            if not isinstance(e, errors.RESTError):
                e = errors.CommandExecutionError(str(e))

            with self.command_state_lock:
                self.command_error = e
                self.command_status = AgentCommandStatus.FAILED
        finally:
            if self.agent:
                self.agent.force_heartbeat()
Example #14
0
    def execute_command(self, command_name, **kwargs):
        """Execute an agent command."""
        with self.command_lock:
            LOG.debug('Executing command: %(name)s with args: %(args)s', {
                'name': command_name,
                'args': utils.remove_large_keys(kwargs)
            })
            extension_part, command_part = self.split_command(command_name)

            if len(self.command_results) > 0:
                last_command = list(self.command_results.values())[-1]
                if not last_command.is_done():
                    LOG.error(
                        'Tried to execute %(command)s, agent is still '
                        'executing %(last)s', {
                            'command': command_name,
                            'last': last_command
                        })
                    raise errors.CommandExecutionError('agent is busy')

            try:
                ext = self.get_extension(extension_part)
                result = ext.execute(command_part, **kwargs)
            except KeyError:
                # Extension Not found
                LOG.exception('Extension %s not found', extension_part)
                raise errors.RequestedObjectNotFoundError(
                    'Extension', extension_part)
            except errors.InvalidContentError as e:
                # Any command may raise a InvalidContentError which will be
                # returned to the caller directly.
                LOG.exception('Invalid content error: %s', e)
                raise e
            except Exception as e:
                # Other errors are considered command execution errors, and are
                # recorded as a failed SyncCommandResult with an error message
                LOG.exception('Command execution error: %s', e)
                result = SyncCommandResult(command_name, kwargs, False, e)
            LOG.info('Command %(name)s completed: %(result)s', {
                'name': command_name,
                'result': utils.remove_large_keys(result)
            })
            self.command_results[result.id] = result
            return result
Example #15
0
    def test_async_command_failure(self):
        result = base.AsyncCommandResult('foo_command', {'fail': True},
                                         foo_execute)
        expected_result = {
            'id': result.id,
            'command_name': 'foo_command',
            'command_status': 'RUNNING',
            'command_result': None,
            'command_error': None,
        }
        self.assertEqualEncoded(expected_result, result)

        result.start()
        result.join()

        expected_result['command_status'] = 'FAILED'
        expected_result['command_error'] = errors.CommandExecutionError(
            str(EXPECTED_ERROR))

        self.assertEqualEncoded(expected_result, result)
Example #16
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)
Example #17
0
def _install_grub2(device, root_uuid, efi_system_part_uuid=None,
                   prep_boot_part_uuid=None):
    """Install GRUB2 bootloader on a given device."""
    LOG.debug("Installing GRUB2 bootloader on device %s", device)

    efi_partition = None
    efi_partition_mount_point = None
    efi_mounted = False

    # NOTE(TheJulia): Seems we need to get this before ever possibly
    # restart the device in the case of multi-device RAID as pyudev
    # doesn't exactly like the partition disappearing.
    root_partition = _get_partition(device, uuid=root_uuid)

    # If the root device is an md device (or partition), restart the device
    # (to help grub finding it) and identify the underlying holder disks
    # to install grub.
    if hardware.is_md_device(device):
        hardware.md_restart(device)
        # If an md device, we need to rescan the devices anyway to pickup
        # the md device partition.
        _rescan_device(device)
    elif (_is_bootloader_loaded(device)
          and not (efi_system_part_uuid
                   or prep_boot_part_uuid)):
        # We always need to put the bootloader in place with software raid
        # so it is okay to elif into the skip doing a bootloader step.
        LOG.info("Skipping installation of bootloader on device %s "
                 "as it is already marked bootable.", device)
        return
    try:
        # Mount the partition and binds
        path = tempfile.mkdtemp()

        if efi_system_part_uuid:
            efi_partition = _get_partition(device, uuid=efi_system_part_uuid)
            efi_partition_mount_point = os.path.join(path, "boot/efi")

        # For power we want to install grub directly onto the PreP partition
        if prep_boot_part_uuid:
            device = _get_partition(device, uuid=prep_boot_part_uuid)

        # If the root device is an md device (or partition),
        # identify the underlying holder disks to install grub.
        if hardware.is_md_device(device):
            disks = hardware.get_holder_disks(device)
        else:
            disks = [device]

        utils.execute('mount', root_partition, path)
        for fs in BIND_MOUNTS:
            utils.execute('mount', '-o', 'bind', fs, path + fs)

        utils.execute('mount', '-t', 'sysfs', 'none', path + '/sys')

        if efi_partition:
            if not os.path.exists(efi_partition_mount_point):
                os.makedirs(efi_partition_mount_point)
            utils.execute('mount', efi_partition, efi_partition_mount_point)
            efi_mounted = True

        binary_name = "grub"
        if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')):
            binary_name = "grub2"

        # Add /bin to PATH variable as grub requires it to find efibootmgr
        # when running in uefi boot mode.
        # Add /usr/sbin to PATH variable to ensure it is there as we do
        # not use full path to grub binary anymore.
        path_variable = os.environ.get('PATH', '')
        path_variable = '%s:/bin:/usr/sbin' % path_variable

        # Install grub. Normally, grub goes to one disk only. In case of
        # md devices, grub goes to all underlying holder (RAID-1) disks.
        LOG.info("GRUB2 will be installed on disks %s", disks)
        for grub_disk in disks:
            LOG.debug("Installing GRUB2 on disk %s", grub_disk)
            utils.execute('chroot %(path)s /bin/sh -c '
                          '"%(bin)s-install %(dev)s"' %
                          {'path': path, 'bin': binary_name,
                           'dev': grub_disk},
                          shell=True, env_variables={'PATH': path_variable})
            LOG.debug("GRUB2 successfully installed on device %s", grub_disk)

        # Also run grub-install with --removable, this installs grub to the
        # EFI fallback path. Useful if the NVRAM wasn't written correctly,
        # was reset or if testing with virt as libvirt resets the NVRAM
        # on instance start.
        # This operation is essentially a copy operation. Use of the
        # --removable flag, per the grub-install source code changes
        # the default file to be copied, destination file name, and
        # prevents NVRAM from being updated.
        # We only run grub2_install for uefi if we can't verify the uefi bits
        if efi_partition:
            utils.execute('chroot %(path)s /bin/sh -c '
                          '"%(bin)s-install %(dev)s --removable"' %
                          {'path': path, 'bin': binary_name, 'dev': device},
                          shell=True, env_variables={'PATH': path_variable})

        # If the image has dracut installed, set the rd.md.uuid kernel
        # parameter for discovered md devices.
        if hardware.is_md_device(device) and _has_dracut(path):
            rd_md_uuids = ["rd.md.uuid=%s" % x['UUID']
                           for x in hardware.md_get_raid_devices().values()]

            LOG.debug("Setting rd.md.uuid kernel parameters: %s", rd_md_uuids)
            with open('%s/etc/default/grub' % path, 'r') as g:
                contents = g.read()
            with open('%s/etc/default/grub' % path, 'w') as g:
                g.write(
                    re.sub(r'GRUB_CMDLINE_LINUX="(.*)"',
                           r'GRUB_CMDLINE_LINUX="\1 %s"'
                           % " ".join(rd_md_uuids),
                           contents))

        # Generate the grub configuration file
        utils.execute('chroot %(path)s /bin/sh -c '
                      '"%(bin)s-mkconfig -o '
                      '/boot/%(bin)s/grub.cfg"' %
                      {'path': path, 'bin': binary_name}, shell=True,
                      env_variables={'PATH': path_variable})

        LOG.info("GRUB2 successfully installed on %s", device)

    except processutils.ProcessExecutionError as e:
        error_msg = ('Installing GRUB2 boot loader to device %(dev)s '
                     'failed with %(err)s.' % {'dev': device, 'err': e})
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)

    finally:
        umount_warn_msg = "Unable to umount %(path)s. Error: %(error)s"
        # Umount binds and partition
        umount_binds_fail = False

        # If umount fails for efi partition, then we cannot be sure that all
        # the changes were written back to the filesystem.
        try:
            if efi_mounted:
                utils.execute('umount', efi_partition_mount_point, attempts=3,
                              delay_on_retry=True)
        except processutils.ProcessExecutionError as e:
            error_msg = ('Umounting efi system partition failed. '
                         'Attempted 3 times. Error: %s' % e)
            LOG.error(error_msg)
            raise errors.CommandExecutionError(error_msg)

        for fs in BIND_MOUNTS:
            try:
                utils.execute('umount', path + fs, attempts=3,
                              delay_on_retry=True)
            except processutils.ProcessExecutionError as e:
                umount_binds_fail = True
                LOG.warning(umount_warn_msg, {'path': path + fs, 'error': e})

        try:
            utils.execute('umount', path + '/sys', attempts=3,
                          delay_on_retry=True)
        except processutils.ProcessExecutionError as e:
            umount_binds_fail = True
            LOG.warning(umount_warn_msg, {'path': path + '/sys', 'error': e})

        # If umounting the binds succeed then we can try to delete it
        if not umount_binds_fail:
            try:
                utils.execute('umount', path, attempts=3, delay_on_retry=True)
            except processutils.ProcessExecutionError as e:
                LOG.warning(umount_warn_msg, {'path': path, 'error': e})
            else:
                # After everything is umounted we can then remove the
                # temporary directory
                shutil.rmtree(path)
Example #18
0
def _manage_uefi(device, efi_system_part_uuid=None):
    """Manage the device looking for valid efi bootloaders to update the nvram.

    This method checks for valid efi bootloaders in the device, if they exists
    it updates the nvram using the efibootmgr.

    :param device: the device to be checked.
    :param efi_system_part_uuid: efi partition uuid.
    :return: True - if it founds any efi bootloader and the nvram was updated
             using the efibootmgr.
             False - if no efi bootloader is found.
    """
    efi_partition = None
    efi_partition_mount_point = None
    efi_mounted = False

    try:
        # Force UEFI to rescan the device. Required if the deployment
        # was over iscsi.
        _rescan_device(device)

        local_path = tempfile.mkdtemp()
        # Trust the contents on the disk in the event of a whole disk image.
        efi_partition = utils.get_efi_part_on_device(device)
        if not efi_partition:
            # _get_partition returns <device>+<partition> and we only need the
            # partition number
            partition = _get_partition(device, uuid=efi_system_part_uuid)
            efi_partition = int(partition.replace(device, ""))

        if efi_partition:
            efi_partition_mount_point = os.path.join(local_path, "boot/efi")
            if not os.path.exists(efi_partition_mount_point):
                os.makedirs(efi_partition_mount_point)

            # The mount needs the device with the partition, in case the
            # device ends with a digit we add a `p` and the partition number we
            # found, otherwise we just join the device and the partition number
            if device[-1].isdigit():
                efi_device_part = '{}p{}'.format(device, efi_partition)
                utils.execute('mount', efi_device_part,
                              efi_partition_mount_point)
            else:
                efi_device_part = '{}{}'.format(device, efi_partition)
                utils.execute('mount', efi_device_part,
                              efi_partition_mount_point)
            efi_mounted = True
        else:
            # If we can't find the partition we need to decide what should
            # happen
            return False
        valid_efi_bootloaders = _get_efi_bootloaders(efi_partition_mount_point)
        if valid_efi_bootloaders:
            _run_efibootmgr(valid_efi_bootloaders, device, efi_partition)
            return True
        else:
            return False

    except processutils.ProcessExecutionError as e:
        error_msg = ('Could not verify uefi on device %(dev)s'
                     'failed with %(err)s.' % {'dev': device, 'err': e})
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
    finally:
        umount_warn_msg = "Unable to umount %(local_path)s. Error: %(error)s"

        try:
            if efi_mounted:
                utils.execute('umount', efi_partition_mount_point,
                              attempts=3, delay_on_retry=True)
        except processutils.ProcessExecutionError as e:
            error_msg = ('Umounting efi system partition failed. '
                         'Attempted 3 times. Error: %s' % e)
            LOG.error(error_msg)
            raise errors.CommandExecutionError(error_msg)

        else:
            # If umounting the binds succeed then we can try to delete it
            try:
                utils.execute('sync')
            except processutils.ProcessExecutionError as e:
                LOG.warning(umount_warn_msg, {'path': local_path, 'error': e})
            else:
                # After everything is umounted we can then remove the
                # temporary directory
                shutil.rmtree(local_path)
Example #19
0
def _install_grub2(device,
                   root_uuid,
                   efi_system_part_uuid=None,
                   prep_boot_part_uuid=None):
    """Install GRUB2 bootloader on a given device."""
    LOG.debug("Installing GRUB2 bootloader on device %s", device)
    root_partition = _get_partition(device, uuid=root_uuid)
    efi_partition = None
    efi_partition_mount_point = None
    efi_mounted = False

    try:
        # Mount the partition and binds
        path = tempfile.mkdtemp()

        if efi_system_part_uuid:
            efi_partition = _get_partition(device, uuid=efi_system_part_uuid)
            efi_partition_mount_point = os.path.join(path, "boot/efi")

        # For power we want to install grub directly onto the PreP partition
        if prep_boot_part_uuid:
            device = _get_partition(device, uuid=prep_boot_part_uuid)

        utils.execute('mount', root_partition, path)
        for fs in BIND_MOUNTS:
            utils.execute('mount', '-o', 'bind', fs, path + fs)

        utils.execute('mount', '-t', 'sysfs', 'none', path + '/sys')

        if efi_partition:
            if not os.path.exists(efi_partition_mount_point):
                os.makedirs(efi_partition_mount_point)
            utils.execute('mount', efi_partition, efi_partition_mount_point)
            efi_mounted = True

        binary_name = "grub"
        if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')):
            binary_name = "grub2"

        # Add /bin to PATH variable as grub requires it to find efibootmgr
        # when running in uefi boot mode.
        # Add /usr/sbin to PATH variable to ensure it is there as we do
        # not use full path to grub binary anymore.
        path_variable = os.environ.get('PATH', '')
        path_variable = '%s:/bin:/usr/sbin' % path_variable

        # Install grub
        utils.execute('chroot %(path)s /bin/sh -c '
                      '"%(bin)s-install %(dev)s"' % {
                          'path': path,
                          'bin': binary_name,
                          'dev': device
                      },
                      shell=True,
                      env_variables={'PATH': path_variable})
        # Also run grub-install with --removable, this installs grub to the
        # EFI fallback path. Useful if the NVRAM wasn't written correctly,
        # was reset or if testing with virt as libvirt resets the NVRAM
        # on instance start.
        # This operation is essentially a copy operation. Use of the
        # --removable flag, per the grub-install source code changes
        # the default file to be copied, destination file name, and
        # prevents NVRAM from being updated.
        if efi_partition:
            utils.execute('chroot %(path)s /bin/sh -c '
                          '"%(bin)s-install %(dev)s --removable"' % {
                              'path': path,
                              'bin': binary_name,
                              'dev': device
                          },
                          shell=True,
                          env_variables={'PATH': path_variable})

        # Generate the grub configuration file
        utils.execute('chroot %(path)s /bin/sh -c '
                      '"%(bin)s-mkconfig -o '
                      '/boot/%(bin)s/grub.cfg"' % {
                          'path': path,
                          'bin': binary_name
                      },
                      shell=True,
                      env_variables={'PATH': path_variable})

        LOG.info("GRUB2 successfully installed on %s", device)

    except processutils.ProcessExecutionError as e:
        error_msg = ('Installing GRUB2 boot loader to device %(dev)s '
                     'failed with %(err)s.' % {
                         'dev': device,
                         'err': e
                     })
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)

    finally:
        umount_warn_msg = "Unable to umount %(path)s. Error: %(error)s"
        # Umount binds and partition
        umount_binds_fail = False

        # If umount fails for efi partition, then we cannot be sure that all
        # the changes were written back to the filesystem.
        try:
            if efi_mounted:
                utils.execute('umount',
                              efi_partition_mount_point,
                              attempts=3,
                              delay_on_retry=True)
        except processutils.ProcessExecutionError as e:
            error_msg = ('Umounting efi system partition failed. '
                         'Attempted 3 times. Error: %s' % e)
            LOG.error(error_msg)
            raise errors.CommandExecutionError(error_msg)

        for fs in BIND_MOUNTS:
            try:
                utils.execute('umount',
                              path + fs,
                              attempts=3,
                              delay_on_retry=True)
            except processutils.ProcessExecutionError as e:
                umount_binds_fail = True
                LOG.warning(umount_warn_msg, {'path': path + fs, 'error': e})

        try:
            utils.execute('umount',
                          path + '/sys',
                          attempts=3,
                          delay_on_retry=True)
        except processutils.ProcessExecutionError as e:
            umount_binds_fail = True
            LOG.warning(umount_warn_msg, {'path': path + '/sys', 'error': e})

        # If umounting the binds succeed then we can try to delete it
        if not umount_binds_fail:
            try:
                utils.execute('umount', path, attempts=3, delay_on_retry=True)
            except processutils.ProcessExecutionError as e:
                LOG.warning(umount_warn_msg, {'path': path, 'error': e})
            else:
                # After everything is umounted we can then remove the
                # temporary directory
                shutil.rmtree(path)
Example #20
0
def _get_partition(device, uuid):
    """Find the partition of a given device."""
    LOG.debug("Find the partition %(uuid)s on device %(dev)s",
              {'dev': device, 'uuid': uuid})

    try:
        # Try to tell the kernel to re-read the partition table
        try:
            utils.execute('partx', '-u', device, attempts=3,
                          delay_on_retry=True)
            utils.execute('udevadm', 'settle')
        except processutils.ProcessExecutionError:
            LOG.warning("Couldn't re-read the partition table "
                        "on device %s", device)

        # If the deploy device is an md device, we want to install on
        # the first partition. We clearly take a shortcut here for now.
        # TODO(arne_wiebalck): Would it possible to use the partition
        #                      UUID and use the "normal" discovery instead?
        if hardware.is_md_device(device):
            md_partition = device + 'p1'
            if (not os.path.exists(md_partition) or
                not stat.S_ISBLK(os.stat(md_partition).st_mode)):
                error_msg = ("Could not find partition %(part)s on md "
                             "device %(dev)s" % {'part': md_partition,
                                                 'dev': device})
                LOG.error(error_msg)
                raise errors.DeviceNotFound(error_msg)
            LOG.debug("Found md device with partition %s", md_partition)
            return md_partition

        lsblk = utils.execute('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE', device)
        report = lsblk[0]
        for line in report.split('\n'):
            part = {}
            # Split into KEY=VAL pairs
            vals = shlex.split(line)
            for key, val in (v.split('=', 1) for v in vals):
                part[key] = val.strip()
            # Ignore non partition
            if part.get('TYPE') != 'part':
                continue

            if part.get('UUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {'uuid': uuid, 'dev': device})
                return '/dev/' + part.get('KNAME')
            if part.get('PARTUUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {'uuid': uuid, 'dev': device})
                return '/dev/' + part.get('KNAME')
        else:
            error_msg = ("No partition with UUID %(uuid)s found on "
                         "device %(dev)s" % {'uuid': uuid, 'dev': device})
            LOG.error(error_msg)
            raise errors.DeviceNotFound(error_msg)
    except processutils.ProcessExecutionError as e:
        error_msg = ('Finding the partition with UUID %(uuid)s on '
                     'device %(dev)s failed with %(err)s' %
                     {'uuid': uuid, 'dev': device, 'err': e})
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
Example #21
0
def sync_clock(ignore_errors=False):
    """Syncs the software clock of the system.

    This method syncs the system software clock if a NTP server
    was defined in the "[DEFAULT]ntp_server" configuration
    parameter. This method does NOT attempt to sync the hardware
    clock.

    It will try to use either ntpdate or chrony to sync the software
    clock of the system. If neither is found, an exception is raised.

    :param ignore_errors: Boolean value default False that allows for
                          the method to be called and ultimately not
                          raise an exception. This may be useful for
                          opportunistically attempting to sync the
                          system software clock.
    :raises: CommandExecutionError if an error is encountered while
             attempting to sync the software clock.
    """

    if not CONF.ntp_server:
        return

    method = determine_time_method()

    if method == 'ntpdate':
        try:
            execute('ntpdate', CONF.ntp_server)
            LOG.debug('Set software clock using ntpdate')
        except processutils.ProcessExecutionError as e:
            msg = ('Failed to sync with ntp server: '
                   '%s: %s' % (CONF.ntp_server, e))
            LOG.error(msg)
            if CONF.fail_if_clock_not_set or not ignore_errors:
                raise errors.CommandExecutionError(msg)
    elif method == 'chronyd':
        try:
            # 0 should be if chronyd started
            # 1 if already running
            execute('chronyd', check_exit_code=[0, 1])
            # NOTE(TheJulia): Once started, chronyd forks and stays in the
            # background as a server service, it will continue to keep the
            # clock in sync.
            try:
                execute('chronyc', 'add', 'server', CONF.ntp_server)
            except processutils.ProcessExecutionError as e:
                if 'Source already present' not in str(e):
                    msg = 'Error occured adding ntp server: %s' % e
                    LOG.error(msg)
                    raise errors.CommandExecutionError(msg)
            # Force the clock to sync now.
            execute('chronyc', 'makestep')
            LOG.debug('Set software clock using chrony')
        except (processutils.ProcessExecutionError,
                errors.CommandExecutionError) as e:
            msg = ('Failed to sync time using chrony to ntp server: '
                   '%s: %s' % (CONF.ntp_server, e))
            LOG.error(msg)
            if CONF.fail_if_clock_not_set or not ignore_errors:
                raise errors.CommandExecutionError(msg)
    else:
        msg = ('Unable to sync clock, available methods of '
               '\'ntpdate\' or \'chrony\' not found.')
        LOG.error(msg)
        if CONF.fail_if_clock_not_set or not ignore_errors:
            raise errors.CommandExecutionError(msg)
Example #22
0
def _get_partition(device, uuid):
    """Find the partition of a given device."""
    LOG.debug("Find the partition %(uuid)s on device %(dev)s", {
        'dev': device,
        'uuid': uuid
    })

    try:
        _rescan_device(device)
        lsblk = utils.execute('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE', device)
        report = lsblk[0]
        for line in report.split('\n'):
            part = {}
            # Split into KEY=VAL pairs
            vals = shlex.split(line)
            for key, val in (v.split('=', 1) for v in vals):
                part[key] = val.strip()
            # Ignore non partition
            if part.get('TYPE') not in ['md', 'part']:
                # NOTE(TheJulia): This technically creates an edge failure
                # case where a filesystem on a whole block device sans
                # partitioning would behave differently.
                continue

            if part.get('UUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {
                              'uuid': uuid,
                              'dev': device
                          })
                return '/dev/' + part.get('KNAME')
            if part.get('PARTUUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {
                              'uuid': uuid,
                              'dev': device
                          })
                return '/dev/' + part.get('KNAME')
        else:
            # NOTE(TheJulia): We may want to consider moving towards using
            # findfs in the future, if we're comfortable with the execution
            # and interaction. There is value in either way though.
            # NOTE(rg): alternative: blkid -l -t UUID=/PARTUUID=
            try:
                findfs, stderr = utils.execute('findfs', 'UUID=%s' % uuid)
                return findfs.strip()
            except processutils.ProcessExecutionError as e:
                LOG.debug(
                    'First fallback detection attempt for locating '
                    'partition via UUID %(uuid)s failed. '
                    'Error: %(err)s', {
                        'uuid': uuid,
                        'err': e
                    })
                try:
                    findfs, stderr = utils.execute('findfs',
                                                   'PARTUUID=%s' % uuid)
                    return findfs.strip()
                except processutils.ProcessExecutionError as e:
                    LOG.debug(
                        'Secondary fallback detection attempt for '
                        'locating partition via UUID %(uuid)s failed. '
                        'Error: %(err)s', {
                            'uuid': uuid,
                            'err': e
                        })

            # Last fallback: In case we cannot find the partition by UUID
            # and the deploy device is an md device, we check if the md
            # device has a partition (which we assume to contain the root fs).
            if hardware.is_md_device(device):
                md_partition = device + 'p1'
                if (os.path.exists(md_partition)
                        and stat.S_ISBLK(os.stat(md_partition).st_mode)):
                    LOG.debug("Found md device with partition %s",
                              md_partition)
                    return md_partition
                else:
                    LOG.debug(
                        'Could not find partition %(part)s on md '
                        'device %(dev)s', {
                            'part': md_partition,
                            'dev': device
                        })

            # Partition not found, time to escalate.
            error_msg = ("No partition with UUID %(uuid)s found on "
                         "device %(dev)s" % {
                             'uuid': uuid,
                             'dev': device
                         })
            LOG.error(error_msg)
            raise errors.DeviceNotFound(error_msg)
    except processutils.ProcessExecutionError as e:
        error_msg = ('Finding the partition with UUID %(uuid)s on '
                     'device %(dev)s failed with %(err)s' % {
                         'uuid': uuid,
                         'dev': device,
                         'err': e
                     })
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
Example #23
0
def _install_grub2(device, root_uuid, efi_system_part_uuid=None):
    """Install GRUB2 bootloader on a given device."""
    LOG.debug("Installing GRUB2 bootloader on device %s", device)
    root_partition = _get_partition(device, uuid=root_uuid)
    efi_partition = None
    efi_partition_mount_point = None

    try:
        # Mount the partition and binds
        path = tempfile.mkdtemp()

        if efi_system_part_uuid:
            efi_partition = _get_partition(device, uuid=efi_system_part_uuid)
            efi_partition_mount_point = os.path.join(path, "boot/efi")

        utils.execute('mount', root_partition, path)
        for fs in BIND_MOUNTS:
            utils.execute('mount', '-o', 'bind', fs, path + fs)

        utils.execute('mount', '-t', 'sysfs', 'none', path + '/sys')

        if efi_partition:
            if not os.path.exists(efi_partition_mount_point):
                os.makedirs(efi_partition_mount_point)
            utils.execute('mount', efi_partition, efi_partition_mount_point)

        binary_name = "grub"
        if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')):
            binary_name = "grub2"

        # Add /bin to PATH variable as grub requires it to find efibootmgr
        # when running in uefi boot mode.
        path_variable = os.environ.get('PATH', '')
        path_variable = '%s:/bin' % path_variable

        # Install grub
        utils.execute('chroot %(path)s /bin/bash -c '
                      '"/usr/sbin/%(bin)s-install %(dev)s"' % {
                          'path': path,
                          'bin': binary_name,
                          'dev': device
                      },
                      shell=True,
                      env_variables={'PATH': path_variable})

        # Generate the grub configuration file
        utils.execute('chroot %(path)s /bin/bash -c '
                      '"/usr/sbin/%(bin)s-mkconfig -o '
                      '/boot/%(bin)s/grub.cfg"' % {
                          'path': path,
                          'bin': binary_name
                      },
                      shell=True,
                      env_variables={'PATH': path_variable})

        LOG.info("GRUB2 successfully installed on %s", device)

    except processutils.ProcessExecutionError as e:
        error_msg = ('Installing GRUB2 boot loader to device %(dev)s '
                     'failed with %(err)s.' % {
                         'dev': device,
                         'err': e
                     })
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)

    finally:
        umount_warn_msg = "Unable to umount %(path)s. Error: %(error)s"
        # Umount binds and partition
        umount_binds_fail = False

        # If umount fails for efi partition, then we cannot be sure that all
        # the changes were written back to the filesystem.
        try:
            if efi_partition:
                utils.execute('umount',
                              efi_partition_mount_point,
                              attempts=3,
                              delay_on_retry=True)
        except processutils.ProcessExecutionError as e:
            error_msg = ('Umounting efi system partition failed. '
                         'Attempted 3 times. Error: %s' % e)
            LOG.error(error_msg)
            raise errors.CommandExecutionError(error_msg)

        for fs in BIND_MOUNTS:
            try:
                utils.execute('umount',
                              path + fs,
                              attempts=3,
                              delay_on_retry=True)
            except processutils.ProcessExecutionError as e:
                umount_binds_fail = True
                LOG.warning(umount_warn_msg, {'path': path + fs, 'error': e})

        try:
            utils.execute('umount',
                          path + '/sys',
                          attempts=3,
                          delay_on_retry=True)
        except processutils.ProcessExecutionError as e:
            umount_binds_fail = True
            LOG.warning(umount_warn_msg, {'path': path + '/sys', 'error': e})

        # If umounting the binds succeed then we can try to delete it
        if not umount_binds_fail:
            try:
                utils.execute('umount', path, attempts=3, delay_on_retry=True)
            except processutils.ProcessExecutionError as e:
                LOG.warning(umount_warn_msg, {'path': path, 'error': e})
            else:
                # After everything is umounted we can then remove the
                # temporary directory
                shutil.rmtree(path)
Example #24
0
def _install_grub2(device,
                   root_uuid,
                   efi_system_part_uuid=None,
                   prep_boot_part_uuid=None,
                   target_boot_mode='bios'):
    """Install GRUB2 bootloader on a given device."""
    LOG.debug("Installing GRUB2 bootloader on device %s", device)

    efi_partitions = None
    efi_part = None
    efi_partition_mount_point = None
    efi_mounted = False
    efi_preserved = False
    holders = None
    path_variable = _get_path_variable()

    # NOTE(TheJulia): Seems we need to get this before ever possibly
    # restart the device in the case of multi-device RAID as pyudev
    # doesn't exactly like the partition disappearing.
    root_partition = _get_partition(device, uuid=root_uuid)

    # If the root device is an md device (or partition), restart the device
    # (to help grub finding it) and identify the underlying holder disks
    # to install grub.
    if hardware.is_md_device(device):
        # If the root device is an md device (or partition),
        # restart the device to help grub find it later on.
        hardware.md_restart(device)
        # If an md device, we need to rescan the devices anyway to pickup
        # the md device partition.
        _rescan_device(device)
    elif (_is_bootloader_loaded(device)
          and not (efi_system_part_uuid or prep_boot_part_uuid)):
        # We always need to put the bootloader in place with software raid
        # so it is okay to elif into the skip doing a bootloader step.
        LOG.info(
            "Skipping installation of bootloader on device %s "
            "as it is already marked bootable.", device)
        return

    try:
        # Mount the partition and binds
        path = tempfile.mkdtemp()
        if efi_system_part_uuid:
            efi_part = _get_partition(device, uuid=efi_system_part_uuid)
            efi_partitions = [efi_part]

        if hardware.is_md_device(device):
            holders = hardware.get_holder_disks(device)
            efi_partitions = _prepare_boot_partitions_for_softraid(
                device, holders, efi_part, target_boot_mode)

        if efi_partitions:
            efi_partition_mount_point = os.path.join(path, "boot/efi")

        # For power we want to install grub directly onto the PreP partition
        if prep_boot_part_uuid:
            device = _get_partition(device, uuid=prep_boot_part_uuid)

        # If the root device is an md device (or partition),
        # identify the underlying holder disks to install grub.
        if hardware.is_md_device(device):
            disks = holders
        else:
            disks = [device]

        utils.execute('mount', root_partition, path)

        _mount_for_chroot(path)

        # UEFI asset management for RAID is handled elsewhere
        if not hardware.is_md_device(device) and efi_partition_mount_point:
            # NOTE(TheJulia): It may make sense to retool all efi
            # asset preservation logic at some point since the paths
            # can be a little different, but largely this is JUST for
            # partition images as there _should not_ be a mount
            # point if we have no efi partitions at all.
            efi_preserved = _try_preserve_efi_assets(
                device, path, efi_system_part_uuid, efi_partitions,
                efi_partition_mount_point)
            if efi_preserved:
                _append_uefi_to_fstab(path, efi_system_part_uuid)
                # Success preserving efi assets
                return
            else:
                # Failure, either via exception or not found
                # which in this case the partition needs to be
                # remounted.
                LOG.debug('No EFI assets were preserved for setup or the '
                          'ramdisk was unable to complete the setup. '
                          'falling back to bootloader installation from'
                          'deployed image.')
                if not os.path.ismount(path):
                    LOG.debug('Re-mounting the root partition.')
                    utils.execute('mount', root_partition, path)

        binary_name = "grub"
        if os.path.exists(os.path.join(path, 'usr/sbin/grub2-install')):
            binary_name = "grub2"

        # Mount all vfat partitions listed in the fstab of the root partition.
        # This is to make sure grub2 finds all files it needs, as some of them
        # may not be inside the root partition but in the ESP (like grub2env).
        LOG.debug("Mounting all partitions inside the image ...")
        utils.execute('chroot %(path)s /bin/sh -c "mount -a -t vfat"' %
                      {'path': path},
                      shell=True,
                      env_variables={'PATH': path_variable})

        if efi_partitions:
            if not os.path.exists(efi_partition_mount_point):
                os.makedirs(efi_partition_mount_point)
            LOG.warning(
                "GRUB2 will be installed for UEFI on efi partitions "
                "%s using the install command which does not place "
                "Secure Boot signed binaries.", efi_partitions)
            for efi_partition in efi_partitions:
                utils.execute('mount', efi_partition,
                              efi_partition_mount_point)
                efi_mounted = True
                # FIXME(rg): does not work in cross boot mode case (target
                # boot mode differs from ramdisk one)
                # Probe for the correct target (depends on the arch, example
                # --target=x86_64-efi)
                utils.execute('chroot %(path)s /bin/sh -c '
                              '"%(bin)s-install"' % {
                                  'path': path,
                                  'bin': binary_name
                              },
                              shell=True,
                              env_variables={'PATH': path_variable})
                # Also run grub-install with --removable, this installs grub to
                # the EFI fallback path. Useful if the NVRAM wasn't written
                # correctly, was reset or if testing with virt as libvirt
                # resets the NVRAM on instance start.
                # This operation is essentially a copy operation. Use of the
                # --removable flag, per the grub-install source code changes
                # the default file to be copied, destination file name, and
                # prevents NVRAM from being updated.
                # We only run grub2_install for uefi if we can't verify the
                # uefi bits
                utils.execute('chroot %(path)s /bin/sh -c '
                              '"%(bin)s-install --removable"' % {
                                  'path': path,
                                  'bin': binary_name
                              },
                              shell=True,
                              env_variables={'PATH': path_variable})
                utils.execute('umount',
                              efi_partition_mount_point,
                              attempts=3,
                              delay_on_retry=True)
                efi_mounted = False
            # NOTE: probably never needed for grub-mkconfig, does not hurt in
            # case of doubt, cleaned in the finally clause anyway
            utils.execute('mount', efi_partitions[0],
                          efi_partition_mount_point)
            efi_mounted = True
        else:
            # FIXME(rg): does not work if ramdisk boot mode is not the same
            # as the target (--target=i386-pc, arch dependent).
            # See previous FIXME

            # Install grub. Normally, grub goes to one disk only. In case of
            # md devices, grub goes to all underlying holder (RAID-1) disks.
            LOG.info("GRUB2 will be installed on disks %s", disks)
            for grub_disk in disks:
                LOG.debug("Installing GRUB2 on disk %s", grub_disk)
                utils.execute(
                    'chroot %(path)s /bin/sh -c "%(bin)s-install %(dev)s"' % {
                        'path': path,
                        'bin': binary_name,
                        'dev': grub_disk
                    },
                    shell=True,
                    env_variables={'PATH': path_variable})
                LOG.debug("GRUB2 successfully installed on device %s",
                          grub_disk)

        # NOTE(TheJulia): Setup grub configuration again since IF we reach
        # this point, then we've manually installed grub which is not the
        # recommended path.
        _configure_grub(device, path)

        if efi_mounted:
            _append_uefi_to_fstab(path, efi_system_part_uuid)

        LOG.info("GRUB2 successfully installed on %s", device)

    except processutils.ProcessExecutionError as e:
        error_msg = ('Installing GRUB2 boot loader to device %(dev)s '
                     'failed with %(err)s.' % {
                         'dev': device,
                         'err': e
                     })
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)

    finally:
        LOG.debug('Executing _install_grub2 clean-up.')
        # Umount binds and partition
        umount_warn_msg = "Unable to umount %(path)s. Error: %(error)s"

        # If umount fails for efi partition, then we cannot be sure that all
        # the changes were written back to the filesystem.
        try:
            if efi_mounted:
                utils.execute('umount',
                              efi_partition_mount_point,
                              attempts=3,
                              delay_on_retry=True)
        except processutils.ProcessExecutionError as e:
            error_msg = ('Umounting efi system partition failed. '
                         'Attempted 3 times. Error: %s' % e)
            LOG.error(error_msg)
            raise errors.CommandExecutionError(error_msg)

        # If umounting the binds succeed then we can try to delete it
        if _umount_all_partitions(path, path_variable, umount_warn_msg):
            try:
                utils.execute('umount', path, attempts=3, delay_on_retry=True)
            except processutils.ProcessExecutionError as e:
                LOG.warning(umount_warn_msg, {'path': path, 'error': e})
            else:
                # After everything is umounted we can then remove the
                # temporary directory
                shutil.rmtree(path)
Example #25
0
def _get_partition(device, uuid):
    """Find the partition of a given device."""
    LOG.debug("Find the partition %(uuid)s on device %(dev)s",
              {'dev': device, 'uuid': uuid})

    try:
        _rescan_device(device)

        # If the deploy device is an md device, we want to install on
        # the first partition. We clearly take a shortcut here for now.
        # TODO(arne_wiebalck): Would it possible to use the partition
        #                      UUID and use the "normal" discovery instead?
        if hardware.is_md_device(device):
            md_partition = device + 'p1'
            if (not os.path.exists(md_partition) or
                not stat.S_ISBLK(os.stat(md_partition).st_mode)):
                error_msg = ("Could not find partition %(part)s on md "
                             "device %(dev)s" % {'part': md_partition,
                                                 'dev': device})
                LOG.error(error_msg)
                raise errors.DeviceNotFound(error_msg)
            LOG.debug("Found md device with partition %s", md_partition)
            return md_partition

        lsblk = utils.execute('lsblk', '-PbioKNAME,UUID,PARTUUID,TYPE', device)
        report = lsblk[0]
        for line in report.split('\n'):
            part = {}
            # Split into KEY=VAL pairs
            vals = shlex.split(line)
            for key, val in (v.split('=', 1) for v in vals):
                part[key] = val.strip()
            # Ignore non partition
            if part.get('TYPE') != 'part':
                # NOTE(TheJulia): This techincally creates an edge failure
                # case where a filesystem on a whole block device sans
                # partitioning would behave differently.
                continue

            if part.get('UUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {'uuid': uuid, 'dev': device})
                return '/dev/' + part.get('KNAME')
            if part.get('PARTUUID') == uuid:
                LOG.debug("Partition %(uuid)s found on device "
                          "%(dev)s", {'uuid': uuid, 'dev': device})
                return '/dev/' + part.get('KNAME')
        else:
            # NOTE(TheJulia): We may want to consider moving towards using
            # findfs in the future, if we're comfortable with the execution
            # and interaction. There is value in either way though.
            try:
                findfs, stderr = utils.execute('findfs', 'UUID=%s' % uuid)
                return findfs.strip()
            except processutils.ProcessExecutionError as e:
                LOG.debug('First fallback detection attempt for locating '
                          'partition via UUID %(uuid)s failed. '
                          'Error: %(err)s',
                          {'uuid': uuid,
                           'err': e})
                try:
                    findfs, stderr = utils.execute(
                        'findfs', 'PARTUUID=%s' % uuid)
                    return findfs.strip()
                except processutils.ProcessExecutionError as e:
                    LOG.debug('Secondary fallback detection attempt for '
                              'locating partition via UUID %(uuid)s failed. '
                              'Error: %(err)s',
                              {'uuid': uuid,
                               'err': e})
            error_msg = ("No partition with UUID %(uuid)s found on "
                         "device %(dev)s" % {'uuid': uuid, 'dev': device})
            LOG.error(error_msg)
            raise errors.DeviceNotFound(error_msg)
    except processutils.ProcessExecutionError as e:
        error_msg = ('Finding the partition with UUID %(uuid)s on '
                     'device %(dev)s failed with %(err)s' %
                     {'uuid': uuid, 'dev': device, 'err': e})
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
Example #26
0
 def test_fail(self, mock_collect):
     data = {}
     mock_collect.side_effect = errors.CommandExecutionError('boom')
     self.assertIsNone(inspector.collect_logs(data, None))
     self.assertNotIn('logs', data)
Example #27
0
def _manage_uefi(device, efi_system_part_uuid=None):
    """Manage the device looking for valid efi bootloaders to update the nvram.

    This method checks for valid efi bootloaders in the device, if they exists
    it updates the nvram using the efibootmgr.

    :param device: the device to be checked.
    :param efi_system_part_uuid: efi partition uuid.
    :raises: DeviceNotFound if the efi partition cannot be found.
    :return: True - if it founds any efi bootloader and the nvram was updated
             using the efibootmgr.
             False - if no efi bootloader is found.
    """
    efi_partition_mount_point = None
    efi_mounted = False

    try:
        # Force UEFI to rescan the device. Required if the deployment
        # was over iscsi.
        _rescan_device(device)

        local_path = tempfile.mkdtemp()
        # Trust the contents on the disk in the event of a whole disk image.
        efi_partition = utils.get_efi_part_on_device(device)
        if not efi_partition and efi_system_part_uuid:
            # _get_partition returns <device>+<partition> and we only need the
            # partition number
            partition = _get_partition(device, uuid=efi_system_part_uuid)
            efi_partition = int(partition.replace(device, ""))

        if not efi_partition:
            # NOTE(dtantsur): we cannot have a valid EFI deployment without an
            # EFI partition at all. This code path is easily hit when using an
            # image that is not UEFI compatible (which sadly applies to most
            # cloud images out there, with a nice exception of Ubuntu).
            raise errors.DeviceNotFound(
                "No EFI partition could be detected on device %s and "
                "EFI partition UUID has not been recorded during deployment "
                "(which is often the case for whole disk images). "
                "Are you using a UEFI-compatible image?" % device)

        efi_partition_mount_point = os.path.join(local_path, "boot/efi")
        if not os.path.exists(efi_partition_mount_point):
            os.makedirs(efi_partition_mount_point)

        # The mount needs the device with the partition, in case the
        # device ends with a digit we add a `p` and the partition number we
        # found, otherwise we just join the device and the partition number
        if device[-1].isdigit():
            efi_device_part = '{}p{}'.format(device, efi_partition)
            utils.execute('mount', efi_device_part, efi_partition_mount_point)
        else:
            efi_device_part = '{}{}'.format(device, efi_partition)
            utils.execute('mount', efi_device_part, efi_partition_mount_point)
        efi_mounted = True

        valid_efi_bootloaders = _get_efi_bootloaders(efi_partition_mount_point)
        if valid_efi_bootloaders:
            _run_efibootmgr(valid_efi_bootloaders, device, efi_partition)
            return True
        else:
            # NOTE(dtantsur): if we have an empty EFI partition, try to use
            # grub-install to populate it.
            return False

    except processutils.ProcessExecutionError as e:
        error_msg = ('Could not verify uefi on device %(dev)s'
                     'failed with %(err)s.' % {
                         'dev': device,
                         'err': e
                     })
        LOG.error(error_msg)
        raise errors.CommandExecutionError(error_msg)
    finally:
        LOG.debug('Executing _manage_uefi clean-up.')
        umount_warn_msg = "Unable to umount %(local_path)s. Error: %(error)s"

        try:
            if efi_mounted:
                utils.execute('umount',
                              efi_partition_mount_point,
                              attempts=3,
                              delay_on_retry=True)
        except processutils.ProcessExecutionError as e:
            error_msg = ('Umounting efi system partition failed. '
                         'Attempted 3 times. Error: %s' % e)
            LOG.error(error_msg)
            raise errors.CommandExecutionError(error_msg)

        else:
            # If umounting the binds succeed then we can try to delete it
            try:
                utils.execute('sync')
            except processutils.ProcessExecutionError as e:
                LOG.warning(umount_warn_msg, {'path': local_path, 'error': e})
            else:
                # After everything is umounted we can then remove the
                # temporary directory
                shutil.rmtree(local_path)