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 test_link_bootloader_copies_previously_downloaded_files(self): method = PXEBootMethod() with tempdir() as tmp: new_dir = os.path.join(tmp, "new") current_dir = os.path.join(tmp, "current") os.makedirs(new_dir) os.makedirs(current_dir) factory.make_file(current_dir, method.bootloader_files[0]) for bootloader_file in method.bootloader_files[1:]: factory.make_file(current_dir, bootloader_file) real_syslinux_dir = os.path.join(tmp, "syslinux") os.makedirs(real_syslinux_dir) atomic_symlink( real_syslinux_dir, os.path.join(current_dir, "syslinux") ) method.link_bootloader(new_dir) for bootloader_file in method.bootloader_files: bootloader_file_path = os.path.join(new_dir, bootloader_file) self.assertTrue(os.path.isfile(bootloader_file_path)) syslinux_link = os.path.join(new_dir, "syslinux") self.assertTrue(os.path.islink(syslinux_link)) self.assertEqual( real_syslinux_dir, os.path.realpath(syslinux_link) )
def test_atomic_symlink_uses_relative_path(self): filename = self.make_file(contents=factory.make_string()) link_name = factory.make_name("link") target = os.path.join(os.path.dirname(filename), link_name) atomic_symlink(filename, target) self.assertEqual(os.path.basename(filename), os.readlink(target)) self.assertTrue(os.path.samefile(filename, target))
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 test_atomic_symlink_uses_relative_path_for_directory(self): target_path = self.make_dir() # The target is a directory. link_path = os.path.join(self.make_dir(), factory.make_name("sub")) atomic_symlink(target_path, link_path) self.assertThat( os.readlink(link_path), Equals(os.path.relpath(target_path, os.path.dirname(link_path)))) self.assertTrue(os.path.samefile(target_path, link_path))
def test_atomic_symlink_creates_symlink(self): filename = self.make_file(contents=factory.make_string()) target_dir = self.make_dir() link_name = factory.make_name("link") target = os.path.join(target_dir, link_name) atomic_symlink(filename, target) self.assertTrue(os.path.islink(target), "atomic_symlink didn't create a symlink") self.assertThat(target, SamePath(filename))
def test_atomic_symlink_does_not_leak_temp_file_if_failure(self): # In the face of failure, no temp file is leaked. self.patch(os, "rename", Mock(side_effect=OSError())) filename = self.make_file() target_dir = self.make_dir() link_name = factory.make_name("link") target = os.path.join(target_dir, link_name) with ExpectedException(OSError): atomic_symlink(filename, target) self.assertEqual([], os.listdir(target_dir))
def _link_simplestream_bootloaders(self, stream_path, destination): super()._link_simplestream_bootloaders(stream_path, destination) # MAAS only requires the bootloader_files listed above to boot. # However some users may want to use extra PXE files in custom # configs or for debug. PXELinux checks / and then /syslinux so # create a symlink to the stream_path which contains all extra PXE # files. This also ensures if upstream ever starts requiring more # modules PXE will continue to work. syslinux_dst = os.path.join(destination, 'syslinux') atomic_symlink(stream_path, syslinux_dst)
def test_atomic_symlink_overwrites_dest_file(self): filename = self.make_file(contents=factory.make_string()) target_dir = self.make_dir() link_name = factory.make_name("link") # Create a file that will be overwritten. factory.make_file(location=target_dir, name=link_name) target = os.path.join(target_dir, link_name) atomic_symlink(filename, target) self.assertTrue(os.path.islink(target), "atomic_symlink didn't create a symlink") self.assertThat(target, SamePath(filename))
def test_link_bootloader_links_bootloaders_found_elsewhere_on_fs(self): method = FakeBootMethod() with tempdir() as tmp: bootresources_dir = os.path.join(tmp, 'boot-resources') new_dir = os.path.join(bootresources_dir, 'new') current_dir = os.path.join(bootresources_dir, 'current') os.makedirs(new_dir) os.makedirs(current_dir) for bootloader_file in method.bootloader_files: factory.make_file(tmp, bootloader_file) atomic_symlink(os.path.join(tmp, bootloader_file), os.path.join(current_dir, bootloader_file)) method.link_bootloader(new_dir) for bootloader_file in method.bootloader_files: bootloader_file_path = os.path.join(new_dir, bootloader_file) self.assertTrue(os.path.islink(bootloader_file_path))
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 link_bootloader(self, destination: str): """Installs the required files for this boot method into the destination. :param destination: path to install bootloader """ super(PXEBootMethod, self).link_bootloader(destination) # When lpxelinux.0 doesn't exist we run find and copy to add that file # in the correct place. lpxelinux = os.path.join(destination, 'lpxelinux.0') if not os.path.exists(lpxelinux): self._find_and_copy_bootloaders(destination, bootloader_files=['lpxelinux.0']) # Symlink pxelinux.0 to lpxelinux.0 for backwards compatibility of # external DHCP servers that point next-server to pxelinux.0. pxelinux = os.path.join(destination, 'pxelinux.0') atomic_symlink(lpxelinux, pxelinux)
def update_current_symlink(storage, latest_snapshot): """Symlink `latest_snapshot` as the "current" snapshot.""" atomic_symlink(latest_snapshot, os.path.join(storage, "current"))
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