diff options
Diffstat (limited to 'roles')
4 files changed, 334 insertions, 92 deletions
| diff --git a/roles/openshift_health_checker/library/aos_version.py b/roles/openshift_health_checker/library/aos_version.py index 4460ec324..4c205e48c 100755 --- a/roles/openshift_health_checker/library/aos_version.py +++ b/roles/openshift_health_checker/library/aos_version.py @@ -16,8 +16,6 @@ of release availability already. Without duplicating all that, we would  like the user to have a helpful error message if we detect things will  not work out right. Note that if openshift_release is not specified in  the inventory, the version comparison checks just pass. - -TODO: fail gracefully on non-yum systems (dnf in Fedora)  '''  from ansible.module_utils.basic import AnsibleModule @@ -26,7 +24,7 @@ IMPORT_EXCEPTION = None  try:      import yum  # pylint: disable=import-error  except ImportError as err: -    IMPORT_EXCEPTION = err  # in tox test env, yum import fails +    IMPORT_EXCEPTION = err  class AosVersionException(Exception): @@ -37,12 +35,10 @@ class AosVersionException(Exception):  def main(): -    '''Entrypoint for this Ansible module''' +    """Entrypoint for this Ansible module"""      module = AnsibleModule(          argument_spec=dict( -            requested_openshift_release=dict(type="str", default=''), -            openshift_deployment_type=dict(required=True), -            rpm_prefix=dict(required=True),  # atomic-openshift, origin, ...? +            package_list=dict(type="list", required=True),          ),          supports_check_mode=True      ) @@ -51,32 +47,37 @@ def main():          module.fail_json(msg="aos_version module could not import yum: %s" % IMPORT_EXCEPTION)      # determine the packages we will look for -    rpm_prefix = module.params['rpm_prefix'] -    if not rpm_prefix: -        module.fail_json(msg="rpm_prefix must not be empty") -    expected_pkgs = set([ -        rpm_prefix, -        rpm_prefix + '-master', -        rpm_prefix + '-node', -    ]) - -    # determine what level of precision the user specified for the openshift version. -    # should look like a version string with possibly many segments e.g. "3.4.1": -    requested_openshift_release = module.params['requested_openshift_release'] +    package_list = module.params['package_list'] +    if not package_list: +        module.fail_json(msg="package_list must not be empty") + +    # generate set with only the names of expected packages +    expected_pkg_names = [p["name"] for p in package_list] + +    # gather packages that require a multi_minor_release check +    multi_minor_pkgs = [p for p in package_list if p["check_multi"]] + +    # generate list of packages with a specified (non-empty) version +    # should look like a version string with possibly many segments e.g. "3.4.1" +    versioned_pkgs = [p for p in package_list if p["version"]]      # get the list of packages available and complain if anything is wrong      try: -        pkgs = _retrieve_available_packages(expected_pkgs) -        if requested_openshift_release: -            _check_precise_version_found(pkgs, expected_pkgs, requested_openshift_release) -            _check_higher_version_found(pkgs, expected_pkgs, requested_openshift_release) -        if module.params['openshift_deployment_type'] in ['openshift-enterprise']: -            _check_multi_minor_release(pkgs, expected_pkgs) +        pkgs = _retrieve_available_packages(expected_pkg_names) +        if versioned_pkgs: +            _check_precise_version_found(pkgs, _to_dict(versioned_pkgs)) +            _check_higher_version_found(pkgs, _to_dict(versioned_pkgs)) +        if multi_minor_pkgs: +            _check_multi_minor_release(pkgs, _to_dict(multi_minor_pkgs))      except AosVersionException as excinfo:          module.fail_json(msg=str(excinfo))      module.exit_json(changed=False) +def _to_dict(pkg_list): +    return {pkg["name"]: pkg for pkg in pkg_list} + +  def _retrieve_available_packages(expected_pkgs):      # search for package versions available for openshift pkgs      yb = yum.YumBase()  # pylint: disable=invalid-name @@ -104,56 +105,60 @@ def _retrieve_available_packages(expected_pkgs):  class PreciseVersionNotFound(AosVersionException): -    '''Exception for reporting packages not available at given release''' -    def __init__(self, requested_release, not_found): -        msg = ['Not all of the required packages are available at requested version %s:' % requested_release] -        msg += ['  ' + name for name in not_found] +    """Exception for reporting packages not available at given version""" +    def __init__(self, not_found): +        msg = ['Not all of the required packages are available at their requested version'] +        msg += ['{}:{} '.format(pkg["name"], pkg["version"]) for pkg in not_found]          msg += ['Please check your subscriptions and enabled repositories.']          AosVersionException.__init__(self, '\n'.join(msg), not_found) -def _check_precise_version_found(pkgs, expected_pkgs, requested_openshift_release): +def _check_precise_version_found(pkgs, expected_pkgs_dict):      # see if any packages couldn't be found at requested release version      # we would like to verify that the latest available pkgs have however specific a version is given.      # so e.g. if there is a package version 3.4.1.5 the check passes; if only 3.4.0, it fails. -    pkgs_precise_version_found = {} +    pkgs_precise_version_found = set()      for pkg in pkgs: -        if pkg.name not in expected_pkgs: +        if pkg.name not in expected_pkgs_dict:              continue          # does the version match, to the precision requested?          # and, is it strictly greater, at the precision requested? -        match_version = '.'.join(pkg.version.split('.')[:requested_openshift_release.count('.') + 1]) -        if match_version == requested_openshift_release: -            pkgs_precise_version_found[pkg.name] = True +        expected_pkg_version = expected_pkgs_dict[pkg.name]["version"] +        match_version = '.'.join(pkg.version.split('.')[:expected_pkg_version.count('.') + 1]) +        if match_version == expected_pkg_version: +            pkgs_precise_version_found.add(pkg.name)      not_found = [] -    for name in expected_pkgs: +    for name, pkg in expected_pkgs_dict.items():          if name not in pkgs_precise_version_found: -            not_found.append(name) +            not_found.append(pkg)      if not_found: -        raise PreciseVersionNotFound(requested_openshift_release, not_found) +        raise PreciseVersionNotFound(not_found)  class FoundHigherVersion(AosVersionException): -    '''Exception for reporting that a higher version than requested is available''' -    def __init__(self, requested_release, higher_found): +    """Exception for reporting that a higher version than requested is available""" +    def __init__(self, higher_found):          msg = ['Some required package(s) are available at a version', -               'that is higher than requested %s:' % requested_release] +               'that is higher than requested']          msg += ['  ' + name for name in higher_found]          msg += ['This will prevent installing the version you requested.']          msg += ['Please check your enabled repositories or adjust openshift_release.']          AosVersionException.__init__(self, '\n'.join(msg), higher_found) -def _check_higher_version_found(pkgs, expected_pkgs, requested_openshift_release): -    req_release_arr = [int(segment) for segment in requested_openshift_release.split(".")] +def _check_higher_version_found(pkgs, expected_pkgs_dict): +    expected_pkg_names = list(expected_pkgs_dict) +      # see if any packages are available in a version higher than requested      higher_version_for_pkg = {}      for pkg in pkgs: -        if pkg.name not in expected_pkgs: +        if pkg.name not in expected_pkg_names:              continue +        expected_pkg_version = expected_pkgs_dict[pkg.name]["version"] +        req_release_arr = [int(segment) for segment in expected_pkg_version.split(".")]          version = [int(segment) for segment in pkg.version.split(".")]          too_high = version[:len(req_release_arr)] > req_release_arr          higher_than_seen = version > higher_version_for_pkg.get(pkg.name, []) @@ -164,11 +169,11 @@ def _check_higher_version_found(pkgs, expected_pkgs, requested_openshift_release          higher_found = []          for name, version in higher_version_for_pkg.items():              higher_found.append(name + '-' + '.'.join(str(segment) for segment in version)) -        raise FoundHigherVersion(requested_openshift_release, higher_found) +        raise FoundHigherVersion(higher_found)  class FoundMultiRelease(AosVersionException): -    '''Exception for reporting multiple minor releases found for same package''' +    """Exception for reporting multiple minor releases found for same package"""      def __init__(self, multi_found):          msg = ['Multiple minor versions of these packages are available']          msg += ['  ' + name for name in multi_found] @@ -176,18 +181,18 @@ class FoundMultiRelease(AosVersionException):          AosVersionException.__init__(self, '\n'.join(msg), multi_found) -def _check_multi_minor_release(pkgs, expected_pkgs): +def _check_multi_minor_release(pkgs, expected_pkgs_dict):      # see if any packages are available in more than one minor version      pkgs_by_name_version = {}      for pkg in pkgs:          # keep track of x.y (minor release) versions seen          minor_release = '.'.join(pkg.version.split('.')[:2])          if pkg.name not in pkgs_by_name_version: -            pkgs_by_name_version[pkg.name] = {} -        pkgs_by_name_version[pkg.name][minor_release] = True +            pkgs_by_name_version[pkg.name] = set() +        pkgs_by_name_version[pkg.name].add(minor_release)      multi_found = [] -    for name in expected_pkgs: +    for name in expected_pkgs_dict:          if name in pkgs_by_name_version and len(pkgs_by_name_version[name]) > 1:              multi_found.append(name) diff --git a/roles/openshift_health_checker/openshift_checks/package_version.py b/roles/openshift_health_checker/openshift_checks/package_version.py index 682f6bd40..2e737818b 100644 --- a/roles/openshift_health_checker/openshift_checks/package_version.py +++ b/roles/openshift_health_checker/openshift_checks/package_version.py @@ -1,5 +1,5 @@  # pylint: disable=missing-docstring -from openshift_checks import OpenShiftCheck, get_var +from openshift_checks import OpenShiftCheck, OpenShiftCheckException, get_var  from openshift_checks.mixins import NotContainerizedMixin @@ -9,6 +9,25 @@ class PackageVersion(NotContainerizedMixin, OpenShiftCheck):      name = "package_version"      tags = ["preflight"] +    openshift_to_ovs_version = { +        "3.6": "2.6", +        "3.5": "2.6", +        "3.4": "2.4", +    } + +    openshift_to_docker_version = { +        "3.1": "1.8", +        "3.2": "1.10", +        "3.3": "1.10", +        "3.4": "1.12", +    } + +    # map major release versions across releases +    # to a common major version +    openshift_major_release_version = { +        "1": "3", +    } +      @classmethod      def is_active(cls, task_vars):          """Skip hosts that do not have package requirements.""" @@ -17,9 +36,90 @@ class PackageVersion(NotContainerizedMixin, OpenShiftCheck):          return super(PackageVersion, cls).is_active(task_vars) and master_or_node      def run(self, tmp, task_vars): +        rpm_prefix = get_var(task_vars, "openshift", "common", "service_type") +        openshift_release = get_var(task_vars, "openshift_release", default='') +        deployment_type = get_var(task_vars, "openshift_deployment_type") +        check_multi_minor_release = deployment_type in ['openshift-enterprise'] +          args = { -            "requested_openshift_release": get_var(task_vars, "openshift_release", default=''), -            "openshift_deployment_type": get_var(task_vars, "openshift_deployment_type"), -            "rpm_prefix": get_var(task_vars, "openshift", "common", "service_type"), +            "package_list": [ +                { +                    "name": "openvswitch", +                    "version": self.get_required_ovs_version(task_vars), +                    "check_multi": False, +                }, +                { +                    "name": "docker", +                    "version": self.get_required_docker_version(task_vars), +                    "check_multi": False, +                }, +                { +                    "name": "{}".format(rpm_prefix), +                    "version": openshift_release, +                    "check_multi": check_multi_minor_release, +                }, +                { +                    "name": "{}-master".format(rpm_prefix), +                    "version": openshift_release, +                    "check_multi": check_multi_minor_release, +                }, +                { +                    "name": "{}-node".format(rpm_prefix), +                    "version": openshift_release, +                    "check_multi": check_multi_minor_release, +                }, +            ],          } +          return self.execute_module("aos_version", args, tmp, task_vars) + +    def get_required_ovs_version(self, task_vars): +        """Return the correct Open vSwitch version for the current OpenShift version. +        If the current OpenShift version is >= 3.5, ensure Open vSwitch version 2.6, +        Else ensure Open vSwitch version 2.4""" +        openshift_version = self.get_openshift_version(task_vars) + +        if float(openshift_version) < 3.5: +            return self.openshift_to_ovs_version["3.4"] + +        ovs_version = self.openshift_to_ovs_version.get(str(openshift_version)) +        if ovs_version: +            return ovs_version + +        msg = "There is no recommended version of Open vSwitch for the current version of OpenShift: {}" +        raise OpenShiftCheckException(msg.format(openshift_version)) + +    def get_required_docker_version(self, task_vars): +        """Return the correct Docker version for the current OpenShift version. +        If the OpenShift version is 3.1, ensure Docker version 1.8. +        If the OpenShift version is 3.2 or 3.3, ensure Docker version 1.10. +        If the current OpenShift version is >= 3.4, ensure Docker version 1.12.""" +        openshift_version = self.get_openshift_version(task_vars) + +        if float(openshift_version) >= 3.4: +            return self.openshift_to_docker_version["3.4"] + +        docker_version = self.openshift_to_docker_version.get(str(openshift_version)) +        if docker_version: +            return docker_version + +        msg = "There is no recommended version of Docker for the current version of OpenShift: {}" +        raise OpenShiftCheckException(msg.format(openshift_version)) + +    def get_openshift_version(self, task_vars): +        openshift_version = get_var(task_vars, "openshift_image_tag") +        if openshift_version and openshift_version[0] == 'v': +            openshift_version = openshift_version[1:] + +        return self.parse_version(openshift_version) + +    def parse_version(self, version): +        components = version.split(".") +        if not components or len(components) < 2: +            msg = "An invalid version of OpenShift was found for this host: {}" +            raise OpenShiftCheckException(msg.format(version)) + +        if components[0] in self.openshift_major_release_version: +            components[0] = self.openshift_major_release_version[components[0]] + +        return '.'.join(components[:2]) diff --git a/roles/openshift_health_checker/test/aos_version_test.py b/roles/openshift_health_checker/test/aos_version_test.py index 39c86067a..697805dd2 100644 --- a/roles/openshift_health_checker/test/aos_version_test.py +++ b/roles/openshift_health_checker/test/aos_version_test.py @@ -4,89 +4,118 @@ import aos_version  from collections import namedtuple  Package = namedtuple('Package', ['name', 'version']) -expected_pkgs = set(['spam', 'eggs']) +expected_pkgs = { +    "spam": { +        "name": "spam", +        "version": "3.2.1", +        "check_multi": False, +    }, +    "eggs": { +        "name": "eggs", +        "version": "3.2.1", +        "check_multi": False, +    }, +} -@pytest.mark.parametrize('pkgs, requested_release, expect_not_found', [ +@pytest.mark.parametrize('pkgs, expect_not_found', [      (          [], -        '3.2.1', -        expected_pkgs,  # none found +        { +            "spam": { +                "name": "spam", +                "version": "3.2.1", +                "check_multi": False, +            }, +            "eggs": { +                "name": "eggs", +                "version": "3.2.1", +                "check_multi": False, +            } +        },  # none found      ),      (          [Package('spam', '3.2.1')], -        '3.2', -        ['eggs'],  # completely missing +        { +            "eggs": { +                "name": "eggs", +                "version": "3.2.1", +                "check_multi": False, +            } +        },  # completely missing      ),      (          [Package('spam', '3.2.1'), Package('eggs', '3.3.2')], -        '3.2', -        ['eggs'],  # not the right version +        { +            "eggs": { +                "name": "eggs", +                "version": "3.2.1", +                "check_multi": False, +            } +        },  # not the right version      ),      (          [Package('spam', '3.2.1'), Package('eggs', '3.2.1')], -        '3.2', -        [],  # all found +        {},  # all found      ),      (          [Package('spam', '3.2.1'), Package('eggs', '3.2.1.5')], -        '3.2.1', -        [],  # found with more specific version +        {},  # found with more specific version      ),      (          [Package('eggs', '1.2.3'), Package('eggs', '3.2.1.5')], -        '3.2.1', -        ['spam'],  # eggs found with multiple versions +        { +            "spam": { +                "name": "spam", +                "version": "3.2.1", +                "check_multi": False, +            } +        },  # eggs found with multiple versions      ),  ]) -def test_check_pkgs_for_precise_version(pkgs, requested_release, expect_not_found): +def test_check_pkgs_for_precise_version(pkgs, expect_not_found):      if expect_not_found:          with pytest.raises(aos_version.PreciseVersionNotFound) as e: -            aos_version._check_precise_version_found(pkgs, expected_pkgs, requested_release) -        assert set(expect_not_found) == set(e.value.problem_pkgs) +            aos_version._check_precise_version_found(pkgs, expected_pkgs) + +        assert list(expect_not_found.values()) == e.value.problem_pkgs      else: -        aos_version._check_precise_version_found(pkgs, expected_pkgs, requested_release) +        aos_version._check_precise_version_found(pkgs, expected_pkgs) -@pytest.mark.parametrize('pkgs, requested_release, expect_higher', [ +@pytest.mark.parametrize('pkgs, expect_higher', [      (          [], -        '3.2.1',          [],      ),      ( -        [Package('spam', '3.2.1')], -        '3.2', +        [Package('spam', '3.2.1.9')],          [],  # more precise but not strictly higher      ),      (          [Package('spam', '3.3')], -        '3.2.1',          ['spam-3.3'],  # lower precision, but higher      ),      (          [Package('spam', '3.2.1'), Package('eggs', '3.3.2')], -        '3.2',          ['eggs-3.3.2'],  # one too high      ),      (          [Package('eggs', '1.2.3'), Package('eggs', '3.2.1.5'), Package('eggs', '3.4')], -        '3.2.1',          ['eggs-3.4'],  # multiple versions, one is higher      ),      (          [Package('eggs', '3.2.1'), Package('eggs', '3.4'), Package('eggs', '3.3')], -        '3.2.1',          ['eggs-3.4'],  # multiple versions, two are higher      ),  ]) -def test_check_pkgs_for_greater_version(pkgs, requested_release, expect_higher): +def test_check_pkgs_for_greater_version(pkgs, expect_higher):      if expect_higher:          with pytest.raises(aos_version.FoundHigherVersion) as e: -            aos_version._check_higher_version_found(pkgs, expected_pkgs, requested_release) +            aos_version._check_higher_version_found(pkgs, expected_pkgs)          assert set(expect_higher) == set(e.value.problem_pkgs)      else: -        aos_version._check_higher_version_found(pkgs, expected_pkgs, requested_release) +        aos_version._check_higher_version_found(pkgs, expected_pkgs)  @pytest.mark.parametrize('pkgs, expect_to_flag_pkgs', [ diff --git a/roles/openshift_health_checker/test/package_version_test.py b/roles/openshift_health_checker/test/package_version_test.py index 196d9816a..91eace512 100644 --- a/roles/openshift_health_checker/test/package_version_test.py +++ b/roles/openshift_health_checker/test/package_version_test.py @@ -1,24 +1,132 @@  import pytest -from openshift_checks.package_version import PackageVersion +from openshift_checks.package_version import PackageVersion, OpenShiftCheckException -def test_package_version(): +@pytest.mark.parametrize('openshift_release, extra_words', [ +    ('111.7.0', ["no recommended version of Open vSwitch"]), +    ('0.0.0', ["no recommended version of Docker"]), +]) +def test_openshift_version_not_supported(openshift_release, extra_words): +    def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None): +        return {} + +    task_vars = dict( +        openshift=dict(common=dict(service_type='origin')), +        openshift_release=openshift_release, +        openshift_image_tag='v' + openshift_release, +        openshift_deployment_type='origin', +    ) + +    check = PackageVersion(execute_module=execute_module) +    with pytest.raises(OpenShiftCheckException) as excinfo: +        check.run(tmp=None, task_vars=task_vars) + +    for word in extra_words: +        assert word in str(excinfo.value) + + +def test_invalid_openshift_release_format(): +    def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None): +        return {} + +    task_vars = dict( +        openshift=dict(common=dict(service_type='origin')), +        openshift_image_tag='v0', +        openshift_deployment_type='origin', +    ) + +    check = PackageVersion(execute_module=execute_module) +    with pytest.raises(OpenShiftCheckException) as excinfo: +        check.run(tmp=None, task_vars=task_vars) +    assert "invalid version" in str(excinfo.value) + + +@pytest.mark.parametrize('openshift_release', [ +    "3.5", +    "3.6", +    "3.4", +    "3.3", +]) +def test_package_version(openshift_release):      task_vars = dict(          openshift=dict(common=dict(service_type='origin')), -        openshift_release='3.5', +        openshift_release=openshift_release, +        openshift_image_tag='v' + openshift_release,          openshift_deployment_type='origin',      )      return_value = object()      def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None):          assert module_name == 'aos_version' -        assert 'requested_openshift_release' in module_args -        assert 'openshift_deployment_type' in module_args -        assert 'rpm_prefix' in module_args -        assert module_args['requested_openshift_release'] == task_vars['openshift_release'] -        assert module_args['openshift_deployment_type'] == task_vars['openshift_deployment_type'] -        assert module_args['rpm_prefix'] == task_vars['openshift']['common']['service_type'] +        assert "package_list" in module_args + +        for pkg in module_args["package_list"]: +            if "-master" in pkg["name"] or "-node" in pkg["name"]: +                assert pkg["version"] == task_vars["openshift_release"] + +        return return_value + +    check = PackageVersion(execute_module=execute_module) +    result = check.run(tmp=None, task_vars=task_vars) +    assert result is return_value + + +@pytest.mark.parametrize('deployment_type,openshift_release,expected_ovs_version', [ +    ("openshift-enterprise", "3.5", "2.6"), +    ("origin", "3.6", "2.6"), +    ("openshift-enterprise", "3.4", "2.4"), +    ("origin", "3.3", "2.4"), +]) +def test_ovs_package_version(deployment_type, openshift_release, expected_ovs_version): +    task_vars = dict( +        openshift=dict(common=dict(service_type='origin')), +        openshift_release=openshift_release, +        openshift_image_tag='v' + openshift_release, +        openshift_deployment_type=deployment_type, +    ) +    return_value = object() + +    def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None): +        assert module_name == 'aos_version' +        assert "package_list" in module_args + +        for pkg in module_args["package_list"]: +            if pkg["name"] == "openvswitch": +                assert pkg["version"] == expected_ovs_version + +        return return_value + +    check = PackageVersion(execute_module=execute_module) +    result = check.run(tmp=None, task_vars=task_vars) +    assert result is return_value + + +@pytest.mark.parametrize('deployment_type,openshift_release,expected_docker_version', [ +    ("origin", "3.5", "1.12"), +    ("openshift-enterprise", "3.4", "1.12"), +    ("origin", "3.3", "1.10"), +    ("openshift-enterprise", "3.2", "1.10"), +    ("origin", "3.1", "1.8"), +    ("openshift-enterprise", "3.1", "1.8"), +]) +def test_docker_package_version(deployment_type, openshift_release, expected_docker_version): +    task_vars = dict( +        openshift=dict(common=dict(service_type='origin')), +        openshift_release=openshift_release, +        openshift_image_tag='v' + openshift_release, +        openshift_deployment_type=deployment_type, +    ) +    return_value = object() + +    def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None): +        assert module_name == 'aos_version' +        assert "package_list" in module_args + +        for pkg in module_args["package_list"]: +            if pkg["name"] == "docker": +                assert pkg["version"] == expected_docker_version +          return return_value      check = PackageVersion(execute_module=execute_module) | 
