diff options
34 files changed, 393 insertions, 194 deletions
diff --git a/.tito/packages/openshift-ansible b/.tito/packages/openshift-ansible index 810510bdf..9a5acc500 100644 --- a/.tito/packages/openshift-ansible +++ b/.tito/packages/openshift-ansible @@ -1 +1 @@ -3.7.0-0.125.1 ./ +3.7.0-0.126.0 ./ diff --git a/docs/proposals/README.md b/docs/proposals/README.md new file mode 100644 index 000000000..89bbe5163 --- /dev/null +++ b/docs/proposals/README.md @@ -0,0 +1,27 @@ +# OpenShift-Ansible Proposal Process + +## Proposal Decision Tree +TODO: Add details about when a proposal is or is not required. + +## Proposal Process +The following process should be followed when a proposal is needed: + +1. Create a pull request with the initial proposal + * Use the [proposal template][template] + * Name the proposal using two or three topic words with underscores as a separator (i.e. proposal_template.md) + * Place the proposal in the docs/proposals directory +2. Notify the development team of the proposal and request feedback +3. Review the proposal on the OpenShift-Ansible Architecture Meeting +4. Update the proposal as needed and ask for feedback +5. Approved/Closed Phase + * If 75% or more of the active development team give the proposal a :+1: it is Approved + * If 50% or more of the active development team disagrees with the proposal it is Closed + * If the person proposing the proposal no longer wishes to continue they can request it to be Closed + * If there is no activity on a proposal, the active development team may Close the proposal at their discretion + * If none of the above is met the cycle can continue to Step 4. +6. For approved proposals, the current development lead(s) will: + * Update the Pull Request with the result and merge the proposal + * Create a card on the Cluster Lifecycle [Trello board][trello] so it may be scheduled for implementation. + +[template]: proposal_template.md +[trello]: https://trello.com/b/wJYDst6C diff --git a/docs/proposals/proposal_template.md b/docs/proposals/proposal_template.md new file mode 100644 index 000000000..ece288037 --- /dev/null +++ b/docs/proposals/proposal_template.md @@ -0,0 +1,30 @@ +# Proposal Title + +## Description +<Short introduction> + +## Rationale +<Summary of main points of Design> + +## Design +<Main content goes here> + +## Checklist +* Item 1 +* Item 2 +* Item 3 + +## User Story +As a developer on OpenShift-Ansible, +I want ... +so that ... + +## Acceptance Criteria +* Verify that ... +* Verify that ... +* Verify that ... + +## References +* Link +* Link +* Link diff --git a/inventory/byo/hosts.origin.example b/inventory/byo/hosts.origin.example index c32c47fe1..dbe57bbd2 100644 --- a/inventory/byo/hosts.origin.example +++ b/inventory/byo/hosts.origin.example @@ -118,7 +118,7 @@ openshift_release=v3.6 # Force the registry to use for the docker/crio system container. By default the registry # will be built off of the deployment type and ansible_distribution. Only # use this option if you are sure you know what you are doing! -#openshift_docker_systemcontainer_image_registry_override="registry.example.com" +#openshift_docker_systemcontainer_image_override="registry.example.com/container-engine:latest" #openshift_crio_systemcontainer_image_registry_override="registry.example.com" # Items added, as is, to end of /etc/sysconfig/docker OPTIONS # Default value: "--log-driver=journald" diff --git a/inventory/byo/hosts.ose.example b/inventory/byo/hosts.ose.example index e867ffbae..0d60de6d2 100644 --- a/inventory/byo/hosts.ose.example +++ b/inventory/byo/hosts.ose.example @@ -118,7 +118,7 @@ openshift_release=v3.6 # Force the registry to use for the container-engine/crio system container. By default the registry # will be built off of the deployment type and ansible_distribution. Only # use this option if you are sure you know what you are doing! -#openshift_docker_systemcontainer_image_registry_override="registry.example.com" +#openshift_docker_systemcontainer_image_override="registry.example.com/container-engine:latest" #openshift_crio_systemcontainer_image_registry_override="registry.example.com" # Items added, as is, to end of /etc/sysconfig/docker OPTIONS # Default value: "--log-driver=journald" diff --git a/openshift-ansible.spec b/openshift-ansible.spec index fa63bad19..3be13145e 100644 --- a/openshift-ansible.spec +++ b/openshift-ansible.spec @@ -10,7 +10,7 @@ Name: openshift-ansible Version: 3.7.0 -Release: 0.125.1%{?dist} +Release: 0.126.0%{?dist} Summary: Openshift and Atomic Enterprise Ansible License: ASL 2.0 URL: https://github.com/openshift/openshift-ansible @@ -280,6 +280,17 @@ Atomic OpenShift Utilities includes %changelog +* Mon Sep 11 2017 Jenkins CD Merge Bot <smunilla@redhat.com> 3.7.0-0.126.0 +- Fix rpm version logic for hosts (mgugino@redhat.com) +- Revert back to hostnamectl and previous default of not setting hostname + (sdodson@redhat.com) +- Correct include path to not follow symlink (rteague@redhat.com) +- Fix include path for docker upgrade tasks (rteague@redhat.com) +- Fix issue with etcd_common when using pre_upgrade tag (rteague@redhat.com) +- inventory: Denote new required upgrade variables (smilner@redhat.com) +- upgrade: Verify required network items are set (smilner@redhat.com) +- ami build process calls openshift-node/config.yml (kwoodson@redhat.com) + * Fri Sep 08 2017 Scott Dodson <sdodson@redhat.com> 3.7.0-0.125.1 - Consolidating AWS roles and variables underneath openshift_aws role. (kwoodson@redhat.com) diff --git a/playbooks/byo/openshift-master/scaleup.yml b/playbooks/byo/openshift-master/scaleup.yml index 2179d1416..a09edd55a 100644 --- a/playbooks/byo/openshift-master/scaleup.yml +++ b/playbooks/byo/openshift-master/scaleup.yml @@ -1,7 +1,7 @@ --- - include: ../openshift-cluster/initialize_groups.yml -- name: Ensure there are new_masters +- name: Ensure there are new_masters or new_nodes hosts: localhost connection: local become: no @@ -13,7 +13,7 @@ add hosts to the new_masters and new_nodes host groups to add masters. when: - - (g_new_master_hosts | default([]) | length == 0) or (g_new_node_hosts | default([]) | length == 0) + - (g_new_master_hosts | default([]) | length == 0) and (g_new_node_hosts | default([]) | length == 0) - include: ../../common/openshift-cluster/std_include.yml diff --git a/playbooks/common/openshift-cluster/sanity_checks.yml b/playbooks/common/openshift-cluster/sanity_checks.yml index 7e28a11e8..26716a92d 100644 --- a/playbooks/common/openshift-cluster/sanity_checks.yml +++ b/playbooks/common/openshift-cluster/sanity_checks.yml @@ -45,3 +45,7 @@ - fail: msg: openshift_hostname must be 63 characters or less when: openshift_hostname is defined and openshift_hostname | length > 63 + + - fail: + msg: openshift_public_hostname must be 63 characters or less + when: openshift_public_hostname is defined and openshift_public_hostname | length > 63 diff --git a/playbooks/common/openshift-cluster/upgrades/etcd/backup.yml b/playbooks/common/openshift-cluster/upgrades/etcd/backup.yml index 616ba04f8..2cc6c9019 100644 --- a/playbooks/common/openshift-cluster/upgrades/etcd/backup.yml +++ b/playbooks/common/openshift-cluster/upgrades/etcd/backup.yml @@ -2,7 +2,7 @@ - name: Backup etcd hosts: oo_etcd_hosts_to_backup roles: - - role: openshift_facts + - role: openshift_etcd_facts - role: etcd_common r_etcd_common_action: backup r_etcd_common_backup_tag: etcd_backup_tag diff --git a/playbooks/common/openshift-cluster/upgrades/pre/verify_inventory_vars.yml b/playbooks/common/openshift-cluster/upgrades/pre/verify_inventory_vars.yml index 78923e04f..4c345dbe8 100644 --- a/playbooks/common/openshift-cluster/upgrades/pre/verify_inventory_vars.yml +++ b/playbooks/common/openshift-cluster/upgrades/pre/verify_inventory_vars.yml @@ -22,7 +22,7 @@ variables when upgrading. These variables should match what is currently used in the cluster. If you don't remember what these values are you can find them in /etc/origin/master/master-config.yaml on a master with the names clusterNetworkCIDR (osm_cluster_network_cidr), - osm_host_subnet_length (hostSubnetLength), and openshift_portal_net (hostSubnetLength). + hostSubnetLength (osm_host_subnet_length), and serviceNetworkCIDR (openshift_portal_net). # Error out in situations where the user has older versions specified in their # inventory in any of the openshift_release, openshift_image_tag, and diff --git a/roles/docker/tasks/systemcontainer_crio.yml b/roles/docker/tasks/systemcontainer_crio.yml index 24ca0d9f8..0bab0899c 100644 --- a/roles/docker/tasks/systemcontainer_crio.yml +++ b/roles/docker/tasks/systemcontainer_crio.yml @@ -95,7 +95,7 @@ - name: Set to default prepend set_fact: l_crio_image_prepend: "docker.io/gscrivano" - l_crio_image_name: "crio-o-fedora" + l_crio_image_name: "cri-o-fedora" - name: Use Centos based image when distribution is CentOS set_fact: diff --git a/roles/docker/tasks/systemcontainer_docker.yml b/roles/docker/tasks/systemcontainer_docker.yml index 57a84bc2c..146e5f430 100644 --- a/roles/docker/tasks/systemcontainer_docker.yml +++ b/roles/docker/tasks/systemcontainer_docker.yml @@ -100,18 +100,22 @@ l_docker_image_prepend: "registry.fedoraproject.org/f25" when: ansible_distribution == 'Fedora' - # For https://github.com/openshift/openshift-ansible/pull/4049#discussion_r114478504 - - name: Use a testing registry if requested - set_fact: - l_docker_image_prepend: "{{ openshift_docker_systemcontainer_image_registry_override }}" - when: - - openshift_docker_systemcontainer_image_registry_override is defined - - openshift_docker_systemcontainer_image_registry_override != "" - - name: Set the full image name set_fact: l_docker_image: "{{ l_docker_image_prepend }}/{{ openshift.docker.service_name }}:latest" + # For https://github.com/openshift/openshift-ansible/pull/5354#issuecomment-328552959 + - name: Use a specific image if requested + set_fact: + l_docker_image: "{{ openshift_docker_systemcontainer_image_override }}" + when: + - openshift_docker_systemcontainer_image_override is defined + - openshift_docker_systemcontainer_image_override != "" + + # Be nice and let the user see the variable result + - debug: + var: l_docker_image + # NOTE: no_proxy added as a workaround until https://github.com/projectatomic/atomic/pull/999 is released - name: Pre-pull Container Engine System Container image command: "atomic pull --storage ostree {{ l_docker_image }}" diff --git a/roles/etcd_common/defaults/main.yml b/roles/etcd_common/defaults/main.yml index 89993f7ea..b67411f40 100644 --- a/roles/etcd_common/defaults/main.yml +++ b/roles/etcd_common/defaults/main.yml @@ -56,7 +56,7 @@ etcd_is_containerized: False etcd_is_thirdparty: False # etcd dir vars -etcd_data_dir: "{{ '/var/lib/origin/openshift.local.etcd' if r_etcd_common_embedded_etcd | bool else '/var/lib/etcd/' if openshift.common.etcd_runtime != 'runc' else '/var/lib/etcd/etcd.etcd/' }}" +etcd_data_dir: "{{ '/var/lib/origin/openshift.local.etcd' if r_etcd_common_embedded_etcd | bool else '/var/lib/etcd/' if r_etcd_common_etcd_runtime != 'runc' else '/var/lib/etcd/etcd.etcd/' }}" # etcd ports and protocols etcd_client_port: 2379 diff --git a/roles/etcd_common/tasks/backup.yml b/roles/etcd_common/tasks/backup.yml index 2bc486d3f..c1580640f 100644 --- a/roles/etcd_common/tasks/backup.yml +++ b/roles/etcd_common/tasks/backup.yml @@ -29,7 +29,6 @@ - name: Check current etcd disk usage shell: du --exclude='*openshift-backup*' -k {{ l_etcd_data_dir }} | tail -n 1 | cut -f1 register: l_etcd_disk_usage - when: r_etcd_common_embedded_etcd | bool # AUDIT:changed_when: `false` because we are only inspecting # state, not manipulating anything changed_when: false @@ -39,7 +38,7 @@ msg: > {{ l_etcd_disk_usage.stdout }} Kb disk space required for etcd backup, {{ l_avail_disk.stdout }} Kb available. - when: (r_etcd_common_embedded_etcd | bool) and (l_etcd_disk_usage.stdout|int > l_avail_disk.stdout|int) + when: l_etcd_disk_usage.stdout|int*2 > l_avail_disk.stdout|int # For non containerized and non embedded we should have the correct version of # etcd installed already. So don't do anything. diff --git a/roles/openshift_health_checker/action_plugins/openshift_health_check.py b/roles/openshift_health_checker/action_plugins/openshift_health_check.py index 8d35db6b5..d02a43655 100644 --- a/roles/openshift_health_checker/action_plugins/openshift_health_check.py +++ b/roles/openshift_health_checker/action_plugins/openshift_health_check.py @@ -187,7 +187,7 @@ def normalize(checks): def run_check(name, check, user_disabled_checks): """Run a single check if enabled and return a result dict.""" - if name in user_disabled_checks: + if name in user_disabled_checks or '*' in user_disabled_checks: return dict(skipped=True, skipped_reason="Disabled by user request") # pylint: disable=broad-except; capturing exceptions broadly is intentional, diff --git a/roles/openshift_health_checker/callback_plugins/zz_failure_summary.py b/roles/openshift_health_checker/callback_plugins/zz_failure_summary.py index 349655966..dcaf87eca 100644 --- a/roles/openshift_health_checker/callback_plugins/zz_failure_summary.py +++ b/roles/openshift_health_checker/callback_plugins/zz_failure_summary.py @@ -10,6 +10,7 @@ import traceback from ansible.plugins.callback import CallbackBase from ansible import constants as C from ansible.utils.color import stringc +from ansible.module_utils.six import string_types FAILED_NO_MSG = u'Failed without returning a message.' @@ -140,11 +141,19 @@ def deduplicate_failures(failures): Returns a new list of failures such that identical failures from different hosts are grouped together in a single entry. The relative order of failures is preserved. + + If failures is unhashable, the original list of failures is returned. """ groups = defaultdict(list) for failure in failures: group_key = tuple(sorted((key, value) for key, value in failure.items() if key != 'host')) - groups[group_key].append(failure) + try: + groups[group_key].append(failure) + except TypeError: + # abort and return original list of failures when failures has an + # unhashable type. + return failures + result = [] for failure in failures: group_key = tuple(sorted((key, value) for key, value in failure.items() if key != 'host')) @@ -159,7 +168,10 @@ def format_failure(failure): """Return a list of pretty-formatted text entries describing a failure, including relevant information about it. Expect that the list of text entries will be joined by a newline separator when output to the user.""" - host = u', '.join(failure['host']) + if isinstance(failure['host'], string_types): + host = failure['host'] + else: + host = u', '.join(failure['host']) play = failure['play'] task = failure['task'] msg = failure['msg'] diff --git a/roles/openshift_health_checker/openshift_checks/__init__.py b/roles/openshift_health_checker/openshift_checks/__init__.py index 02ee1d0f9..987c955b6 100644 --- a/roles/openshift_health_checker/openshift_checks/__init__.py +++ b/roles/openshift_health_checker/openshift_checks/__init__.py @@ -4,6 +4,7 @@ Health checks for OpenShift clusters. import operator import os +import time from abc import ABCMeta, abstractmethod, abstractproperty from importlib import import_module @@ -57,6 +58,9 @@ class OpenShiftCheck(object): self._execute_module = execute_module self.task_vars = task_vars or {} self.tmp = tmp + # mainly for testing purposes; see execute_module_with_retries + self._module_retries = 3 + self._module_retry_interval = 5 # seconds # set to True when the check changes the host, for accurate total "changed" count self.changed = False @@ -115,6 +119,19 @@ class OpenShiftCheck(object): ) return self._execute_module(module_name, module_args, self.tmp, self.task_vars) + def execute_module_with_retries(self, module_name, module_args): + """Run execute_module and retry on failure.""" + result = {} + tries = 0 + while True: + res = self.execute_module(module_name, module_args) + if tries > self._module_retries or not res.get("failed"): + result.update(res) + return result + result["last_failed"] = res + tries += 1 + time.sleep(self._module_retry_interval) + def get_var(self, *keys, **kwargs): """Get deeply nested values from task_vars. diff --git a/roles/openshift_health_checker/openshift_checks/docker_image_availability.py b/roles/openshift_health_checker/openshift_checks/docker_image_availability.py index 866c74d7c..9c35f0f92 100644 --- a/roles/openshift_health_checker/openshift_checks/docker_image_availability.py +++ b/roles/openshift_health_checker/openshift_checks/docker_image_availability.py @@ -32,7 +32,12 @@ class DockerImageAvailability(DockerHostMixin, OpenShiftCheck): # we use python-docker-py to check local docker for images, and skopeo # to look for images available remotely without waiting to pull them. dependencies = ["python-docker-py", "skopeo"] - skopeo_img_check_command = "timeout 10 skopeo inspect --tls-verify=false" + skopeo_img_check_command = "timeout 10 skopeo inspect --tls-verify=false docker://{registry}/{image}" + + def __init__(self, *args, **kwargs): + super(DockerImageAvailability, self).__init__(*args, **kwargs) + # record whether we could reach a registry or not (and remember results) + self.reachable_registries = {} def is_active(self): """Skip hosts with unsupported deployment types.""" @@ -64,15 +69,21 @@ class DockerImageAvailability(DockerHostMixin, OpenShiftCheck): unavailable_images = set(missing_images) - set(available_images) if unavailable_images: - return { - "failed": True, - "msg": ( - "One or more required Docker images are not available:\n {}\n" - "Configured registries: {}\n" - "Checked by: {}" - ).format(",\n ".join(sorted(unavailable_images)), ", ".join(registries), - self.skopeo_img_check_command), - } + registries = [ + reg if self.reachable_registries.get(reg, True) else reg + " (unreachable)" + for reg in registries + ] + msg = ( + "One or more required Docker images are not available:\n {}\n" + "Configured registries: {}\n" + "Checked by: {}" + ).format( + ",\n ".join(sorted(unavailable_images)), + ", ".join(registries), + self.skopeo_img_check_command + ) + + return dict(failed=True, msg=msg) return {} @@ -128,31 +139,31 @@ class DockerImageAvailability(DockerHostMixin, OpenShiftCheck): def local_images(self, images): """Filter a list of images and return those available locally.""" - return [ - image for image in images - if self.is_image_local(image) - ] + registries = self.known_docker_registries() + found_images = [] + for image in images: + # docker could have the image name as-is or prefixed with any registry + imglist = [image] + [reg + "/" + image for reg in registries] + if self.is_image_local(imglist): + found_images.append(image) + return found_images def is_image_local(self, image): """Check if image is already in local docker index.""" result = self.execute_module("docker_image_facts", {"name": image}) - if result.get("failed", False): - return False - - return bool(result.get("images", [])) + return bool(result.get("images")) and not result.get("failed") def known_docker_registries(self): """Build a list of docker registries available according to inventory vars.""" - docker_facts = self.get_var("openshift", "docker") - regs = set(docker_facts["additional_registries"]) + regs = list(self.get_var("openshift.docker.additional_registries", default=[])) deployment_type = self.get_var("openshift_deployment_type") - if deployment_type == "origin": - regs.update(["docker.io"]) - elif "enterprise" in deployment_type: - regs.update(["registry.access.redhat.com"]) + if deployment_type == "origin" and "docker.io" not in regs: + regs.append("docker.io") + elif "enterprise" in deployment_type and "registry.access.redhat.com" not in regs: + regs.append("registry.access.redhat.com") - return list(regs) + return regs def available_images(self, images, default_registries): """Search remotely for images. Returns: list of images found.""" @@ -165,17 +176,35 @@ class DockerImageAvailability(DockerHostMixin, OpenShiftCheck): """Use Skopeo to determine if required image exists in known registry(s).""" registries = default_registries - # if image already includes a registry, only use that + # If image already includes a registry, only use that. + # NOTE: This logic would incorrectly identify images that do not use a namespace, e.g. + # registry.access.redhat.com/rhel7 as if the registry were a namespace. + # It's not clear that there's any way to distinguish them, but fortunately + # the current set of images all look like [registry/]namespace/name[:version]. if image.count("/") > 1: registry, image = image.split("/", 1) registries = [registry] for registry in registries: - args = { - "_raw_params": self.skopeo_img_check_command + " docker://{}/{}".format(registry, image) - } - result = self.execute_module("command", args) + if registry not in self.reachable_registries: + self.reachable_registries[registry] = self.connect_to_registry(registry) + if not self.reachable_registries[registry]: + continue + + args = {"_raw_params": self.skopeo_img_check_command.format(registry=registry, image=image)} + result = self.execute_module_with_retries("command", args) if result.get("rc", 0) == 0 and not result.get("failed"): return True + if result.get("rc") == 124: # RC 124 == timed out; mark unreachable + self.reachable_registries[registry] = False return False + + def connect_to_registry(self, registry): + """Use ansible wait_for module to test connectivity from host to registry. Returns bool.""" + # test a simple TCP connection + host, _, port = registry.partition(":") + port = port or 443 + args = dict(host=host, port=port, state="started", timeout=30) + result = self.execute_module("wait_for", args) + return result.get("rc", 0) == 0 and not result.get("failed") diff --git a/roles/openshift_health_checker/openshift_checks/mixins.py b/roles/openshift_health_checker/openshift_checks/mixins.py index e9bae60a3..24f1d938a 100644 --- a/roles/openshift_health_checker/openshift_checks/mixins.py +++ b/roles/openshift_health_checker/openshift_checks/mixins.py @@ -36,7 +36,7 @@ class DockerHostMixin(object): # NOTE: we would use the "package" module but it's actually an action plugin # and it's not clear how to invoke one of those. This is about the same anyway: - result = self.execute_module( + result = self.execute_module_with_retries( self.get_var("ansible_pkg_mgr", default="yum"), {"name": self.dependencies, "state": "present"}, ) diff --git a/roles/openshift_health_checker/openshift_checks/package_availability.py b/roles/openshift_health_checker/openshift_checks/package_availability.py index a86180b00..21355c2f0 100644 --- a/roles/openshift_health_checker/openshift_checks/package_availability.py +++ b/roles/openshift_health_checker/openshift_checks/package_availability.py @@ -26,7 +26,7 @@ class PackageAvailability(NotContainerizedMixin, OpenShiftCheck): packages.update(self.node_packages(rpm_prefix)) args = {"packages": sorted(set(packages))} - return self.execute_module("check_yum_update", args) + return self.execute_module_with_retries("check_yum_update", args) @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 1e9aecbe0..8464e8a5e 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): args = {"packages": []} - return self.execute_module("check_yum_update", args) + return self.execute_module_with_retries("check_yum_update", args) diff --git a/roles/openshift_health_checker/openshift_checks/package_version.py b/roles/openshift_health_checker/openshift_checks/package_version.py index 0b795b6c4..d4aec3ed8 100644 --- a/roles/openshift_health_checker/openshift_checks/package_version.py +++ b/roles/openshift_health_checker/openshift_checks/package_version.py @@ -76,7 +76,7 @@ class PackageVersion(NotContainerizedMixin, OpenShiftCheck): ], } - return self.execute_module("aos_version", args) + return self.execute_module_with_retries("aos_version", args) def get_required_ovs_version(self): """Return the correct Open vSwitch version(s) for the current OpenShift version.""" diff --git a/roles/openshift_health_checker/test/action_plugin_test.py b/roles/openshift_health_checker/test/action_plugin_test.py index c109ebd24..58864da21 100644 --- a/roles/openshift_health_checker/test/action_plugin_test.py +++ b/roles/openshift_health_checker/test/action_plugin_test.py @@ -110,11 +110,16 @@ def test_action_plugin_skip_non_active_checks(plugin, task_vars, monkeypatch): assert not skipped(result) -def test_action_plugin_skip_disabled_checks(plugin, task_vars, monkeypatch): +@pytest.mark.parametrize('to_disable', [ + 'fake_check', + ['fake_check', 'spam'], + '*,spam,eggs', +]) +def test_action_plugin_skip_disabled_checks(to_disable, plugin, task_vars, monkeypatch): checks = [fake_check('fake_check', is_active=True)] monkeypatch.setattr('openshift_checks.OpenShiftCheck.subclasses', classmethod(lambda cls: checks)) - task_vars['openshift_disable_check'] = 'fake_check' + task_vars['openshift_disable_check'] = to_disable result = plugin.run(tmp=None, task_vars=task_vars) assert result['checks']['fake_check'] == dict(skipped=True, skipped_reason="Disabled by user request") diff --git a/roles/openshift_health_checker/test/docker_image_availability_test.py b/roles/openshift_health_checker/test/docker_image_availability_test.py index 8d0a53df9..6a7c16c7e 100644 --- a/roles/openshift_health_checker/test/docker_image_availability_test.py +++ b/roles/openshift_health_checker/test/docker_image_availability_test.py @@ -3,6 +3,23 @@ import pytest from openshift_checks.docker_image_availability import DockerImageAvailability +@pytest.fixture() +def task_vars(): + return dict( + openshift=dict( + common=dict( + service_type='origin', + is_containerized=False, + is_atomic=False, + ), + docker=dict(), + ), + openshift_deployment_type='origin', + openshift_image_tag='', + group_names=['nodes', 'masters'], + ) + + @pytest.mark.parametrize('deployment_type, is_containerized, group_names, expect_active', [ ("origin", True, [], True), ("openshift-enterprise", True, [], True), @@ -15,12 +32,10 @@ from openshift_checks.docker_image_availability import DockerImageAvailability ("origin", False, ["nodes", "masters"], True), ("openshift-enterprise", False, ["etcd"], False), ]) -def test_is_active(deployment_type, is_containerized, group_names, expect_active): - task_vars = dict( - openshift=dict(common=dict(is_containerized=is_containerized)), - openshift_deployment_type=deployment_type, - group_names=group_names, - ) +def test_is_active(task_vars, deployment_type, is_containerized, group_names, expect_active): + task_vars['openshift_deployment_type'] = deployment_type + task_vars['openshift']['common']['is_containerized'] = is_containerized + task_vars['group_names'] = group_names assert DockerImageAvailability(None, task_vars).is_active() == expect_active @@ -30,10 +45,10 @@ def test_is_active(deployment_type, is_containerized, group_names, expect_active (True, False), (False, True), ]) -def test_all_images_available_locally(is_containerized, is_atomic): +def test_all_images_available_locally(task_vars, is_containerized, is_atomic): def execute_module(module_name, module_args, *_): if module_name == "yum": - return {"changed": True} + return {} assert module_name == "docker_image_facts" assert 'name' in module_args @@ -42,19 +57,9 @@ def test_all_images_available_locally(is_containerized, is_atomic): 'images': [module_args['name']], } - result = DockerImageAvailability(execute_module, task_vars=dict( - openshift=dict( - common=dict( - service_type='origin', - is_containerized=is_containerized, - is_atomic=is_atomic, - ), - docker=dict(additional_registries=["docker.io"]), - ), - openshift_deployment_type='origin', - openshift_image_tag='3.4', - group_names=['nodes', 'masters'], - )).run() + task_vars['openshift']['common']['is_containerized'] = is_containerized + task_vars['openshift']['common']['is_atomic'] = is_atomic + result = DockerImageAvailability(execute_module, task_vars).run() assert not result.get('failed', False) @@ -63,53 +68,36 @@ def test_all_images_available_locally(is_containerized, is_atomic): False, True, ]) -def test_all_images_available_remotely(available_locally): +def test_all_images_available_remotely(task_vars, available_locally): def execute_module(module_name, *_): if module_name == 'docker_image_facts': return {'images': [], 'failed': available_locally} - return {'changed': False} + return {} - result = DockerImageAvailability(execute_module, task_vars=dict( - openshift=dict( - common=dict( - service_type='origin', - is_containerized=False, - is_atomic=False, - ), - docker=dict(additional_registries=["docker.io", "registry.access.redhat.com"]), - ), - openshift_deployment_type='origin', - openshift_image_tag='v3.4', - group_names=['nodes', 'masters'], - )).run() + task_vars['openshift']['docker']['additional_registries'] = ["docker.io", "registry.access.redhat.com"] + task_vars['openshift_image_tag'] = 'v3.4' + check = DockerImageAvailability(execute_module, task_vars) + check._module_retry_interval = 0 + result = check.run() assert not result.get('failed', False) -def test_all_images_unavailable(): - def execute_module(module_name=None, *_): - if module_name == "command": - return { - 'failed': True, - } +def test_all_images_unavailable(task_vars): + def execute_module(module_name=None, *args): + if module_name == "wait_for": + return {} + elif module_name == "command": + return {'failed': True} - return { - 'changed': False, - } + return {} # docker_image_facts failure - actual = DockerImageAvailability(execute_module, task_vars=dict( - openshift=dict( - common=dict( - service_type='origin', - is_containerized=False, - is_atomic=False, - ), - docker=dict(additional_registries=["docker.io"]), - ), - openshift_deployment_type="openshift-enterprise", - openshift_image_tag='latest', - group_names=['nodes', 'masters'], - )).run() + task_vars['openshift']['docker']['additional_registries'] = ["docker.io"] + task_vars['openshift_deployment_type'] = "openshift-enterprise" + task_vars['openshift_image_tag'] = 'latest' + check = DockerImageAvailability(execute_module, task_vars) + check._module_retry_interval = 0 + actual = check.run() assert actual['failed'] assert "required Docker images are not available" in actual['msg'] @@ -125,62 +113,63 @@ def test_all_images_unavailable(): ["dependencies can be installed via `yum`"] ), ]) -def test_skopeo_update_failure(message, extra_words): +def test_skopeo_update_failure(task_vars, message, extra_words): def execute_module(module_name=None, *_): if module_name == "yum": return { "failed": True, "msg": message, - "changed": False, } - return {'changed': False} + return {} - actual = DockerImageAvailability(execute_module, task_vars=dict( - openshift=dict( - common=dict( - service_type='origin', - is_containerized=False, - is_atomic=False, - ), - docker=dict(additional_registries=["unknown.io"]), - ), - openshift_deployment_type="openshift-enterprise", - openshift_image_tag='', - group_names=['nodes', 'masters'], - )).run() + task_vars['openshift']['docker']['additional_registries'] = ["unknown.io"] + task_vars['openshift_deployment_type'] = "openshift-enterprise" + check = DockerImageAvailability(execute_module, task_vars) + check._module_retry_interval = 0 + actual = check.run() assert actual["failed"] for word in extra_words: assert word in actual["msg"] -@pytest.mark.parametrize("deployment_type,registries", [ - ("origin", ["unknown.io"]), - ("openshift-enterprise", ["registry.access.redhat.com"]), - ("openshift-enterprise", []), -]) -def test_registry_availability(deployment_type, registries): +@pytest.mark.parametrize( + "image, registries, connection_test_failed, skopeo_failed, " + "expect_success, expect_registries_reached", [ + ( + "spam/eggs:v1", ["test.reg"], + True, True, + False, + {"test.reg": False}, + ), + ( + "spam/eggs:v1", ["test.reg"], + False, True, + False, + {"test.reg": True}, + ), + ( + "eggs.reg/spam/eggs:v1", ["test.reg"], + False, False, + True, + {"eggs.reg": True}, + ), + ]) +def test_registry_availability(image, registries, connection_test_failed, skopeo_failed, + expect_success, expect_registries_reached): def execute_module(module_name=None, *_): - return { - 'changed': False, - } + if module_name == "wait_for": + return dict(msg="msg", failed=connection_test_failed) + elif module_name == "command": + return dict(msg="msg", failed=skopeo_failed) - actual = DockerImageAvailability(execute_module, task_vars=dict( - openshift=dict( - common=dict( - service_type='origin', - is_containerized=False, - is_atomic=False, - ), - docker=dict(additional_registries=registries), - ), - openshift_deployment_type=deployment_type, - openshift_image_tag='', - group_names=['nodes', 'masters'], - )).run() + check = DockerImageAvailability(execute_module, task_vars()) + check._module_retry_interval = 0 - assert not actual.get("failed", False) + available = check.is_available_skopeo_image(image, registries) + assert available == expect_success + assert expect_registries_reached == check.reachable_registries @pytest.mark.parametrize("deployment_type, is_containerized, groups, oreg_url, expected", [ @@ -257,7 +246,7 @@ def test_required_images(deployment_type, is_containerized, groups, oreg_url, ex openshift_image_tag='vtest', ) - assert expected == DockerImageAvailability("DUMMY", task_vars).required_images() + assert expected == DockerImageAvailability(task_vars=task_vars).required_images() def test_containerized_etcd(): @@ -271,4 +260,4 @@ def test_containerized_etcd(): group_names=['etcd'], ) expected = set(['registry.access.redhat.com/rhel7/etcd']) - assert expected == DockerImageAvailability("DUMMY", task_vars).required_images() + assert expected == DockerImageAvailability(task_vars=task_vars).required_images() diff --git a/roles/openshift_health_checker/test/package_availability_test.py b/roles/openshift_health_checker/test/package_availability_test.py index 1fe648b75..8aa87ca59 100644 --- a/roles/openshift_health_checker/test/package_availability_test.py +++ b/roles/openshift_health_checker/test/package_availability_test.py @@ -56,7 +56,7 @@ def test_package_availability(task_vars, must_have_packages, must_not_have_packa assert 'packages' in module_args assert set(module_args['packages']).issuperset(must_have_packages) assert not set(module_args['packages']).intersection(must_not_have_packages) - return return_value + return {'foo': return_value} result = PackageAvailability(execute_module, task_vars).run() - assert result is return_value + assert result['foo'] is return_value diff --git a/roles/openshift_health_checker/test/package_update_test.py b/roles/openshift_health_checker/test/package_update_test.py index 06489b0d7..7d9035a36 100644 --- a/roles/openshift_health_checker/test/package_update_test.py +++ b/roles/openshift_health_checker/test/package_update_test.py @@ -9,7 +9,7 @@ def test_package_update(): assert 'packages' in module_args # empty list of packages means "generic check if 'yum update' will work" assert module_args['packages'] == [] - return return_value + return {'foo': return_value} result = PackageUpdate(execute_module).run() - assert result is return_value + assert result['foo'] is return_value diff --git a/roles/openshift_health_checker/test/package_version_test.py b/roles/openshift_health_checker/test/package_version_test.py index e871f39f0..8564cd4db 100644 --- a/roles/openshift_health_checker/test/package_version_test.py +++ b/roles/openshift_health_checker/test/package_version_test.py @@ -52,7 +52,7 @@ def test_invalid_openshift_release_format(): ]) def test_package_version(openshift_release): - return_value = object() + return_value = {"foo": object()} def execute_module(module_name=None, module_args=None, tmp=None, task_vars=None, *_): assert module_name == 'aos_version' @@ -66,7 +66,7 @@ def test_package_version(openshift_release): check = PackageVersion(execute_module, task_vars_for(openshift_release, 'origin')) result = check.run() - assert result is return_value + assert result == return_value @pytest.mark.parametrize('deployment_type,openshift_release,expected_docker_version', [ @@ -79,7 +79,7 @@ def test_package_version(openshift_release): ]) def test_docker_package_version(deployment_type, openshift_release, expected_docker_version): - return_value = object() + return_value = {"foo": object()} def execute_module(module_name=None, module_args=None, *_): assert module_name == 'aos_version' @@ -93,7 +93,7 @@ def test_docker_package_version(deployment_type, openshift_release, expected_doc check = PackageVersion(execute_module, task_vars_for(openshift_release, deployment_type)) result = check.run() - assert result is return_value + assert result == return_value @pytest.mark.parametrize('group_names,is_containerized,is_active', [ diff --git a/roles/openshift_health_checker/test/zz_failure_summary_test.py b/roles/openshift_health_checker/test/zz_failure_summary_test.py index 0fc258133..69f27653c 100644 --- a/roles/openshift_health_checker/test/zz_failure_summary_test.py +++ b/roles/openshift_health_checker/test/zz_failure_summary_test.py @@ -65,6 +65,21 @@ import pytest }, ], ), + # if a failure contain an unhashable value, it will not be deduplicated + ( + [ + { + 'host': 'master1', + 'msg': {'unhashable': 'value'}, + }, + ], + [ + { + 'host': 'master1', + 'msg': {'unhashable': 'value'}, + }, + ], + ), ]) def test_deduplicate_failures(failures, deduplicated): assert deduplicate_failures(failures) == deduplicated diff --git a/roles/openshift_hosted/defaults/main.yml b/roles/openshift_hosted/defaults/main.yml index 08c1d849e..712a2a591 100644 --- a/roles/openshift_hosted/defaults/main.yml +++ b/roles/openshift_hosted/defaults/main.yml @@ -5,8 +5,8 @@ r_openshift_hosted_router_use_firewalld: "{{ os_firewall_use_firewalld | default r_openshift_hosted_registry_firewall_enabled: "{{ os_firewall_enabled | default(True) }}" r_openshift_hosted_registry_use_firewalld: "{{ os_firewall_use_firewalld | default(False) }}" -openshift_hosted_router_wait: "{{ not openshift_master_bootstrap_enabled | default(True) }}" -openshift_hosted_registry_wait: "{{ not openshift_master_bootstrap_enabled | default(True) }}" +openshift_hosted_router_wait: "{{ not (openshift_master_bootstrap_enabled | default(False)) }}" +openshift_hosted_registry_wait: "{{ not (openshift_master_bootstrap_enabled | default(False)) }}" registry_volume_claim: 'registry-claim' diff --git a/roles/openshift_hosted/tasks/registry/registry.yml b/roles/openshift_hosted/tasks/registry/registry.yml index d73c290ff..48f53aef8 100644 --- a/roles/openshift_hosted/tasks/registry/registry.yml +++ b/roles/openshift_hosted/tasks/registry/registry.yml @@ -137,7 +137,7 @@ edits: "{{ openshift_hosted_registry_edits }}" force: "{{ True|bool in openshift_hosted_registry_force }}" -- when: openshift_hosted_registry_wait +- when: openshift_hosted_registry_wait | bool block: - name: Ensure OpenShift registry correctly rolls out (best-effort today) command: | diff --git a/roles/openshift_hosted/tasks/router/router.yml b/roles/openshift_hosted/tasks/router/router.yml index 68ec7233e..2a42b5a7c 100644 --- a/roles/openshift_hosted/tasks/router/router.yml +++ b/roles/openshift_hosted/tasks/router/router.yml @@ -94,7 +94,7 @@ stats_port: "{{ item.stats_port }}" with_items: "{{ openshift_hosted_routers }}" -- when: openshift_hosted_router_wait +- when: openshift_hosted_router_wait | bool block: - name: Ensure OpenShift router correctly rolls out (best-effort today) command: | diff --git a/roles/openshift_master_facts/filter_plugins/openshift_master.py b/roles/openshift_master_facts/filter_plugins/openshift_master.py index e767772ce..5558f55cb 100644 --- a/roles/openshift_master_facts/filter_plugins/openshift_master.py +++ b/roles/openshift_master_facts/filter_plugins/openshift_master.py @@ -383,7 +383,7 @@ class OpenIDIdentityProvider(IdentityProviderOauthBase): if 'extraAuthorizeParameters' in self._idp: if 'include_granted_scopes' in self._idp['extraAuthorizeParameters']: val = ansible_bool(self._idp['extraAuthorizeParameters'].pop('include_granted_scopes')) - self._idp['extraAuthorizeParameters']['include_granted_scopes'] = val + self._idp['extraAuthorizeParameters']['include_granted_scopes'] = '"true"' if val else '"false"' def validate(self): ''' validate this idp instance ''' diff --git a/roles/openshift_metrics/tasks/pre_install.yaml b/roles/openshift_metrics/tasks/pre_install.yaml index 2e2013d40..d6756f9b9 100644 --- a/roles/openshift_metrics/tasks/pre_install.yaml +++ b/roles/openshift_metrics/tasks/pre_install.yaml @@ -10,7 +10,7 @@ is invalid, must be one of: emptydir, pv, dynamic when: - openshift_metrics_cassandra_storage_type not in openshift_metrics_cassandra_storage_types - - "not {{ openshift_metrics_heapster_standalone | bool }}" + - not (openshift_metrics_heapster_standalone | bool) - name: list existing secrets command: > @@ -48,6 +48,27 @@ def find_files(base_dir, exclude_dirs, include_dirs, file_regex): return found +def recursive_search(search_list, field): + """ + Takes a list with nested dicts, and searches all dicts for a key of the + field provided. If the items in the list are not dicts, the items are not + processed. + """ + fields_found = [] + + for item in search_list: + if isinstance(item, dict): + for key, value in item.items(): + if key == field: + fields_found.append(value) + elif isinstance(value, list): + results = recursive_search(value, field) + for result in results: + fields_found.append(result) + + return fields_found + + def find_entrypoint_playbooks(): '''find entry point playbooks as defined by openshift-ansible''' playbooks = set() @@ -248,37 +269,73 @@ class OpenShiftAnsibleSyntaxCheck(Command): ''' finalize_options ''' pass + def deprecate_jinja2_in_when(self, yaml_contents, yaml_file): + ''' Check for Jinja2 templating delimiters in when conditions ''' + test_result = False + failed_items = [] + + search_results = recursive_search(yaml_contents, 'when') + for item in search_results: + if isinstance(item, str): + if '{{' in item or '{%' in item: + failed_items.append(item) + else: + for sub_item in item: + if '{{' in sub_item or '{%' in sub_item: + failed_items.append(sub_item) + + if len(failed_items) > 0: + print('{}Error: Usage of Jinja2 templating delimiters in when ' + 'conditions is deprecated in Ansible 2.3.\n' + ' File: {}'.format(self.FAIL, yaml_file)) + for item in failed_items: + print(' Found: "{}"'.format(item)) + print(self.ENDC) + test_result = True + + return test_result + + def deprecate_include(self, yaml_contents, yaml_file): + ''' Check for usage of include directive ''' + test_result = False + + search_results = recursive_search(yaml_contents, 'include') + + if len(search_results) > 0: + print('{}Error: The `include` directive is deprecated in Ansible 2.4.\n' + 'https://github.com/ansible/ansible/blob/devel/CHANGELOG.md\n' + ' File: {}'.format(self.FAIL, yaml_file)) + for item in search_results: + print(' Found: "include: {}"'.format(item)) + print(self.ENDC) + test_result = True + + return test_result + def run(self): ''' run command ''' has_errors = False print('Ansible Deprecation Checks') - exclude_dirs = ['adhoc', 'files', 'meta', 'test', 'tests', 'vars', '.tox'] + exclude_dirs = ['adhoc', 'files', 'meta', 'test', 'tests', 'vars', 'defaults', '.tox'] for yaml_file in find_files( os.getcwd(), exclude_dirs, None, r'\.ya?ml$'): with open(yaml_file, 'r') as contents: - for task in yaml.safe_load(contents) or {}: - if not isinstance(task, dict): - # Skip yaml files which are not a dictionary of tasks - continue - if 'when' in task: - if '{{' in task['when'] or '{%' in task['when']: - print('{}Error: Usage of Jinja2 templating delimiters ' - 'in when conditions is deprecated in Ansible 2.3.\n' - ' File: {}\n' - ' Found: "{}"{}'.format( - self.FAIL, yaml_file, - task['when'], self.ENDC)) - has_errors = True - # TODO (rteague): This test will be enabled once we move to Ansible 2.4 - # if 'include' in task: - # print('{}Error: The `include` directive is deprecated in Ansible 2.4.\n' - # 'https://github.com/ansible/ansible/blob/devel/CHANGELOG.md\n' - # ' File: {}\n' - # ' Found: "include: {}"{}'.format( - # self.FAIL, yaml_file, task['include'], self.ENDC)) - # has_errors = True + yaml_contents = yaml.safe_load(contents) + if not isinstance(yaml_contents, list): + continue + + # Check for Jinja2 templating delimiters in when conditions + result = self.deprecate_jinja2_in_when(yaml_contents, yaml_file) + has_errors = result or has_errors + + # TODO (rteague): This test will be enabled once we move to Ansible 2.4 + # result = self.deprecate_include(yaml_contents, yaml_file) + # has_errors = result or has_errors + + if not has_errors: + print('...PASSED') print('Ansible Playbook Entry Point Syntax Checks') for playbook in find_entrypoint_playbooks(): |