def _write_interface_rename_config(self, root, ifname_option_values, overwrite): """Write systemd configuration .link file for interface renaming. :param root: path to the root of the target system :type root: str :param ifname_option_values: list of ifname boot option values :type ifname_option_values: list(str) :param overwrite: overwrite existing configuration file :type overwrite: bool """ if ifname_option_values: target_system_dir = util.join_paths( root, self.SYSTEMD_NETWORK_CONFIG_DIR) util.mkdirChain(target_system_dir) for ifname_value in ifname_option_values: iface, mac = ifname_value.split(":", 1) content = self.INTERFACE_RENAME_FILE_CONTENT_TEMPLATE.format( mac, iface) config_file = self.INTERFACE_RENAME_FILE_TEMPLATE.format(iface) config_file_path = util.join_paths(self.SYSTEMD_NETWORK_CONFIG_DIR, config_file) _write_config_file( root, config_file_path, content, "Cannot write {} configuration file for ifname={} option.". format(config_file_path, ifname_value), overwrite)
def _pick_mount_points(mount_points, download_size, install_size): """Pick mount points for the package installation. :return: a set of sufficient mount points """ suitable = { '/var/tmp', conf.target.system_root, join_paths(conf.target.system_root, 'home'), join_paths(conf.target.system_root, 'tmp'), join_paths(conf.target.system_root, 'var'), } sufficient = set() for mount_point, size in mount_points.items(): # Ignore mount points that are not suitable. if mount_point not in suitable: continue if size >= (download_size + install_size): log.debug("Considering %s (%s) for download and install.", mount_point, size) sufficient.add(mount_point) elif size >= download_size and not mount_point.startswith( conf.target.system_root): log.debug("Considering %s (%s) for download.", mount_point, size) sufficient.add(mount_point) return sufficient
def test_canceled_progress(self, proxy_getter, statvfs_mock): """Test the canceled installation progress.""" callback = Mock() with tempfile.TemporaryDirectory() as sysroot: os.mkdir(join_paths(sysroot, "/boot")) os.mkdir(join_paths(sysroot, "/home")) device_tree = STORAGE.get_proxy(DEVICE_TREE) device_tree.GetMountPoints.return_value = { "/": "dev1", "/boot": "dev2", "/home": "dev3", } statvfs_mock.return_value = \ Mock(f_frsize=1024, f_blocks=150, f_bfree=100) progress = InstallationProgress( sysroot=sysroot, callback=callback, installation_size=1024 * 100 ) with progress: time.sleep(2) expected = [ call("Synchronizing writes to disk"), call("Installing software 0%") ] assert callback.call_args_list == expected
def test_find_rw_mounts(self): """Test that only RO mounts of install sources are in Dracut.""" # everything what starts by string in this list will not be tested ignore_prefixes = ("Makefile", "README", "python-deps", "module-setup.sh") # define false positives false_positives = (re.compile(r'\bmount +(--move|--make-rprivate)'), re.compile(r'\bmount /dev/mapper/live-rw'), re.compile(r'\bmount +-t *overlay')) # filter not interesting content comment_regex = re.compile(r'#[^\n]*') log_regex = re.compile(r'\b(info|warn|error) +"[^"]*') # remove pyanaconda dir dracut_dir_path = join_paths(os.path.split(REPO_DIR)[0], "dracut") files = os.listdir(dracut_dir_path) # helper function to ignore prefixes specified above def _filter_ignore_files(result, file_name): for ignore_prefix in ignore_prefixes: if file_name.startswith(ignore_prefix): print("ignoring", file_name) return result if os.path.isdir(file_name): print("ignoring directory", file_name) return result result.append(file_name) return result files = reduce(_filter_ignore_files, files, []) mount_line_regex = re.compile(r'\bmount ') for f in files: path = join_paths(dracut_dir_path, f) with open(path, "rt") as fd: print("reading content of", path) content = fd.read() content = comment_regex.sub("", content) content = log_regex.sub("", content) for num, line in enumerate(content.split('\n'), start=1): # remove false positives from the line string for fp in false_positives: line = fp.sub("", line) # skip empty lines if not line: continue # skip every line which doesn't contain mount string if not mount_line_regex.search(line): continue # fail on every line which does not have 'mount -o ro' assert re.search(r'\bmount +-o *[a-z,]*ro', line), \ "Dracut mount in '{}' on line '{}' is not read-only!" \ .format(path, num)
def check_system_purpose_set_test(self): """Test the check_system_purpose_set() helper function.""" # system purpose set with tempfile.TemporaryDirectory() as sysroot: # create a dummy syspurpose file syspurpose_path = RHSM_SYSPURPOSE_FILE_PATH directory = os.path.split(syspurpose_path)[0] os.makedirs(util.join_paths(sysroot, directory)) os.mknod(util.join_paths(sysroot, syspurpose_path)) self.assertTrue(check_system_purpose_set(sysroot)) # system purpose not set with tempfile.TemporaryDirectory() as sysroot: self.assertFalse(check_system_purpose_set(sysroot))
def run(self): """Connect the target system to Red Hat Insights.""" # check if we should connect to Red Hat Insights if not self._connect_to_insights: log.debug( "insights-connect-task: Insights not requested, skipping") return elif not self._subscription_attached: log.debug( "insights-connect-task: " "Insights requested but target system is not subscribed, skipping" ) return insights_path = util.join_paths(self._sysroot, self.INSIGHTS_TOOL_PATH) # check the insights client utility is available if not os.path.isfile(insights_path): raise InsightsClientMissingError( "The insight-client tool ({}) is not available.".format( self.INSIGHTS_TOOL_PATH)) # tell the insights client to connect to insights log.debug("insights-connect-task: connecting to insights") rc = util.execWithRedirect(self.INSIGHTS_TOOL_PATH, ["--register"], root=self._sysroot) if rc: raise InsightsConnectError( "Connecting to Red Hat Insights failed.")
def run(self): """Run Repo files installation source setup.""" log.debug("Trying to detect repo files automatically") for repo_dir in self._repo_dirs: if len(glob.glob(join_paths(repo_dir, "*.repo"))) > 0: return raise SourceSetupError("repo files not found")
def pick_download_location(dnf_manager): """Pick the download location. :param dnf_manager: the DNF manager :return: a path to the download location """ download_size = dnf_manager.get_download_size() install_size = dnf_manager.get_installation_size() mount_points = get_free_space_map() # Try to find mount points that are sufficient for download and install. sufficient = _pick_mount_points(mount_points, download_size, install_size) # Or find mount points that are sufficient only for download. if not sufficient: sufficient = _pick_mount_points(mount_points, download_size, install_size=0) # Ignore the system root if there are other mount points. if len(sufficient) > 1: sufficient.discard(conf.target.system_root) if not sufficient: raise RuntimeError("Not enough disk space to download the " "packages; size {}.".format(download_size)) # Choose the biggest sufficient mount point. mount_point = _get_biggest_mount_point(mount_points, sufficient) log.info("Mount point %s picked as download location", mount_point) location = util.join_paths(mount_point, DNF_PACKAGE_CACHE_DIR_SUFFIX) return location
def _dump_journal(self): """Dump journal from the installation environment""" tempfile = "/tmp/journal.log" with open(tempfile, "w") as logfile: execWithRedirect("journalctl", ["-b"], stdout=logfile) self._copy_file_to_sysroot(tempfile, join_paths(ANACONDA_LOG_DIR, "journal.log"))
def _transfer_file(self, target_path, target_name): """Transfer a file with nice logs and raise an exception if it does not exist.""" log.debug("subscription: transferring %s", target_name) target_repo_file_path = util.join_paths(self._sysroot, target_path) if not self._copy_file(target_path, target_repo_file_path): msg = "{} ({}) is missing".format(target_name, self.RHSM_REPO_FILE_PATH) raise SubscriptionTokenTransferError(msg)
def transfer_system_purpose_test(self, path_exists): """Test system purpose transfer method of the subscription token transfer task.""" # simulate syspurpose file existing path_exists.return_value = True with tempfile.TemporaryDirectory() as sysroot: task = TransferSubscriptionTokensTask( sysroot=sysroot, transfer_subscription_tokens=True) task._copy_file = Mock() task._transfer_system_purpose() sysroot_path = util.join_paths( sysroot, TransferSubscriptionTokensTask.RHSM_SYSPURPOSE_FILE_PATH) task._copy_file.assert_called_once_with( TransferSubscriptionTokensTask.RHSM_SYSPURPOSE_FILE_PATH, sysroot_path) # simulate syspurpose file not existing # - this should result in just the copy operation not being attempted path_exists.return_value = False with tempfile.TemporaryDirectory() as sysroot: task = TransferSubscriptionTokensTask( sysroot=sysroot, transfer_subscription_tokens=True) task._copy_file = Mock() task._transfer_system_purpose() task._copy_file.assert_not_called()
def run(self): """Set up the installation source.""" log.debug("Setting up NFS source: %s", self._url) for mount_point in [self._device_mount, self._iso_mount]: if os.path.ismount(mount_point): raise SourceSetupError("The mount point {} is already in use.".format( mount_point )) options, host, path = parse_nfs_url(self._url) path, image = self._split_iso_from_path(path) try: self._mount_nfs(host, options, path) except PayloadSetupError: raise SourceSetupError("Could not mount NFS url '{}'".format(self._url)) iso_source_path = join_paths(self._device_mount, image) if image else self._device_mount iso_name = find_and_mount_iso_image(iso_source_path, self._iso_mount) if iso_name: log.debug("Using the ISO '%s' mounted at '%s'.", iso_name, self._iso_mount) return self._iso_mount if verify_valid_repository(self._device_mount): log.debug("Using the directory at '%s'.", self._device_mount) return self._device_mount # nothing found unmount the existing device unmount(self._device_mount) raise SourceSetupError( "Nothing useful found for NFS source at {}".format(self._url))
def test_transfer_entitlement_keys(self): """Test the entitlement keys transfer method of the subscription token transfer task.""" # simulate entitlement keys not existing with tempfile.TemporaryDirectory() as sysroot: task = TransferSubscriptionTokensTask( sysroot=sysroot, transfer_subscription_tokens=True) task._copy_pem_files = Mock() task._copy_pem_files.return_value = True task._transfer_entitlement_keys() sysroot_path = util.join_paths( sysroot, TransferSubscriptionTokensTask.RHSM_ENTITLEMENT_KEYS_PATH) task._copy_pem_files.assert_called_once_with( TransferSubscriptionTokensTask.RHSM_ENTITLEMENT_KEYS_PATH, sysroot_path) # simulate entitlement keys not existing # - this is a critical error and should raise an exception # (without proper certificates and keys the target system # would be unable to communicate with the Red Hat subscription # infrastructure) with tempfile.TemporaryDirectory() as sysroot: task = TransferSubscriptionTokensTask( sysroot=sysroot, transfer_subscription_tokens=True) task._copy_pem_files = Mock() task._copy_pem_files.return_value = False with self.assertRaises(SubscriptionTokenTransferError): task._transfer_entitlement_keys()
def _create_iso_path(full_path_on_mounted_device, iso_name): # The directory parameter is not pointing directly to ISO if not full_path_on_mounted_device.endswith(iso_name): return os.path.normpath( join_paths(full_path_on_mounted_device, iso_name)) # The directory parameter is pointing directly to ISO return full_path_on_mounted_device
def _get_mount_points(self): """Get mount points in the device tree. :return: a list of mount points """ device_tree = STORAGE.get_proxy(DEVICE_TREE) mount_points = device_tree.GetMountPoints() return [join_paths(self._sysroot, m) for m in mount_points]
def test_connect_error(self, exec_with_redirect): """Test that the expected exception is raised if the Insights client fails when called.""" with tempfile.TemporaryDirectory() as sysroot: # create a fake insights client tool file utility_path = ConnectToInsightsTask.INSIGHTS_TOOL_PATH directory = os.path.split(utility_path)[0] os.makedirs(util.join_paths(sysroot, directory)) os.mknod(util.join_paths(sysroot, utility_path)) task = ConnectToInsightsTask(sysroot=sysroot, subscription_attached=True, connect_to_insights=True) # make sure execWithRedirect has a non zero return code exec_with_redirect.return_value = 1 with self.assertRaises(InsightsConnectError): task.run() # check that call to the insights client has been done with the expected parameters exec_with_redirect.assert_called_once_with( '/usr/bin/insights-client', ['--register'], root=sysroot)
def _copy_tmp_logs(self): """Copy a number of log files from /tmp""" log_files_to_copy = [ "anaconda.log", "syslog", "X.log", "program.log", "packaging.log", "storage.log", "ifcfg.log", "lvm.log", "dnf.librepo.log", "hawkey.log", "dbus.log", ] for logfile in log_files_to_copy: self._copy_file_to_sysroot(join_paths("/tmp/", logfile), join_paths(ANACONDA_LOG_DIR, logfile))
def give_the_system_purpose(sysroot, role, sla, usage, addons): """Set system purpose for the installed system by calling the syspurpose tool. The tool is called in the installed system chroot, so this method can be only called once the system rootfs content is in place. :param str sysroot: system root path :param role: role of the system :type role: str or None :param sla: Service Level Agreement for the system :type sla: str or None :param usage: intended usage of the system :type usage: str or None :param list addons: any additional layered products or features """ if role or sla or usage or addons: # using join_paths() as both paths are absolute syspurpose_sysroot_path = util.join_paths(sysroot, SYSPURPOSE_UTILITY_PATH) if os.path.exists(syspurpose_sysroot_path): # The syspurpose utility can only set one value at a time, # so we might need to call it multiple times to set all the # requested values. # # Also as the values can contain white space we need to make sure the # values passed to arguments are all properly quoted. if role: args = ["set-role", role] if _call_syspurpose_tool(sysroot, args): return False if sla: args = ["set-sla", sla] if _call_syspurpose_tool(sysroot, args): return False if usage: args = ["set-usage", usage] if _call_syspurpose_tool(sysroot, args): return False if addons: args = ["add", "addons"] for addon in addons: args.append(addon) if _call_syspurpose_tool(sysroot, args): return False log.debug("subscription: system purpose has been set") return True else: log.error("the syspurpose tool is missing, cannot set system purpose") return False else: log.warning("not calling syspurpose as no fields have been provided") # doing nothing is still not a failure return True
def _copy_file_to_sysroot(self, src, dest): """Copy a file, if it exists, and set its access bits. :param str src: path to source file :param str dest: path to destination file within sysroot """ if os.path.exists(src): log.info("Copying file: %s -> %s", src, dest) full_dest_path = join_paths(self._sysroot, dest) shutil.copyfile(src, full_dest_path) os.chmod(full_dest_path, 0o0600)
def _copy_tree_to_sysroot(self, src, dest): """Copy a directory tree, if it exists, and set its access bits. :param str src: path to source directory :param str dest: path to destination directory within sysroot """ if os.path.exists(src): log.info("Copying directory tree: %s -> %s", src, dest) full_dest_path = join_paths(self._sysroot, dest) shutil.copytree(src, full_dest_path, dirs_exist_ok=True) os.chmod(full_dest_path, 0o0600)
def test_verify_no_checksum(self): """Test the verification of a checksum.""" with tempfile.TemporaryDirectory() as d: f_name = join_paths(d, "image") task = VerifyImageChecksum(image_path=f_name, checksum="") with self.assertLogs(level="DEBUG") as cm: task.run() msg = "No checksum to verify." assert msg in "\n".join(cm.output)
def from_directory(cls, directory_path): """Generate RepoConfigurationData url from directory path. This will basically add file:/// to the directory and set it to url with a proper type. :param str directory_path: directory which will be used to create url :return: RepoConfigurationData instance """ data = RepoConfigurationData() data.url = join_paths("file:///", directory_path) return data
def verify_valid_repository(path): """Check if the given path is a valid repository. :param str path: path to the repository :returns: True if repository is valid false otherwise :rtype: bool """ repomd_path = join_paths(path, "repodata/repomd.xml") if os.path.exists(repomd_path) and os.path.isfile(repomd_path): return True return False
def get_iso_path(self): """Get path to the ISO from the partition root. This could be an empty string if the source is pointing to installation tree instead of ISO. :return: path to the ISO or empty string if no ISO is involved :rtype: str """ if not self._iso_name: return "" return join_paths(self.directory, self._iso_name)
def _get_destination_size(self, mount_points): # FIXME: Use get_df_map/get_free_space_map. dest_size = 0 for mnt in mount_points: mnt_path = util.join_paths(conf.target.system_root, mnt) if not os.path.exists(mnt_path): continue mnt_stat = os.statvfs(mnt_path) dest_size += mnt_stat.f_frsize * (mnt_stat.f_blocks - mnt_stat.f_bfree) return dest_size
def test_join_paths(self): assert util.join_paths("/first/path/") == \ "/first/path/" assert util.join_paths("") == \ "" assert util.join_paths("/first/path/", "/second/path") == \ "/first/path/second/path" assert util.join_paths("/first/path/", "/second/path", "/third/path") == \ "/first/path/second/path/third/path" assert util.join_paths("/first/path/", "/second/path", "third/path") == \ "/first/path/second/path/third/path" assert util.join_paths("/first/path/", "second/path") == \ "/first/path/second/path" assert util.join_paths("first/path", "/second/path") == \ "first/path/second/path" assert util.join_paths("first/path", "second/path") == \ "first/path/second/path"
def check_system_purpose_set(sysroot="/"): """Check if System Purpose has been set for the system. By manipulating the sysroot parameter it is possible to check is System Purpose has been set for both the installation environment and the target system. For installation environment use "/", for the target system path to the installation root. :param str sysroot: system root where to check :return: True if System Purpose has been set, False otherwise """ syspurpose_path = util.join_paths(sysroot, RHSM_SYSPURPOSE_FILE_PATH) return os.path.exists(syspurpose_path)
def verify_valid_repository(path): """Check if the given path is a valid repository. :param str path: path to the repository :returns: True if repository is valid false otherwise :rtype: bool """ repomd_path = join_paths(path, "repodata/repomd.xml") if os.path.exists(repomd_path) and os.path.isfile(repomd_path): return True # FIXME: Remove this temporary solution when payload source migration will be finished. # # Source should not point to an installation tree but only to a repository, however, right now # we are in state that sources are only for base repository and just reflecting data from # user. With the unified feature the above check won't work because repository is a sub-folder # redirected by .treeinfo file. Add this check back to fix this issue. if os.path.exists(join_paths(path, ".treeinfo")): return True if os.path.exists(join_paths(path, "treeinfo")): return True return False
def join_paths_test(self): self.assertEqual(util.join_paths("/first/path/"), "/first/path/") self.assertEqual(util.join_paths(""), "") self.assertEqual(util.join_paths("/first/path/", "/second/path"), "/first/path/second/path") self.assertEqual(util.join_paths("/first/path/", "/second/path", "/third/path"), "/first/path/second/path/third/path") self.assertEqual(util.join_paths("/first/path/", "/second/path", "third/path"), "/first/path/second/path/third/path") self.assertEqual(util.join_paths("/first/path/", "second/path"), "/first/path/second/path") self.assertEqual(util.join_paths("first/path", "/second/path"), "first/path/second/path") self.assertEqual(util.join_paths("first/path", "second/path"), "first/path/second/path")
def _set_up_fips(self): """Set up FIPS in the target system.""" log.debug("Copying the crypto policy.") # Create /etc/crypto-policies. src = "/etc/crypto-policies/" dst = join_paths(self._sysroot, src) util.mkdirChain(dst) # Copy the config file. src = "/etc/crypto-policies/config" dst = join_paths(self._sysroot, src) shutil.copyfile(src, dst) # Log the file content on the target system. util.execWithRedirect("/bin/cat", [dst]) # Copy the back-ends. src = "/etc/crypto-policies/back-ends/" dst = join_paths(self._sysroot, src) shutil.copytree(src, dst, symlinks=True) # Log the directory content on the target system. util.execWithRedirect("/bin/ls", ["-l", dst])