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
示例#2
0
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