diff options
Diffstat (limited to 'roles/openshift_health_checker/openshift_checks')
5 files changed, 207 insertions, 23 deletions
diff --git a/roles/openshift_health_checker/openshift_checks/__init__.py b/roles/openshift_health_checker/openshift_checks/__init__.py index f66d19fbf..be63d864a 100644 --- a/roles/openshift_health_checker/openshift_checks/__init__.py +++ b/roles/openshift_health_checker/openshift_checks/__init__.py @@ -8,11 +8,8 @@ import os from abc import ABCMeta, abstractmethod, abstractproperty from importlib import import_module -# add_metaclass is not available in the embedded six from module_utils in Ansible 2.2.1 -from six import add_metaclass -# pylint import-error disabled because pylint cannot find the package -# when installed in a virtualenv -from ansible.module_utils.six.moves import reduce # pylint: disable=import-error, redefined-builtin +from ansible.module_utils import six +from ansible.module_utils.six.moves import reduce # pylint: disable=import-error,redefined-builtin class OpenShiftCheckException(Exception): @@ -20,12 +17,17 @@ class OpenShiftCheckException(Exception): pass -@add_metaclass(ABCMeta) +@six.add_metaclass(ABCMeta) class OpenShiftCheck(object): """A base class for defining checks for an OpenShift cluster environment.""" - def __init__(self, module_executor): - self.module_executor = module_executor + def __init__(self, execute_module=None, module_executor=None): + if execute_module is module_executor is None: + raise TypeError( + "__init__() takes either execute_module (recommended) " + "or module_executor (deprecated), none given") + self.execute_module = execute_module or module_executor + self.module_executor = self.execute_module @abstractproperty def name(self): @@ -61,6 +63,21 @@ class OpenShiftCheck(object): yield subclass +LOADER_EXCLUDES = ( + "__init__.py", + "mixins.py", +) + + +def load_checks(): + """Dynamically import all check modules for the side effect of registering checks.""" + return [ + import_module(__package__ + "." + name[:-3]) + for name in os.listdir(os.path.dirname(__file__)) + if name.endswith(".py") and name not in LOADER_EXCLUDES + ] + + def get_var(task_vars, *keys, **kwargs): """Helper function to get deeply nested values from task_vars. @@ -76,15 +93,3 @@ def get_var(task_vars, *keys, **kwargs): return kwargs["default"] raise OpenShiftCheckException("'{}' is undefined".format(".".join(map(str, keys)))) return value - - -# Dynamically import all submodules for the side effect of loading checks. - -EXCLUDES = ( - "__init__.py", - "mixins.py", -) - -for name in os.listdir(os.path.dirname(__file__)): - if name.endswith(".py") and name not in EXCLUDES: - import_module(__package__ + "." + name[:-3]) diff --git a/roles/openshift_health_checker/openshift_checks/docker_image_availability.py b/roles/openshift_health_checker/openshift_checks/docker_image_availability.py new file mode 100644 index 000000000..cce289b95 --- /dev/null +++ b/roles/openshift_health_checker/openshift_checks/docker_image_availability.py @@ -0,0 +1,179 @@ +# pylint: disable=missing-docstring +from openshift_checks import OpenShiftCheck, get_var + + +class DockerImageAvailability(OpenShiftCheck): + """Check that required Docker images are available. + + This check attempts to ensure that required docker images are + either present locally, or able to be pulled down from available + registries defined in a host machine. + """ + + name = "docker_image_availability" + tags = ["preflight"] + + skopeo_image = "openshift/openshift-ansible" + + # FIXME(juanvallejo): we should consider other possible values of + # `deployment_type` (the key here). See + # https://github.com/openshift/openshift-ansible/blob/8e26f8c/roles/openshift_repos/vars/main.yml#L7 + docker_image_base = { + "origin": { + "repo": "openshift", + "image": "origin", + }, + "openshift-enterprise": { + "repo": "openshift3", + "image": "ose", + }, + } + + def run(self, tmp, task_vars): + required_images = self.required_images(task_vars) + missing_images = set(required_images) - set(self.local_images(required_images, task_vars)) + + # exit early if all images were found locally + if not missing_images: + return {"changed": False} + + msg, failed, changed = self.update_skopeo_image(task_vars) + + # exit early if Skopeo update fails + if failed: + return { + "failed": True, + "changed": changed, + "msg": "Failed to update Skopeo image ({img_name}). {msg}".format(img_name=self.skopeo_image, msg=msg), + } + + registries = self.known_docker_registries(task_vars) + available_images = self.available_images(missing_images, registries, task_vars) + unavailable_images = set(missing_images) - set(available_images) + + if unavailable_images: + return { + "failed": True, + "msg": ( + "One or more required images are not available: {}.\n" + "Configured registries: {}" + ).format(", ".join(sorted(unavailable_images)), ", ".join(registries)), + "changed": changed, + } + + return {"changed": changed} + + def required_images(self, task_vars): + deployment_type = get_var(task_vars, "deployment_type") + # FIXME(juanvallejo): we should handle gracefully with a proper error + # message when given an unexpected value for `deployment_type`. + image_base_name = self.docker_image_base[deployment_type] + + openshift_release = get_var(task_vars, "openshift_release") + # FIXME(juanvallejo): this variable is not required when the + # installation is non-containerized. The example inventories have it + # commented out. We should handle gracefully and with a proper error + # message when this variable is required and not set. + openshift_image_tag = get_var(task_vars, "openshift_image_tag") + + is_containerized = get_var(task_vars, "openshift", "common", "is_containerized") + + if is_containerized: + images = set(self.containerized_docker_images(image_base_name, openshift_release)) + else: + images = set(self.rpm_docker_images(image_base_name, openshift_release)) + + # append images with qualified image tags to our list of required images. + # these are images with a (v0.0.0.0) tag, rather than a standard release + # format tag (v0.0). We want to check this set in both containerized and + # non-containerized installations. + images.update( + self.qualified_docker_images(self.image_from_base_name(image_base_name), "v" + openshift_image_tag) + ) + + return images + + def local_images(self, images, task_vars): + """Filter a list of images and return those available locally.""" + return [ + image for image in images + if self.is_image_local(image, task_vars) + ] + + def is_image_local(self, image, task_vars): + result = self.module_executor("docker_image_facts", {"name": image}, task_vars) + if result.get("failed", False): + return False + + return bool(result.get("images", [])) + + def known_docker_registries(self, task_vars): + result = self.module_executor("docker_info", {}, task_vars) + + if result.get("failed", False): + return [] + + # FIXME(juanvallejo): wrong default type, result["info"] is expected to + # contain a dictionary (see how we call `docker_info.get` below). + docker_info = result.get("info", "") + return [registry.get("Name", "") for registry in docker_info.get("Registries", {})] + + def available_images(self, images, registries, task_vars): + """Inspect existing images using Skopeo and return all images successfully inspected.""" + return [ + image for image in images + if self.is_image_available(image, registries, task_vars) + ] + + def is_image_available(self, image, registries, task_vars): + for registry in registries: + if self.is_available_skopeo_image(image, registry, task_vars): + return True + + return False + + def is_available_skopeo_image(self, image, registry, task_vars): + """Uses Skopeo to determine if required image exists in a given registry.""" + + cmd_str = "skopeo inspect docker://{registry}/{image}".format( + registry=registry, + image=image, + ) + + args = { + "name": "skopeo_inspect", + "image": self.skopeo_image, + "command": cmd_str, + "detach": False, + "cleanup": True, + } + result = self.module_executor("docker_container", args, task_vars) + return result.get("failed", False) + + def containerized_docker_images(self, base_name, version): + return [ + "{image}:{version}".format(image=self.image_from_base_name(base_name), version=version) + ] + + @staticmethod + def rpm_docker_images(base, version): + return [ + "{image_repo}/registry-console:{version}".format(image_repo=base["repo"], version=version) + ] + + @staticmethod + def qualified_docker_images(image_name, version): + return [ + "{}-{}:{}".format(image_name, component, version) + for component in "haproxy-router docker-registry deployer pod".split() + ] + + @staticmethod + def image_from_base_name(base): + return "".join([base["repo"], "/", base["image"]]) + + # ensures that the skopeo docker image exists, and updates it + # with latest if image was already present locally. + def update_skopeo_image(self, task_vars): + result = self.module_executor("docker_image", {"name": self.skopeo_image}, task_vars) + return result.get("msg", ""), result.get("failed", False), result.get("changed", False) diff --git a/roles/openshift_health_checker/openshift_checks/package_availability.py b/roles/openshift_health_checker/openshift_checks/package_availability.py index 771123d61..9891972a6 100644 --- a/roles/openshift_health_checker/openshift_checks/package_availability.py +++ b/roles/openshift_health_checker/openshift_checks/package_availability.py @@ -21,7 +21,7 @@ class PackageAvailability(NotContainerizedMixin, OpenShiftCheck): packages.update(self.node_packages(rpm_prefix)) args = {"packages": sorted(set(packages))} - return self.module_executor("check_yum_update", args, tmp, task_vars) + return self.execute_module("check_yum_update", args, tmp, task_vars) @staticmethod def master_packages(rpm_prefix): diff --git a/roles/openshift_health_checker/openshift_checks/package_update.py b/roles/openshift_health_checker/openshift_checks/package_update.py index c5a226954..fd0c0a755 100644 --- a/roles/openshift_health_checker/openshift_checks/package_update.py +++ b/roles/openshift_health_checker/openshift_checks/package_update.py @@ -11,4 +11,4 @@ class PackageUpdate(NotContainerizedMixin, OpenShiftCheck): def run(self, tmp, task_vars): args = {"packages": []} - return self.module_executor("check_yum_update", args, tmp, task_vars) + return self.execute_module("check_yum_update", args, tmp, task_vars) diff --git a/roles/openshift_health_checker/openshift_checks/package_version.py b/roles/openshift_health_checker/openshift_checks/package_version.py index 2e9d07deb..42193a1c6 100644 --- a/roles/openshift_health_checker/openshift_checks/package_version.py +++ b/roles/openshift_health_checker/openshift_checks/package_version.py @@ -17,4 +17,4 @@ class PackageVersion(NotContainerizedMixin, OpenShiftCheck): "prefix": rpm_prefix, "version": openshift_release, } - return self.module_executor("aos_version", args, tmp, task_vars) + return self.execute_module("aos_version", args, tmp, task_vars) |