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()
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
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
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}
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()
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)
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)
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
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)
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()
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}
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)
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
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
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 }
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)
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
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()
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
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()