def test_both_succeed(self):
        # In the case where both managers will work; only the most specific
        # manager should have it's function called.
        hardware.dispatch_to_managers('both_succeed')

        self.assertEqual(1, self.mainline_hwm.obj._call_counts['both_succeed'])
        self.assertEqual(0, self.generic_hwm.obj._call_counts['both_succeed'])
    def test_mainline_fails(self):
        # Ensure that if the mainline manager is unable to run the method
        # that we properly fall back to generic.
        hardware.dispatch_to_managers('mainline_fail')

        self.assertEqual(
            1, self.mainline_hwm.obj._call_counts['mainline_fail'])
        self.assertEqual(1, self.generic_hwm.obj._call_counts['mainline_fail'])
    def test_mainline_fails(self):
        """Ensure that if the mainline manager is unable to run the method
        that we properly fall back to generic.
        """
        hardware.dispatch_to_managers('mainline_fail')

        self.assertEqual(1, FakeMainlineHardwareManager.mainline_fail.called)
        self.assertEqual(1, FakeGenericHardwareManager.mainline_fail.called)
    def test_both_succeed(self):
        """In the case where both managers will work; only the most specific
        manager should have it's function called.
        """
        hardware.dispatch_to_managers('both_succeed')

        self.assertEqual(1, FakeMainlineHardwareManager.both_succeed.called)
        self.assertEqual(0, FakeGenericHardwareManager.both_succeed.called)
def collect_default(data, failures):
    """The default inspection collector.

    This is the only collector that is called by default. It collects
    the whole inventory as returned by the hardware manager(s).

    It also tries to get BMC address, PXE boot device and the expected
    root device.

    :param data: mutable data that we'll send to inspector
    :param failures: AccumulatedFailures object
    """
    wait_for_dhcp()
    inventory = hardware.dispatch_to_managers('list_hardware_info')

    data['inventory'] = inventory
    # Replicate the same logic as in deploy. We need to make sure that when
    # root device hints are not set, inspector will use the same root disk as
    # will be used for deploy.
    try:
        root_disk = utils.guess_root_disk(inventory['disks'][:])
    except errors.DeviceNotFound:
        root_disk = None
        LOG.warning('no suitable root device detected')
    else:
        data['root_disk'] = root_disk
        LOG.debug('default root device is %s', root_disk.name)
    # The boot interface might not be present, we don't count it as failure.
    # TODO(dtantsur): stop using the boot_interface field.
    data['boot_interface'] = inventory['boot'].pxe_interface
    LOG.debug('boot devices was %s', data['boot_interface'])
    LOG.debug('BMC IP address: %s', inventory.get('bmc_address'))
    def set_agent_advertise_addr(self):
        """Set advertised IP address for the agent, if not already set.

        If agent's advertised IP address is still default (None), try to
        find a better one.  If the agent's network interface is None, replace
        that as well.

        :raises: LookupAgentInterfaceError if a valid network interface cannot
                 be found.
        :raises: LookupAgentIPError if an IP address could not be found
        """
        if self.advertise_address[0] is not None:
            return

        if self.network_interface is None:
            ifaces = self.get_agent_network_interfaces()
        else:
            ifaces = [self.network_interface]

        attempts = 0
        while (attempts < self.ip_lookup_attempts):
            for iface in ifaces:
                found_ip = hardware.dispatch_to_managers('get_ipv4_addr',
                                                         iface)
                if found_ip is not None:
                    self.advertise_address = (found_ip,
                                              self.advertise_address[1])
                    self.network_interface = iface
                    return
            attempts += 1
            time.sleep(self.ip_lookup_sleep)

        raise errors.LookupAgentIPError('Agent could not find a valid IP '
                                        'address.')
    def execute_clean_step(self, step, node, ports, clean_version=None,
                           **kwargs):
        """Execute a clean step.

        :param step: A clean step with 'step', 'priority' and 'interface' keys
        :param node: A dict representation of a node
        :param ports: A dict representation of ports attached to node
        :param clean_version: The clean version as returned by
                              _get_current_clean_version() at the beginning
                              of cleaning/zapping
        :returns: a CommandResult object with command_result set to whatever
            the step returns.
        """
        # Ensure the agent is still the same version, or raise an exception
        _check_clean_version(clean_version)

        if 'step' not in step:
            raise ValueError('Malformed clean_step, no "step" key: %s'.format(
                step))
        try:
            result = hardware.dispatch_to_managers(step['step'], node, ports)
        except Exception as e:
            raise errors.CleaningError(
                'Error performing clean_step %(step)s: %(err)s' %
                {'step': step['step'], 'err': e})
        # Return the step that was executed so we can dispatch
        # to the appropriate Ironic interface
        return {
            'clean_result': result,
            'clean_step': step
        }
    def run(self):
        """Run the Ironic Python Agent."""
        # Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError
        # if there is an issue (uncaught, restart agent)
        self.started_at = _time()
        if not self.standalone:
            content = self.api_client.lookup_node(
                hardware_info=hardware.dispatch_to_managers(
                                  'list_hardware_info'),
                timeout=self.lookup_timeout,
                starting_interval=self.lookup_interval)

            self.node = content['node']
            self.heartbeat_timeout = content['heartbeat_timeout']

        wsgi = simple_server.make_server(
            self.listen_address[0],
            self.listen_address[1],
            self.api,
            server_class=simple_server.WSGIServer)

        if not self.standalone:
            # Don't start heartbeating until the server is listening
            self.heartbeater.start()

        try:
            wsgi.serve_forever()
        except BaseException:
            self.log.exception('shutting down')

        if not self.standalone:
            self.heartbeater.stop()
Example #9
0
    def cache_image(self, image_info=None, force=False):
        """Asynchronously caches specified image to the local OS device.

        :param image_info: Image information dictionary.
        :param force: Optional. If True forces cache_image to download and
                      cache image, even if the same image already exists on
                      the local OS install device. Defaults to False.

        :raises: ImageDownloadError if the image download fails for any reason.
        :raises: ImageChecksumError if the downloaded image's checksum does not
                  match the one reported in image_info.
        :raises: ImageWriteError if writing the image fails.
        """
        LOG.debug('Caching image %s', image_info['id'])
        device = hardware.dispatch_to_managers('get_os_install_device')

        msg = 'image ({0}) already present on device {1} '

        if self.cached_image_id != image_info['id'] or force:
            LOG.debug('Already had %s cached, overwriting',
                      self.cached_image_id)
            self._cache_and_write_image(image_info, device)
            msg = 'image ({0}) cached to device {1} '

        result_msg = _message_format(msg, image_info, device,
                                     self.partition_uuids)

        LOG.info(result_msg)
        return result_msg
Example #10
0
def wait_for_dhcp():
    """Wait until all NIC's get their IP addresses via DHCP or timeout happens.

    Ignores interfaces which do not even have a carrier.

    Note: only supports IPv4 addresses for now.

    :return: True if all NIC's got IP addresses, False if timeout happened.
             Also returns True if waiting is disabled via configuration.
    """
    if not CONF.inspection_dhcp_wait_timeout:
        return True

    threshold = time.time() + CONF.inspection_dhcp_wait_timeout
    while time.time() <= threshold:
        interfaces = hardware.dispatch_to_managers('list_network_interfaces')
        missing = [iface.name for iface in interfaces
                   if iface.has_carrier and not iface.ipv4_address]
        if not missing:
            return True

        LOG.debug('Still waiting for interfaces %s to get IP addresses',
                  missing)
        time.sleep(_DHCP_RETRY_INTERVAL)

    LOG.warning('Not all network interfaces received IP addresses in '
                '%(timeout)d seconds: %(missing)s',
                {'timeout': CONF.inspection_dhcp_wait_timeout,
                 'missing': missing})
    return False
Example #11
0
    def start_iscsi_target(self, iqn=None):
        """Expose the disk as an ISCSI target."""
        # If iqn is not given, generate one
        if iqn is None:
            iqn = 'iqn.2008-10.org.openstack:%s' % uuidutils.generate_uuid()

        device = hardware.dispatch_to_managers('get_os_install_device')
        LOG.debug("Starting ISCSI target with iqn %(iqn)s on device "
                  "%(device)s", {'iqn': iqn, 'device': device})

        try:
            rts_root = rtslib_fb.RTSRoot()
        except (EnvironmentError, rtslib_fb.RTSLibError) as exc:
            LOG.warning('Linux-IO is not available, falling back to TGT. '
                        'Error: %s.', exc)
            rts_root = None

        if rts_root is None:
            _start_tgtd(iqn, device)
        else:
            _start_lio(iqn, device)
            LOG.debug('Linux-IO configuration: %s', rts_root.dump())

        LOG.info('Created iSCSI target with iqn %(iqn)s on device %(dev)s '
                 'using %(method)s',
                 {'iqn': iqn, 'dev': device,
                  'method': 'tgtd' if rts_root is None else 'linux-io'})

        return {"iscsi_target_iqn": iqn}
Example #12
0
    def run(self):
        """Run the Ironic Python Agent."""
        # Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError
        # if there is an issue (uncaught, restart agent)
        self.started_at = _time()

        # Cached hw managers at runtime, not load time. See bug 1490008.
        hardware.load_managers()
        # Operator-settable delay before hardware actually comes up.
        # Helps with slow RAID drivers - see bug 1582797.
        if self.hardware_initialization_delay > 0:
            LOG.info('Waiting %d seconds before proceeding',
                     self.hardware_initialization_delay)
            time.sleep(self.hardware_initialization_delay)

        if not self.standalone:
            # Inspection should be started before call to lookup, otherwise
            # lookup will fail due to unknown MAC.
            uuid = None
            if cfg.CONF.inspection_callback_url:
                uuid = inspector.inspect()

            if self.api_url:
                self._wait_for_interface()
                content = self.api_client.lookup_node(
                    hardware_info=hardware.dispatch_to_managers(
                        'list_hardware_info'),
                    timeout=self.lookup_timeout,
                    starting_interval=self.lookup_interval,
                    node_uuid=uuid)

                LOG.debug('Received lookup results: %s', content)
                self.node = content['node']
                LOG.info('Lookup succeeded, node UUID is %s',
                         self.node['uuid'])
                hardware.cache_node(self.node)
                self.heartbeat_timeout = content['config']['heartbeat_timeout']

                # Update config with values from Ironic
                config = content.get('config', {})
                if config.get('metrics'):
                    for opt, val in config.items():
                        setattr(cfg.CONF.metrics, opt, val)
                if config.get('metrics_statsd'):
                    for opt, val in config.items():
                        setattr(cfg.CONF.metrics_statsd, opt, val)
            elif cfg.CONF.inspection_callback_url:
                LOG.info('No ipa-api-url configured, Heartbeat and lookup '
                         'skipped for inspector.')
            else:
                LOG.error('Neither ipa-api-url nor inspection_callback_url'
                          'found, please check your pxe append parameters.')

        self.serve_ipa_api()

        if not self.standalone and self.api_url:
            self.heartbeater.stop()
Example #13
0
    def start_iscsi_target(self, iqn=None):
        """Expose the disk as an ISCSI target."""
        # If iqn is not given, generate one
        if iqn is None:
            iqn = 'iqn-' + uuidutils.generate_uuid()

        device = hardware.dispatch_to_managers('get_os_install_device')
        _start_iscsi_daemon(iqn, device)
        return {"iscsi_target_iqn": iqn}
    def cache_image(self, image_info=None, force=False):
        device = hardware.dispatch_to_managers("get_os_install_device")

        result_msg = "image ({0}) already present on device {1}"

        if self.cached_image_id != image_info["id"] or force:
            _download_image(image_info)
            _write_image(image_info, device)
            self.cached_image_id = image_info["id"]
            result_msg = "image ({0}) cached to device {1}"

        return result_msg.format(image_info["id"], device)
    def cache_image(self, image_info=None, force=False):
        device = hardware.dispatch_to_managers('get_os_install_device')

        result_msg = 'image ({0}) already present on device {1} root_uuid={2}'
        write_result = None
        if self.cached_image_id != image_info['id'] or force:
            _download_image(image_info)
            write_result = _write_image(image_info, device)
            self.cached_image_id = image_info['id']
            result_msg = 'image ({0}) cached to device {1} root_uuid={2}'

        return result_msg.format(image_info['id'], device, write_result)
Example #16
0
    def prepare_image(self,
                      image_info=None,
                      configdrive=None):
        """Asynchronously prepares specified image on local OS install device.

        In this case, 'prepare' means make local machine completely ready to
        reboot to the image specified by image_info.

        Downloads and writes an image to disk if necessary. Also writes a
        configdrive to disk if the configdrive parameter is specified.

        :param image_info: Image information dictionary.
        :param configdrive: A string containing the location of the config
                            drive as a URL OR the contents (as gzip/base64)
                            of the configdrive. Optional, defaults to None.

        :raises: ImageDownloadError if the image download encounters an error.
        :raises: ImageChecksumError if the checksum of the local image does not
             match the checksum as reported by glance in image_info.
        :raises: ImageWriteError if writing the image fails.
        :raises: ConfigDriveTooLargeError if the configdrive contents are too
             large to store on the given device.
        """
        LOG.debug('Preparing image %s', image_info['id'])
        device = hardware.dispatch_to_managers('get_os_install_device')

        disk_format = image_info.get('disk_format')
        stream_raw_images = image_info.get('stream_raw_images', False)
        # don't write image again if already cached
        if self.cached_image_id != image_info['id']:

            if self.cached_image_id is not None:
                LOG.debug('Already had %s cached, overwriting',
                          self.cached_image_id)

            if (stream_raw_images and disk_format == 'raw' and
                image_info.get('image_type') != 'partition'):
                self._stream_raw_image_onto_device(image_info, device)
            else:
                self._cache_and_write_image(image_info, device)

        # the configdrive creation is taken care by ironic-lib's
        # work_on_disk().
        if image_info.get('image_type') != 'partition':
            if configdrive is not None:
                _write_configdrive_to_partition(configdrive, device)

        msg = 'image ({0}) written to device {1} '
        result_msg = _message_format(msg, image_info, device,
                                     self.partition_uuids)
        LOG.info(result_msg)
        return result_msg
    def prepare_image(self, image_info=None, configdrive=None):
        device = hardware.dispatch_to_managers("get_os_install_device")

        # don't write image again if already cached
        if self.cached_image_id != image_info["id"]:
            _download_image(image_info)
            _write_image(image_info, device)
            self.cached_image_id = image_info["id"]

        if configdrive is not None:
            _write_configdrive_to_partition(configdrive, device)

        return "image ({0}) written to device {1}".format(image_info["id"], device)
Example #18
0
    def cache_image(self, image_info=None, force=False):
        LOG.debug("Caching image %s", image_info["id"])
        device = hardware.dispatch_to_managers("get_os_install_device")

        result_msg = "image ({0}) already present on device {1}"

        if self.cached_image_id != image_info["id"] or force:
            LOG.debug("Already had %s cached, overwriting", self.cached_image_id)
            self._cache_and_write_image(image_info, device)
            result_msg = "image ({0}) cached to device {1}"

        msg = result_msg.format(image_info["id"], device)
        LOG.info(msg)
        return msg
Example #19
0
    def install_bootloader(self, root_uuid, efi_system_part_uuid=None):
        """Install the GRUB2 bootloader on the image.

        :param root_uuid: The UUID of the root partition.
        :param efi_system_part_uuid: The UUID of the efi system partition.
            To be used only for uefi boot mode.  For uefi boot mode, the
            boot loader will be installed here.
        :raises: CommandExecutionError if the installation of the
                 bootloader fails.
        :raises: DeviceNotFound if the root partition is not found.

        """
        device = hardware.dispatch_to_managers("get_os_install_device")
        iscsi.clean_up(device)
        _install_grub2(device, root_uuid=root_uuid, efi_system_part_uuid=efi_system_part_uuid)
Example #20
0
    def run(self):
        """Run the Ironic Python Agent."""
        # Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError
        # if there is an issue (uncaught, restart agent)
        self.started_at = _time()

        # Cached hw managers at runtime, not load time. See bug 1490008.
        hardware.load_managers()
        # Operator-settable delay before hardware actually comes up.
        # Helps with slow RAID drivers - see bug 1582797.
        if self.hardware_initialization_delay > 0:
            LOG.info('Waiting %d seconds before proceeding',
                     self.hardware_initialization_delay)
            time.sleep(self.hardware_initialization_delay)

        if not self.standalone:
            # Inspection should be started before call to lookup, otherwise
            # lookup will fail due to unknown MAC.
            uuid = inspector.inspect()

            content = self.api_client.lookup_node(
                hardware_info=hardware.dispatch_to_managers(
                    'list_hardware_info'),
                timeout=self.lookup_timeout,
                starting_interval=self.lookup_interval,
                node_uuid=uuid)

            self.node = content['node']
            hardware.cache_node(self.node)
            self.heartbeat_timeout = content['heartbeat_timeout']

        wsgi = simple_server.make_server(
            self.listen_address[0],
            self.listen_address[1],
            self.api,
            server_class=simple_server.WSGIServer)

        if not self.standalone:
            # Don't start heartbeating until the server is listening
            self.heartbeater.start()

        try:
            wsgi.serve_forever()
        except BaseException:
            LOG.exception('shutting down')

        if not self.standalone:
            self.heartbeater.stop()
Example #21
0
    def start_iscsi_target(self, iqn=None, wipe_disk_metadata=False,
                           portal_port=None):
        """Expose the disk as an ISCSI target.

        :param iqn: IQN for iSCSI target. If None, a new IQN is generated.
        :param wipe_disk_metadata: if the disk metadata should be wiped out
                                   before the disk is exposed.
        :param portal_port: customized port for iSCSI port, can be None.
        :returns: a dict that provides IQN of iSCSI target.
        """
        # If iqn is not given, generate one
        if iqn is None:
            iqn = 'iqn.2008-10.org.openstack:%s' % uuidutils.generate_uuid()

        device = hardware.dispatch_to_managers('get_os_install_device')

        if wipe_disk_metadata:
            disk_utils.destroy_disk_metadata(
                device,
                self.agent.get_node_uuid())

        LOG.debug("Starting ISCSI target with iqn %(iqn)s on device "
                  "%(device)s", {'iqn': iqn, 'device': device})

        try:
            rts_root = rtslib_fb.RTSRoot()
        except (EnvironmentError, rtslib_fb.RTSLibError) as exc:
            LOG.warning('Linux-IO is not available, falling back to TGT. '
                        'Error: %s.', exc)
            rts_root = None

        if portal_port is None:
            portal_port = DEFAULT_ISCSI_PORTAL_PORT

        if rts_root is None:
            _start_tgtd(iqn, portal_port, device)
        else:
            _start_lio(iqn, portal_port, device)
            LOG.debug('Linux-IO configuration: %s', rts_root.dump())

        LOG.info('Created iSCSI target with iqn %(iqn)s, portal port %(port)d,'
                 ' on device %(dev)s using %(method)s',
                 {'iqn': iqn, 'port': portal_port, 'dev': device,
                  'method': 'tgtd' if rts_root is None else 'linux-io'})

        return {"iscsi_target_iqn": iqn}
Example #22
0
    def _wait_for_interface(self):
        """Wait until at least one interface is up."""

        wait_till = time.time() + NETWORK_WAIT_TIMEOUT
        while time.time() < wait_till:
            interfaces = hardware.dispatch_to_managers(
                'list_network_interfaces')
            if not any(ifc.mac_address for ifc in interfaces):
                LOG.debug('Network is not up yet. '
                          'No valid interfaces found, retrying ...')
                time.sleep(NETWORK_WAIT_RETRY)
            else:
                break

        else:
            LOG.warning("No valid network interfaces found. "
                        "Node lookup will probably fail.")
    def prepare_image(self,
                      image_info=None,
                      configdrive=None):
        device = hardware.dispatch_to_managers('get_os_install_device')

        # don't write image again if already cached
        write_result = None
        if self.cached_image_id != image_info['id']:
            _download_image(image_info)
            write_result = _write_image(image_info, device)
            self.cached_image_id = image_info['id']

        if configdrive is not None:
            _write_configdrive_to_partition(configdrive, device)

        return 'image ({0}) written to device {1} root_uuid={2}'.format(
                   image_info['id'], device, write_result)
Example #24
0
    def cache_image(self, image_info=None, force=False):
        LOG.debug('Caching image %s', image_info['id'])
        device = hardware.dispatch_to_managers('get_os_install_device')

        result_msg = 'image ({0}) already present on device {1}'

        if self.cached_image_id != image_info['id'] or force:
            LOG.debug('Already had %s cached, overwriting',
                      self.cached_image_id)
            _download_image(image_info)
            _write_image(image_info, device)
            self.cached_image_id = image_info['id']
            result_msg = 'image ({0}) cached to device {1}'

        msg = result_msg.format(image_info['id'], device)
        LOG.info(msg)
        return msg
Example #25
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.
    """
    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')
    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 get_agent_network_interfaces(self):
        """Get a list of all network interfaces available.

        Excludes loopback connections.

        :returns: list of network interfaces available.
        :raises: LookupAgentInterfaceError if a valid interface could not
                 be found.
        """
        iface_list = [iface.serialize()['name'] for iface in
                hardware.dispatch_to_managers('list_network_interfaces')]
        iface_list = [name for name in iface_list if 'lo' not in name]

        if len(iface_list) == 0:
            raise errors.LookupAgentInterfaceError('Agent could not find a '
                                                   'valid network interface.')
        else:
            return iface_list
Example #27
0
    def execute_clean_step(self, step, node, ports, clean_version=None,
                           **kwargs):
        """Execute a clean step.

        :param step: A clean step with 'step', 'priority' and 'interface' keys
        :param node: A dict representation of a node
        :param ports: A dict representation of ports attached to node
        :param clean_version: The clean version as returned by
                              _get_current_clean_version() at the beginning
                              of cleaning/zapping
        :returns: a CommandResult object with command_result set to whatever
            the step returns.
        """
        # Ensure the agent is still the same version, or raise an exception
        LOG.debug('Executing clean step %s', step)
        hardware.cache_node(node)
        _check_clean_version(clean_version)

        if 'step' not in step:
            msg = 'Malformed clean_step, no "step" key: %s' % step
            LOG.error(msg)
            raise ValueError(msg)
        try:
            result = hardware.dispatch_to_managers(step['step'], node, ports)
        except Exception as e:
            msg = ('Error performing clean_step %(step)s: %(err)s' %
                   {'step': step['step'], 'err': e})
            LOG.exception(msg)
            raise errors.CleaningError(msg)

        LOG.info('Clean step completed: %(step)s, result: %(result)s',
                 {'step': step, 'result': result})

        # Cast result tuples (like output of utils.execute) as lists, or
        # WSME throws errors
        if isinstance(result, tuple):
            result = list(result)

        # Return the step that was executed so we can dispatch
        # to the appropriate Ironic interface
        return {
            'clean_result': result,
            'clean_step': step
        }
Example #28
0
def collect_default(data, failures):
    """The default inspection collector.

    This is the only collector that is called by default. It is designed to be
    both backward and future compatible:
        1. it collects exactly the same data as the old bash-based ramdisk
        2. it also posts the whole inventory which we'll eventually use.

    In both cases it tries to get BMC address, PXE boot device and the expected
    root device.

    :param data: mutable data that we'll send to inspector
    :param failures: AccumulatedFailures object
    """
    wait_for_dhcp()
    inventory = hardware.dispatch_to_managers('list_hardware_info')

    # In the future we will only need the current version of inventory,
    # a guessed root disk, PXE boot interface and IPMI address.
    # Everything else will be done by inspector itself and its plugins.
    data['inventory'] = inventory
    # Replicate the same logic as in deploy. We need to make sure that when
    # root device hints are not set, inspector will use the same root disk as
    # will be used for deploy.
    try:
        root_disk = utils.guess_root_disk(inventory['disks'][:])
    except errors.DeviceNotFound:
        root_disk = None
        LOG.warning('no suitable root device detected')
    else:
        data['root_disk'] = root_disk
        LOG.debug('default root device is %s', root_disk.name)
    # Both boot interface and IPMI address might not be present,
    # we don't count it as failure
    data['boot_interface'] = inventory['boot'].pxe_interface
    LOG.debug('boot devices was %s', data['boot_interface'])
    data['ipmi_address'] = inventory.get('bmc_address')
    LOG.debug('BMC IP address: %s', data['ipmi_address'])

    # These 2 calls are required for backward compatibility and should be
    # dropped after inspector is ready (probably in Mitaka cycle).
    discover_network_properties(inventory, data, failures)
    discover_scheduling_properties(inventory, data, root_disk)
Example #29
0
def wait_for_dhcp():
    """Wait until NIC's get their IP addresses via DHCP or timeout happens.

    Depending on the value of inspection_dhcp_all_interfaces configuration
    option will wait for either all or only PXE booting NIC.

    Note: only supports IPv4 addresses for now.

    :return: True if all NIC's got IP addresses, False if timeout happened.
             Also returns True if waiting is disabled via configuration.
    """
    if not CONF.inspection_dhcp_wait_timeout:
        return True

    pxe_mac = utils.get_agent_params().get('BOOTIF')
    if pxe_mac:
        pxe_mac = _normalize_mac(pxe_mac)
    elif not CONF.inspection_dhcp_all_interfaces:
        LOG.warning('No PXE boot interface known - not waiting for it '
                    'to get the IP address')
        return False

    threshold = time.time() + CONF.inspection_dhcp_wait_timeout
    while time.time() <= threshold:
        interfaces = hardware.dispatch_to_managers('list_network_interfaces')
        interfaces = [iface for iface in interfaces
                      if CONF.inspection_dhcp_all_interfaces
                      or iface.mac_address.lower() == pxe_mac]
        missing = [iface.name for iface in interfaces
                   if not iface.ipv4_address]
        if not missing:
            return True

        LOG.debug('Still waiting for interfaces %s to get IP addresses',
                  missing)
        time.sleep(_DHCP_RETRY_INTERVAL)

    LOG.warning('Not all network interfaces received IP addresses in '
                '%(timeout)d seconds: %(missing)s',
                {'timeout': CONF.inspection_dhcp_wait_timeout,
                 'missing': missing})
    return False
Example #30
0
    def run(self):
        """Run the Ironic Python Agent."""
        # Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError
        # if there is an issue (uncaught, restart agent)
        self.started_at = _time()

        # Cached hw managers at runtime, not load time. See bug 1490008.
        hardware.load_managers()

        if not self.standalone:
            # Inspection should be started before call to lookup, otherwise
            # lookup will fail due to unknown MAC.
            uuid = inspector.inspect()

            content = self.api_client.lookup_node(
                hardware_info=hardware.dispatch_to_managers(
                    'list_hardware_info'),
                timeout=self.lookup_timeout,
                starting_interval=self.lookup_interval,
                node_uuid=uuid)

            self.node = content['node']
            self.heartbeat_timeout = content['heartbeat_timeout']

        wsgi = simple_server.make_server(
            self.listen_address[0],
            self.listen_address[1],
            self.api,
            server_class=simple_server.WSGIServer)

        if not self.standalone:
            # Don't start heartbeating until the server is listening
            self.heartbeater.start()

        try:
            wsgi.serve_forever()
        except BaseException:
            LOG.exception('shutting down')

        if not self.standalone:
            self.heartbeater.stop()
Example #31
0
    def prepare_image(self, image_info=None, configdrive=None):
        """Asynchronously prepares specified image on local OS install device.

        In this case, 'prepare' means make local machine completely ready to
        reboot to the image specified by image_info.

        Downloads and writes an image to disk if necessary. Also writes a
        configdrive to disk if the configdrive parameter is specified.

        :param image_info: Image information dictionary.
        :param configdrive: A string containing the location of the config
                            drive as a URL OR the contents (as gzip/base64)
                            of the configdrive. Optional, defaults to None.

        :raises: ImageDownloadError if the image download encounters an error.
        :raises: ImageChecksumError if the checksum of the local image does not
             match the checksum as reported by glance in image_info.
        :raises: ImageWriteError if writing the image fails.
        :raises: InstanceDeployFailure if failed to create config drive.
             large to store on the given device.
        """
        LOG.debug('Preparing image %s', image_info['id'])
        device = hardware.dispatch_to_managers('get_os_install_device')

        disk_format = image_info.get('disk_format')
        stream_raw_images = image_info.get('stream_raw_images', False)
        # don't write image again if already cached
        if self.cached_image_id != image_info['id']:
            if self.cached_image_id is not None:
                LOG.debug('Already had %s cached, overwriting',
                          self.cached_image_id)

            if stream_raw_images and disk_format == 'raw':
                if image_info.get('image_type') == 'partition':
                    self.partition_uuids = _write_partition_image(
                        None, image_info, device)
                    stream_to = self.partition_uuids['partitions']['root']
                else:
                    stream_to = device

                self._stream_raw_image_onto_device(image_info, stream_to)
            else:
                self._cache_and_write_image(image_info, device)

        _validate_partitioning(device)

        # the configdrive creation is taken care by ironic-lib's
        # work_on_disk().
        if image_info.get('image_type') != 'partition':
            if configdrive is not None:
                # Will use dummy value of 'local' for 'node_uuid',
                # if it is not available. This is to handle scenario
                # wherein new IPA is being used with older version
                # of Ironic that did not pass 'node_uuid' in 'image_info'
                node_uuid = image_info.get('node_uuid', 'local')
                disk_utils.create_config_drive_partition(
                    node_uuid, device, configdrive)
        msg = 'image ({}) written to device {} '
        result_msg = _message_format(msg, image_info, device,
                                     self.partition_uuids)
        LOG.info(result_msg)
        return result_msg
Example #32
0
    def run(self):
        """Run the Ironic Python Agent."""
        # Get the UUID so we can heartbeat to Ironic. Raises LookupNodeError
        # if there is an issue (uncaught, restart agent)
        self.started_at = _time()

        # Cached hw managers at runtime, not load time. See bug 1490008.
        hardware.load_managers()
        # Operator-settable delay before hardware actually comes up.
        # Helps with slow RAID drivers - see bug 1582797.
        if self.hardware_initialization_delay > 0:
            LOG.info('Waiting %d seconds before proceeding',
                     self.hardware_initialization_delay)
            time.sleep(self.hardware_initialization_delay)

        if not self.standalone:
            # Inspection should be started before call to lookup, otherwise
            # lookup will fail due to unknown MAC.
            uuid = None
            if cfg.CONF.inspection_callback_url:
                try:
                    # Attempt inspection. This may fail, and previously
                    # an error would be logged.
                    uuid = inspector.inspect()
                except errors.InspectionError as e:
                    LOG.error('Failed to perform inspection: %(err)s',
                              {'error': e})
            if self.api_url:
                self._wait_for_interface()
                content = self.api_client.lookup_node(
                    hardware_info=hardware.dispatch_to_managers(
                        'list_hardware_info'),
                    timeout=self.lookup_timeout,
                    starting_interval=self.lookup_interval,
                    node_uuid=uuid)

                LOG.debug('Received lookup results: %s', content)
                self.node = content['node']
                LOG.info('Lookup succeeded, node UUID is %s',
                         self.node['uuid'])
                hardware.cache_node(self.node)
                self.heartbeat_timeout = content['config']['heartbeat_timeout']

                # Update config with values from Ironic
                config = content.get('config', {})
                if config.get('metrics'):
                    for opt, val in config.items():
                        setattr(cfg.CONF.metrics, opt, val)
                if config.get('metrics_statsd'):
                    for opt, val in config.items():
                        setattr(cfg.CONF.metrics_statsd, opt, val)
            elif cfg.CONF.inspection_callback_url:
                LOG.info('No ipa-api-url configured, Heartbeat and lookup '
                         'skipped for inspector.')
            else:
                LOG.error('Neither ipa-api-url nor inspection_callback_url'
                          'found, please check your pxe append parameters.')

        self.serve_ipa_api()

        if not self.standalone and self.api_url:
            self.heartbeater.stop()