def _single_repo_validation(self, release, repo_name, dry_run: bool = False): """ Separate private method for basic single repo validations. Parameters ---------- release: Single Upgrade ISO release version repo_name: str Repository name dry_run: bool If this parameter is set to `True` this method doesn't raise Exceptions, just collects them Returns ------- """ logger.info(f"Validate single SW upgrade repo '{repo_name}' " f"of release {release}") # general check from pkg manager point of view try: self._check_repo_is_valid(REPO_CANDIDATE_NAME, f"sw_upgrade_{repo_name}") except SaltCmdResultError as exc: exc = SWUpdateRepoSourceError(repo_name, f"malformed repo: '{exc}'") if dry_run: self._exceptions.append(exc) else: raise exc # there is no the same release repo is already active if self._is_repo_enabled(f'sw_upgrade_{repo_name}_{release}'): err_msg = ("SW update repository for the release " f"'{release}' has been already enabled") logger.warning(err_msg) exc = SWUpdateRepoSourceError( str(repo_name), (f"SW upgrade repository '{repo_name}' for the release " f"'{release}' has been already enabled")) if dry_run: self._exceptions.append(exc) else: raise exc
def test_SWUpdateRepoSourceError_str(): source = 'some-src' reason = 'some-reason' obj = SWUpdateRepoSourceError(source, reason) assert str(obj) == 'repo source {} is not acceptable, reason: {!r}'.format( source, reason)
def _pre_repo_validation(self, params: inputs.SWUpgradeRepo, dry_run: bool = False): """ SW upgrade repository pre-validation. Parameters ---------- params: inputs.SWUpgradeRepo Input repository parameters dry_run: bool If this parameter is set to `True` this method doesn't raise Exceptions, just collects them Raises ------ SWUpdateRepoSourceError: Raise this exception if candidate repository validation fails """ if not params.is_remote(): if params.sig_file is None: logger.info("Signature file is not specified") suffix = params.source.suffix + ".sig" params.sig_file = Path(str(params.source)).with_suffix(suffix) if not params.sig_file.exists(): exc = SWUpdateRepoSourceError( str(params.source), f"Signature file '{params.sig_file}' is not found") if dry_run: self._exceptions.append(exc) else: raise exc self._check_iso_authenticity(params, dry_run) if params.hash: logger.warning('Only integrity check is available.') logger.info("`hash` parameter is setup. Start checksum " "validation for the whole ISO file") hash_info = self._get_hash_params(params) self._check_iso_integrity(params, hash_info, dry_run) # TODO IMPROVE VALIDATION EOS-14350 # - there is no other candidate that is being verified: # if found makes sense to raise an error in case the other # logic is still running, if not - forcibly remove the previous # candidate # - after first mount 'sw_update_candidate' listed in disabled repos # NOTE: yum repoinfo supports the wildcards in the name of a searching # repository if not dry_run and self._does_repo_exist( f'sw_upgrade_*_{params.release}'): logger.warning( 'other repo candidate was found, proceeding with force removal' )
def _base_repo_validation(self, candidate_repo: inputs.SWUpgradeRepo, base_dir: Path, dry_run: bool = False): """ Base SW upgrade repository validation. Parameters ---------- candidate_repo: inputs.SWUpgradeRepo Candidate SW upgrade repository parameters base_dir: Path Path to base SW upgrade directory dry_run: bool If this parameter is set to `True` this method doesn't raise Exceptions, just collects them Returns ------- dict: return SW upgrade candidate metadata Raises ------- SWUpdateRepoSourceError: Raise this exception if candidate repository validation fails """ if not candidate_repo.is_remote(): iso_mount_dir = base_dir / REPO_CANDIDATE_NAME if not dry_run: sw_upgrade_bundle_validator = FileSchemeValidator( SW_UPGRADE_BUNDLE_SCHEME[self._source_version]) try: sw_upgrade_bundle_validator.validate(iso_mount_dir) except ValidationError as e: logger.debug( f"Catalog structure validation error occurred: {e}") exc = SWUpdateRepoSourceError( str(candidate_repo.source), f"Catalog structure validation error occurred:{e}") if dry_run: self._exceptions.append(exc) else: raise exc from e else: logger.info("Catalog structure validation succeeded")
def _validate_python_index(self, index_path: Path, dry_run: bool = False): """ Perform the dynamic validation for SW upgrade Python index by the given index path `index_path` Parameters ---------- index_path: Path Path to the SW upgrade Python index Returns ------- None Raises ------ SWUpdateRepoSourceError If Python index validation fails """ logger.debug("Start Python index validation") if not index_path.exists() or not any( p for p in index_path.iterdir() if p.is_dir()): return pkgs = (p for p in index_path.iterdir() if p.is_dir()) try: test_package_name = next(pkgs).name except StopIteration: logger.debug("Python index is empty, skip the validation") return with tempfile.TemporaryDirectory() as tmp_dir: cmd = (f"pip3 download {test_package_name} --dest={tmp_dir}/ " f"--index-url file://{index_path.resolve()}") try: cmd_run(cmd, targets=local_minion_id(), fun_kwargs=dict(python_shell=True)) except Exception as e: exc = SWUpdateRepoSourceError( index_path, "Python index validation failed: " f"{e}") if dry_run: self._exceptions.append(exc) else: raise exc logger.debug("Python index validation succeeded")
def _check_iso_authenticity(self, params: inputs.SWUpgradeRepo, dry_run: bool = False): """ SW upgrade repository pre-validation. Parameters ---------- params: inputs.SWUpgradeRepo: inputs parameters for a SW upgrade ISO dry_run: bool If this parameter is set to `True` this method doesn't raise Exceptions, just collects them Returns ------- """ logger.info("File with ISO signature is specified. Start GPG " "signature validation for the ISO") # NOTE: We use CheckISOAuthenticity class instead of # AuthenticityValidator to import GPG public key if # `import_pub_key` is specified auth_validator = CheckISOAuthenticity() res = auth_validator.run(iso_path=params.source, sig_file=params.sig_file, gpg_pub_key=params.gpg_pub_key, import_pub_key=params.import_pub_key) if res[ISOValidationFields.STATUS.value] == CheckVerdict.FAIL.value: logger.warning(f"ISO signature validation is failed: " f"'{res[ISOValidationFields.MSG.value]}'") exc = SWUpdateRepoSourceError( str(params.source), "ISO signature validation error occurred: " f"'{res[ISOValidationFields.MSG.value]}'") if dry_run: self._exceptions.append(exc) else: raise exc else: logger.info('ISO signature validation succeeded')
def _check_iso_integrity(self, params: inputs.SWUpgradeRepo, hash_info: HashInfo, dry_run: bool = False): """ Parameters ---------- params: inputs.SWUpgradeRepo hash_info: HashInfo context with all user defined hash parameters dry_run: bool If this parameter is set to `True` this method doesn't raise Exceptions, just collects them Returns ------- """ upgrade_bundle_hash_validator = HashSumValidator( hash_sum=hash_info.hash_sum, hash_type=hash_info.hash_type) try: upgrade_bundle_hash_validator.validate(params.source) except ValidationError as e: logger.warning(f"Check sum validation error occurred: {e}") exc = SWUpdateRepoSourceError( str(params.source), f"Check sum validation error occurred: '{e}'") if dry_run: self._exceptions.append(exc) else: raise exc from e else: logger.info('Check sum validation succeeded')
def dynamic_validation( self, params, targets: str, dry_run: bool = False ) -> Union[CortxISOInfo, None]: # noqa: C901, E501 """ Validate single SW upgrade ISO structure. Parameters ---------- params: Input repository parameters targets: str Salt target to perform base mount and validation logic dry_run: bool If this parameter is set to `True` this method doesn't raise Exceptions, just collects them. In dry mode command doesn't enable any candidate repos and doesn't create yum repo files. Returns ------- CortxISOInfo: return SW upgrade candidate metadata and version of packages or None repo source is special provisioner value Raises ------ SWUpdateRepoSourceError: Raise this exception if candidate repository validation fails """ repo = params base_dir = None if repo.is_special(): logger.info("Skipping update repo validation for special value: " f"{repo.source}") return checker = Check() try: check_res = checker.run(Checks.ACTIVE_UPGRADE_ISO.value) except Exception as e: logger.warning("During the detection of the active SW upgrade ISO " f"error happened: {str(e)}") else: if check_res.is_failed: exc = SWUpgradeError( "An active SW upgrade ISO is detected. Please, finish " "the current upgrade before start the new one") if dry_run: self._exceptions.append(exc) else: raise exc self._source_version = repo.source_version logger.info("SW upgrade ISO struction version is " f"'{self._source_version.value}'") logger.info(f"Validating upgrade repo: release '{REPO_CANDIDATE_NAME}'" f", source {repo.source}") candidate_repo = inputs.SWUpgradeRepo( repo.source, REPO_CANDIDATE_NAME, source_version=self._source_version.value) self._pre_repo_validation(params, dry_run) try: logger.debug("Configuring upgrade candidate repo for validation") if not candidate_repo.is_remote(): self._prepare_single_iso_for_apply(candidate_repo) base_dir = self._get_mount_dir() candidate_repo.target_build = base_dir candidate_repo.enabled = False logger.debug("Configure pillars and apply states for " "candidate SW upgrade ISO") self._apply(candidate_repo, targets=targets, local=False) self._base_repo_validation(candidate_repo, base_dir, dry_run) cortx_release = CortxRelease(REPO_CANDIDATE_NAME) metadata = cortx_release.metadata repo.metadata = metadata logger.debug(f"Resolved metadata {metadata}") try: # TODO here cortx_release.version returns 'candidate', # it doesn't match metadata version info, # might be a case for improvement release = cortx_release.release_info.version except KeyError: exc = SWUpdateRepoSourceError( str(repo.source), f"No release data found in '{RELEASE_INFO_FILE}'") if dry_run: self._exceptions.append(exc) release = None else: raise exc self._post_repo_validation(candidate_repo, base_dir, dry_run) repo.release = release if (version.parse(release) < version.parse( GetRelease.cortx_version())): exc = SWUpdateRepoSourceError( str(repo.source), f"ISO version '{release}' should be greater than current " f"one {GetRelease.cortx_version()}") if dry_run: self._exceptions.append(exc) else: raise exc packages = self.get_packages_version(REPO_CANDIDATE_NAME, dry_run) cortx_info = CortxISOInfo(packages=packages, metadata=metadata, exceptions=self._exceptions) # Packages compatibility validation compatibility_validator = CompatibilityValidator() try: compatibility_validator.validate(cortx_info) except ValidationError as e: logger.debug(f"Packages compatibility check is failed: {e}") exc = SWUpdateRepoSourceError( str(candidate_repo.source), f"Packages compatibility check is failed {e}") if dry_run: cortx_info.exceptions.append(exc) else: raise exc from e else: logger.info("Packages compatibility check succeeded") finally: # remove the repo candidate_repo.source = values.UNDEFINED logger.info("Post-validation cleanup") self._apply(candidate_repo, targets, local=False) return cortx_info