def _find_and_copy_bootloaders(self, destination, log_missing=True): if not super()._find_and_copy_bootloaders(destination, False): # If a previous copy of the UEFI AMD64 Grub files can't be found # see the files are on the system from an Ubuntu package install. # The package uses a different filename than what MAAS uses so # when we copy make sure the right name is used. missing_files = [] if os.path.exists("/usr/lib/shim/shim.efi.signed"): atomic_symlink( "/usr/lib/shim/shim.efi.signed", os.path.join(destination, "bootx64.efi"), ) else: missing_files.append("bootx64.efi") if os.path.exists( "/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed"): atomic_symlink( "/usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed", os.path.join(destination, "grubx64.efi"), ) else: missing_files.append("grubx64.efi") if missing_files != [] and log_missing: err_msg = ( "Unable to find a copy of %s in the SimpleStream and the " "packages shim-signed, and grub-efi-amd64-signed are not " "installed. The %s bootloader type may not work." % (", ".join(missing_files), self.name)) try_send_rack_event(EVENT_TYPES.RACK_IMPORT_ERROR, err_msg) maaslog.error(err_msg) return False return True
def _find_and_copy_bootloaders(self, destination, log_missing=True): """Attempt to copy bootloaders from the previous snapshot :param destination: The path to link the bootloaders to :param log_missing: Log missing files, default True :return: True if all bootloaders have been found and copied, False otherwise. """ boot_sources_base = os.path.realpath(os.path.join(destination, "..")) previous_snapshot = os.path.join(boot_sources_base, "current") files_found = True for bootloader_file in self.bootloader_files: bootloader_src = os.path.join(previous_snapshot, bootloader_file) bootloader_src = os.path.realpath(bootloader_src) bootloader_dst = os.path.join(destination, bootloader_file) if os.path.exists(bootloader_src): # Copy files if their realpath is inside the previous snapshot # as once we're done the previous snapshot is deleted. Symlinks # to other areas of the filesystem are maintained. if boot_sources_base in bootloader_src: atomic_copy(bootloader_src, bootloader_dst) else: atomic_symlink(bootloader_src, bootloader_dst) else: files_found = False if log_missing: err_msg = ( "Unable to find a copy of %s in the SimpleStream or a " "previously downloaded copy. The %s bootloader type " "may not work." % (bootloader_file, self.name) ) try_send_rack_event(EVENT_TYPES.RACK_IMPORT_ERROR, err_msg) maaslog.error(err_msg) return files_found
def get_template(self, purpose, arch, subarch): """Gets the best avaliable template for the boot method. Templates are loaded each time here so that they can be changed on the fly without restarting the provisioning server. :param purpose: The boot purpose, e.g. "local". :param arch: Main machine architecture. :param subarch: Sub-architecture, or "generic" if there is none. :return: `tempita.Template` """ pxe_templates_dir = self.get_template_dir() for filename in gen_template_filenames(purpose, arch, subarch): template_name = os.path.join(pxe_templates_dir, filename) try: return tempita.Template.from_filename( template_name, encoding="UTF-8" ) except IOError as error: if error.errno != ENOENT: raise else: error = ( "No PXE template found in %r for:\n" " Purpose: %r, Arch: %r, Subarch: %r\n" "This can happen if you manually power up a node when its " "state is not one that allows it. Is the node in the " "'New' or 'Ready' states? It needs to be Enlisting, " "Commissioning or Allocated." % (pxe_templates_dir, purpose, arch, subarch) ) try_send_rack_event(EVENT_TYPES.RACK_IMPORT_ERROR, error) raise AssertionError(error)
def update_targets_conf(snapshot): """Runs tgt-admin to update the new targets from "maas.tgt".""" # Ensure that tgt is running before tgt-admin is used. service_monitor.ensureService("tgt").wait(30) # Update the tgt config. targets_conf = os.path.join(snapshot, 'maas.tgt') # The targets_conf may not exist in the event the BootSource is broken # and images havn't been imported yet. This fixes LP:1655721 if not os.path.exists(targets_conf): return try: call_and_check( sudo([ get_path('/usr/sbin/tgt-admin'), '--conf', targets_conf, '--update', 'ALL', ])) except ExternalProcessError as e: msg = "Unable to update TGT config: %s" % e try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg) maaslog.warning(msg)
def link_bootloaders(destination): """Link the all the required file from each bootloader method. :param destination: Directory where the loaders should be stored. """ for _, boot_method in BootMethodRegistry: try: boot_method.link_bootloader(destination) except BaseException: msg = "Unable to link the %s bootloader.", boot_method.name try_send_rack_event(EVENT_TYPES.RACK_IMPORT_ERROR, msg) maaslog.error(msg)
def _link_simplestream_bootloaders(self, stream_path, destination): """Link the bootloaders downloaded from the SimpleStream into the destination(tftp root). :param stream_path: The path to the bootloaders in the SimpleStream :param destination: The path to link the bootloaders to """ for bootloader_file in self.bootloader_files: bootloader_src = os.path.join(stream_path, bootloader_file) bootloader_dst = os.path.join(destination, bootloader_file) if os.path.exists(bootloader_src): atomic_symlink(bootloader_src, bootloader_dst) else: err_msg = ( "SimpleStream is missing required bootloader file '%s' " "from bootloader %s." % (bootloader_file, self.name)) try_send_rack_event(EVENT_TYPES.RACK_IMPORT_ERROR, err_msg) maaslog.error(err_msg)
def import_images(sources): """Import images. Callable from the command line. :param config: An iterable of dicts representing the sources from which boot images will be downloaded. """ if len(sources) == 0: msg = "Can't import: region did not provide a source." try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg) maaslog.warning(msg) return False msg = "Starting rack boot image import" maaslog.info(msg) try_send_rack_event(EVENT_TYPES.RACK_IMPORT_INFO, msg) with ClusterConfiguration.open() as config: storage = FilePath(config.tftp_root).parent().path with tempdir("keyrings") as keyrings_path: # XXX: Band-aid to ensure that the keyring_data is bytes. Future task: # try to figure out why this sometimes happens. for source in sources: if "keyring_data" in source and not isinstance( source["keyring_data"], bytes): source["keyring_data"] = source["keyring_data"].encode("utf-8") # We download the keyrings now because we need them for both # download_all_image_descriptions() and # download_all_boot_resources() later. sources = write_all_keyrings(keyrings_path, sources) # The region produces a SimpleStream which is similar, but not # identical to the actual SimpleStream. These differences cause # validation to fail. So grab everything from the region and trust it # did proper filtering before the rack. image_descriptions = download_all_image_descriptions( sources, validate_products=False) if image_descriptions.is_empty(): msg = ("Finished importing boot images, the region does not have " "any boot images available.") try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg) maaslog.warning(msg) return False meta_file_content = image_descriptions.dump_json() if meta_contains(storage, meta_file_content): maaslog.info("Finished importing boot images, the region does not " "have any new images.") try_send_rack_event(EVENT_TYPES.RACK_IMPORT_INFO, msg) maaslog.info(msg) return False product_mapping = map_products(image_descriptions) try: snapshot_path = download_all_boot_resources( sources, storage, product_mapping) except Exception as e: try_send_rack_event( EVENT_TYPES.RACK_IMPORT_ERROR, "Unable to import boot images: %s" % e, ) maaslog.error( "Unable to import boot images; cleaning up failed snapshot " "and cache.") # Cleanup snapshots and cache since download failed. cleanup_snapshots_and_cache(storage) raise maaslog.info("Writing boot image metadata.") write_snapshot_metadata(snapshot_path, meta_file_content) maaslog.info("Linking boot images snapshot %s" % snapshot_path) link_bootloaders(snapshot_path) # If we got here, all went well. This is now truly the "current" snapshot. update_current_symlink(storage, snapshot_path) # Now cleanup the old snapshots and cache. maaslog.info("Cleaning up old snapshots and cache.") cleanup_snapshots_and_cache(storage) # Import is now finished. msg = "Finished importing boot images." maaslog.info(msg) try_send_rack_event(EVENT_TYPES.RACK_IMPORT_INFO, msg) return True
def _find_and_copy_bootloaders(self, destination, log_missing=True, bootloader_files=None): if bootloader_files is None: bootloader_files = self.bootloader_files boot_sources_base = os.path.realpath(os.path.join(destination, '..')) default_search_path = os.path.join(boot_sources_base, 'current') syslinux_search_path = os.path.join(default_search_path, 'syslinux') # In addition to the default search path search the previous # syslinux subdir as well. Previously MAAS didn't copy all of the # files required for PXE into the root tftp path. Also search the # paths the syslinux-common and pxelinux Ubuntu packages installs files # to on Xenial. search_paths = [ default_search_path, syslinux_search_path, '/usr/lib/PXELINUX', '/usr/lib/syslinux/modules/bios', ] files_found = [] for search_path in search_paths: for bootloader_file in bootloader_files: bootloader_src = os.path.join(search_path, bootloader_file) bootloader_src = os.path.realpath(bootloader_src) bootloader_dst = os.path.join(destination, bootloader_file) if (os.path.exists(bootloader_src) and not os.path.exists(bootloader_dst)): # If the file was found in a previous snapshot copy it as # once we're done the previous snapshot will be deleted. If # the file was found elsewhere on the filesystem create a # symlink so we stay current with that source. if boot_sources_base in bootloader_src: atomic_copy(bootloader_src, bootloader_dst) else: atomic_symlink(bootloader_src, bootloader_dst) files_found.append(bootloader_file) missing_files = [ bootloader_file for bootloader_file in bootloader_files if bootloader_file not in files_found ] if missing_files != []: files_are_missing = True if log_missing: err_msg = ( "Unable to find a copy of %s in the SimpleStream or in " "the system search paths %s. The %s bootloader type may " "not work." % (', '.join(missing_files), ', '.join(search_paths), self.name)) try_send_rack_event(EVENT_TYPES.RACK_IMPORT_ERROR, err_msg) maaslog.error(err_msg) else: files_are_missing = False syslinux_search_paths = [ syslinux_search_path, '/usr/lib/syslinux/modules/bios', ] for search_path in syslinux_search_paths: if os.path.exists(search_path): syslinux_src = os.path.realpath(search_path) syslinux_dst = os.path.join(destination, 'syslinux') if destination in os.path.realpath(syslinux_src): shutil.copy(bootloader_src, bootloader_dst) else: atomic_symlink(syslinux_src, syslinux_dst) break return files_are_missing