diff options
33 files changed, 720 insertions, 74 deletions
| diff --git a/filter_plugins/openshift_master.py b/filter_plugins/openshift_master.py index ec09b09f6..437f4c400 100644 --- a/filter_plugins/openshift_master.py +++ b/filter_plugins/openshift_master.py @@ -161,7 +161,7 @@ class LDAPPasswordIdentityProvider(IdentityProviderBase):              AnsibleFilterError:      """      def __init__(self, api_version, idp): -        IdentityProviderBase.__init__(self, api_version, idp) +        super(self.__class__, self).__init__(api_version, idp)          self._allow_additional = False          self._required += [['attributes'], ['url'], ['insecure']]          self._optional += [['ca'], @@ -176,7 +176,6 @@ class LDAPPasswordIdentityProvider(IdentityProviderBase):      def validate(self):          ''' validate this idp instance ''' -        IdentityProviderBase.validate(self)          if not isinstance(self.provider['attributes'], dict):              raise errors.AnsibleFilterError("|failed attributes for provider "                                              "{0} must be a dictionary".format(self.__class__.__name__)) @@ -206,7 +205,7 @@ class KeystonePasswordIdentityProvider(IdentityProviderBase):              AnsibleFilterError:      """      def __init__(self, api_version, idp): -        IdentityProviderBase.__init__(self, api_version, idp) +        super(self.__class__, self).__init__(api_version, idp)          self._allow_additional = False          self._required += [['url'], ['domainName', 'domain_name']]          self._optional += [['ca'], ['certFile', 'cert_file'], ['keyFile', 'key_file']] @@ -225,7 +224,7 @@ class RequestHeaderIdentityProvider(IdentityProviderBase):              AnsibleFilterError:      """      def __init__(self, api_version, idp): -        IdentityProviderBase.__init__(self, api_version, idp) +        super(self.__class__, self).__init__(api_version, idp)          self._allow_additional = False          self._required += [['headers']]          self._optional += [['challengeURL', 'challenge_url'], @@ -238,7 +237,6 @@ class RequestHeaderIdentityProvider(IdentityProviderBase):      def validate(self):          ''' validate this idp instance ''' -        IdentityProviderBase.validate(self)          if not isinstance(self.provider['headers'], list):              raise errors.AnsibleFilterError("|failed headers for provider {0} "                                              "must be a list".format(self.__class__.__name__)) @@ -257,7 +255,7 @@ class AllowAllPasswordIdentityProvider(IdentityProviderBase):              AnsibleFilterError:      """      def __init__(self, api_version, idp): -        IdentityProviderBase.__init__(self, api_version, idp) +        super(self.__class__, self).__init__(api_version, idp)          self._allow_additional = False @@ -274,7 +272,7 @@ class DenyAllPasswordIdentityProvider(IdentityProviderBase):              AnsibleFilterError:      """      def __init__(self, api_version, idp): -        IdentityProviderBase.__init__(self, api_version, idp) +        super(self.__class__, self).__init__(api_version, idp)          self._allow_additional = False @@ -291,7 +289,7 @@ class HTPasswdPasswordIdentityProvider(IdentityProviderBase):              AnsibleFilterError:      """      def __init__(self, api_version, idp): -        IdentityProviderBase.__init__(self, api_version, idp) +        super(self.__class__, self).__init__(api_version, idp)          self._allow_additional = False          self._required += [['file', 'filename', 'fileName', 'file_name']] @@ -316,7 +314,7 @@ class BasicAuthPasswordIdentityProvider(IdentityProviderBase):              AnsibleFilterError:      """      def __init__(self, api_version, idp): -        IdentityProviderBase.__init__(self, api_version, idp) +        super(self.__class__, self).__init__(api_version, idp)          self._allow_additional = False          self._required += [['url']]          self._optional += [['ca'], ['certFile', 'cert_file'], ['keyFile', 'key_file']] @@ -335,13 +333,12 @@ class IdentityProviderOauthBase(IdentityProviderBase):              AnsibleFilterError:      """      def __init__(self, api_version, idp): -        IdentityProviderBase.__init__(self, api_version, idp) +        super(self.__class__, self).__init__(api_version, idp)          self._allow_additional = False          self._required += [['clientID', 'client_id'], ['clientSecret', 'client_secret']]      def validate(self):          ''' validate this idp instance ''' -        IdentityProviderBase.validate(self)          if self.challenge:              raise errors.AnsibleFilterError("|failed provider {0} does not "                                              "allow challenge authentication".format(self.__class__.__name__)) diff --git a/library/modify_yaml.py b/library/modify_yaml.py index d8d22d5ea..8706e80c2 100755 --- a/library/modify_yaml.py +++ b/library/modify_yaml.py @@ -6,6 +6,11 @@  import yaml +# ignore pylint errors related to the module_utils import +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import +from ansible.module_utils.basic import *  # noqa: F402,F403 + +  DOCUMENTATION = '''  ---  module: modify_yaml @@ -21,8 +26,18 @@ EXAMPLES = '''  ''' -# pylint: disable=missing-docstring  def set_key(yaml_data, yaml_key, yaml_value): +    ''' Updates a parsed yaml structure setting a key to a value. + +        :param yaml_data: yaml structure to modify. +        :type yaml_data: dict +        :param yaml_key: Key to modify. +        :type yaml_key: mixed +        :param yaml_value: Value use for yaml_key. +        :type yaml_value: mixed +        :returns: Changes to the yaml_data structure +        :rtype: dict(tuple()) +    '''      changes = []      ptr = yaml_data      final_key = yaml_key.split('.')[-1] @@ -75,6 +90,7 @@ def main():      # pylint: disable=missing-docstring, unused-argument      def none_representer(dumper, data):          return yaml.ScalarNode(tag=u'tag:yaml.org,2002:null', value=u'') +      yaml.add_representer(type(None), none_representer)      try: @@ -95,14 +111,9 @@ def main():      # ignore broad-except error to avoid stack trace to ansible user      # pylint: disable=broad-except -    except Exception as e: -        return module.fail_json(msg=str(e)) - +    except Exception as error: +        return module.fail_json(msg=str(error)) -# ignore pylint errors related to the module_utils import -# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, wrong-import-position -# import module snippets -from ansible.module_utils.basic import *  # noqa: F402,F403  if __name__ == '__main__':      main() diff --git a/playbooks/byo/openshift-preflight/README.md b/playbooks/byo/openshift-preflight/README.md new file mode 100644 index 000000000..b50292eac --- /dev/null +++ b/playbooks/byo/openshift-preflight/README.md @@ -0,0 +1,43 @@ +# OpenShift preflight checks + +Here we provide an Ansible playbook for detecting potential roadblocks prior to +an install or upgrade. + +Ansible's default operation mode is to fail fast, on the first error. However, +when performing checks, it is useful to gather as much information about +problems as possible in a single run. + +The `check.yml` playbook runs a battery of checks against the inventory hosts +and tells Ansible to ignore intermediate errors, thus giving a more complete +diagnostic of the state of each host. Still, if any check failed, the playbook +run will be marked as having failed. + +To facilitate understanding the problems that were encountered, we provide a +custom callback plugin to summarize execution errors at the end of a playbook +run. + +--- + +*Note that currently the `check.yml` playbook is only useful for RPM-based +installations. Containerized installs are excluded from checks for now, but +might be included in the future if there is demand for that.* + +--- + +## Running + +With an installation of Ansible 2.2 or greater, run the playbook directly +against your inventory file. Here is the step-by-step: + +1. If you haven't done it yet, clone this repository: + +    ```console +    $ git clone https://github.com/openshift/openshift-ansible +    $ cd openshift-ansible +    ``` + +2. Run the playbook: + +    ```console +    $ ansible-playbook -i <inventory file> playbooks/byo/openshift-preflight/check.yml +    ``` diff --git a/playbooks/byo/openshift-preflight/check.yml b/playbooks/byo/openshift-preflight/check.yml new file mode 100644 index 000000000..32673d01d --- /dev/null +++ b/playbooks/byo/openshift-preflight/check.yml @@ -0,0 +1,31 @@ +--- +- hosts: OSEv3 +  roles: +    - openshift_preflight/init + +- hosts: OSEv3 +  name: checks that apply to all hosts +  gather_facts: no +  ignore_errors: yes +  roles: +    - openshift_preflight/common + +- hosts: masters +  name: checks that apply to masters +  gather_facts: no +  ignore_errors: yes +  roles: +    - openshift_preflight/masters + +- hosts: nodes +  name: checks that apply to nodes +  gather_facts: no +  ignore_errors: yes +  roles: +    - openshift_preflight/nodes + +- hosts: OSEv3 +  name: verify check results +  gather_facts: no +  roles: +    - openshift_preflight/verify_status diff --git a/playbooks/common/openshift-cluster/upgrades/etcd/backup.yml b/playbooks/common/openshift-cluster/upgrades/etcd/backup.yml index be42f005f..d0eadf1fc 100644 --- a/playbooks/common/openshift-cluster/upgrades/etcd/backup.yml +++ b/playbooks/common/openshift-cluster/upgrades/etcd/backup.yml @@ -4,6 +4,7 @@    vars:      embedded_etcd: "{{ groups.oo_etcd_to_config | default([]) | length == 0 }}"      timestamp: "{{ lookup('pipe', 'date +%Y%m%d%H%M%S') }}" +    etcdctl_command: "{{ 'etcdctl' if not openshift.common.is_containerized or embedded_etcd else 'docker exec etcd_container etcdctl' }}"    roles:    - openshift_facts    tasks: @@ -67,7 +68,7 @@    - name: Generate etcd backup      command: > -      etcdctl backup --data-dir={{ openshift.etcd.etcd_data_dir }} +      {{ etcdctl_command }} backup --data-dir={{ openshift.etcd.etcd_data_dir }}        --backup-dir={{ openshift.common.data_dir }}/etcd-backup-{{ backup_tag | default('') }}{{ timestamp }}    - set_fact: diff --git a/playbooks/common/openshift-master/config.yml b/playbooks/common/openshift-master/config.yml index 21f3c80a1..39d64a126 100644 --- a/playbooks/common/openshift-master/config.yml +++ b/playbooks/common/openshift-master/config.yml @@ -74,11 +74,6 @@          public_console_url: "{{ openshift_master_public_console_url | default(None) }}"          ha: "{{ openshift_master_ha | default(groups.oo_masters | length > 1) }}"          master_count: "{{ openshift_master_count | default(groups.oo_masters | length) }}" -  - openshift_facts: -      role: hosted -      openshift_env: -        openshift_hosted_registry_storage_kind: 'nfs' -    when: openshift_hosted_registry_storage_kind is not defined and groups.oo_nfs_to_config is defined and groups.oo_nfs_to_config | length > 0  - name: Create temp directory for syncing certs    hosts: localhost diff --git a/roles/openshift_builddefaults/tasks/main.yml b/roles/openshift_builddefaults/tasks/main.yml index 1f44b29b9..e0b51eee0 100644 --- a/roles/openshift_builddefaults/tasks/main.yml +++ b/roles/openshift_builddefaults/tasks/main.yml @@ -15,6 +15,7 @@        no_proxy: "{{ openshift_builddefaults_no_proxy | default(None) }}"        git_http_proxy: "{{ openshift_builddefaults_git_http_proxy | default(None) }}"        git_https_proxy: "{{ openshift_builddefaults_git_https_proxy | default(None) }}" +      git_no_proxy: "{{ openshift_builddefaults_git_no_proxy | default(None) }}"  - name: Set builddefaults config structure    openshift_facts: diff --git a/roles/openshift_builddefaults/vars/main.yml b/roles/openshift_builddefaults/vars/main.yml index bcdf68112..c9ec3b82f 100644 --- a/roles/openshift_builddefaults/vars/main.yml +++ b/roles/openshift_builddefaults/vars/main.yml @@ -6,16 +6,28 @@ builddefaults_yaml:        kind: BuildDefaultsConfig        gitHTTPProxy: "{{ openshift.builddefaults.git_http_proxy | default('', true) }}"        gitHTTPSProxy: "{{ openshift.builddefaults.git_https_proxy | default('', true) }}" +      gitNoProxy: "{{ openshift.builddefaults.git_no_proxy | default('', true) }}"        env:        - name: HTTP_PROXY          value: "{{ openshift.builddefaults.http_proxy | default('', true) }}"        - name: HTTPS_PROXY          value: "{{ openshift.builddefaults.https_proxy | default('', true) }}"        - name: NO_PROXY -        value: "{{ openshift.builddefaults.no_proxy | default('', true) | join(',') }}" +        value: "{{ openshift.builddefaults.no_proxy | default('', true) }}"        - name: http_proxy          value: "{{ openshift.builddefaults.http_proxy | default('', true) }}"        - name: https_proxy          value: "{{ openshift.builddefaults.https_proxy | default('', true) }}"        - name: no_proxy -        value: "{{ openshift.builddefaults.no_proxy | default('', true) | join(',') }}" +        value: "{{ openshift.builddefaults.no_proxy | default('', true) }}" +      imageLabels: "{{ openshift_builddefaults_image_labels | default(None) }}" +      nodeSelector: "{{ openshift_builddefaults_nodeselectors | default(None) }}" +      annotations: "{{ openshift_builddefaults_annotations | default(None) }}" +      #resources: "{{ openshift.builddefaults.resources | default(None) }}" +      resources: +        requests: +          cpu: "{{ openshift_builddefaults_resources_requests_cpu | default(None) }}" +          memory: "{{ openshift_builddefaults_resources_requests_memory | default(None) }}" +        limits: +          cpu: "{{ openshift_builddefaults_resources_limits_cpu | default(None) }}" +          memory: "{{ openshift_builddefaults_resources_limits_memory | default(None) }}" diff --git a/roles/openshift_buildoverrides/meta/main.yml b/roles/openshift_buildoverrides/meta/main.yml new file mode 100644 index 000000000..e9d2e8712 --- /dev/null +++ b/roles/openshift_buildoverrides/meta/main.yml @@ -0,0 +1,15 @@ +--- +galaxy_info: +  author: Ben Parees +  description: OpenShift Build Overrides configuration +  company: Red Hat, Inc. +  license: Apache License, Version 2.0 +  min_ansible_version: 1.9 +  platforms: +  - name: EL +    versions: +    - 7 +  categories: +  - cloud +dependencies: +- role: openshift_facts diff --git a/roles/openshift_buildoverrides/tasks/main.yml b/roles/openshift_buildoverrides/tasks/main.yml new file mode 100644 index 000000000..82fce1c5b --- /dev/null +++ b/roles/openshift_buildoverrides/tasks/main.yml @@ -0,0 +1,15 @@ +--- +#- name: Set buildoverrides +#  openshift_facts: +#    role: buildoverrides +#    local_facts: +#      force_pull: "{{ openshift_buildoverrides_force_pull | default(None) }}" +#      image_labels: "{{ openshift_buildoverrides_image_labels | default(None) }}" +#      nodeselectors: "{{ openshift_buildoverrides_nodeselectors | default(None) }}" +#      annotations: "{{ openshift_buildoverrides_annotations | default(None) }}" + +- name: Set buildoverrides config structure +  openshift_facts: +    role: buildoverrides +    local_facts: +      config: "{{ openshift_buildoverrides_json | default(buildoverrides_yaml) }}" diff --git a/roles/openshift_buildoverrides/vars/main.yml b/roles/openshift_buildoverrides/vars/main.yml new file mode 100644 index 000000000..f0f9c255b --- /dev/null +++ b/roles/openshift_buildoverrides/vars/main.yml @@ -0,0 +1,10 @@ +--- +buildoverrides_yaml: +  BuildOverrides: +    configuration: +      apiVersion: v1 +      kind: BuildOverridesConfig +      forcePull: "{{ openshift_buildoverrides_force_pull | default('', true) }}" +      imageLabels: "{{ openshift_buildoverrides_image_labels | default(None) }}" +      nodeSelector: "{{ openshift_buildoverrides_nodeselectors | default(None) }}" +      annotations: "{{ openshift_buildoverrides_annotations | default(None) }}" diff --git a/roles/openshift_facts/library/openshift_facts.py b/roles/openshift_facts/library/openshift_facts.py index 10e30f1c4..10121f82a 100755 --- a/roles/openshift_facts/library/openshift_facts.py +++ b/roles/openshift_facts/library/openshift_facts.py @@ -1246,10 +1246,10 @@ def build_api_server_args(facts):  def is_service_running(service):      """ Queries systemd through dbus to see if the service is running """      service_running = False -    bus = SystemBus() -    systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') -    manager = Interface(systemd, dbus_interface='org.freedesktop.systemd1.Manager')      try: +        bus = SystemBus() +        systemd = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1') +        manager = Interface(systemd, dbus_interface='org.freedesktop.systemd1.Manager')          service_unit = service if service.endswith('.service') else manager.GetUnit('{0}.service'.format(service))          service_proxy = bus.get_object('org.freedesktop.systemd1', str(service_unit))          service_properties = Interface(service_proxy, dbus_interface='org.freedesktop.DBus.Properties') @@ -1258,6 +1258,8 @@ def is_service_running(service):          if service_load_state == 'loaded' and service_active_state == 'active':              service_running = True      except DBusException: +        # TODO: do not swallow exception, as it may be hiding useful debugging +        # information.          pass      return service_running @@ -1463,7 +1465,9 @@ def merge_facts(orig, new, additive_facts_to_overwrite, protected_facts_to_overw      # here, just completely overwrite with the new if they are present there.      inventory_json_facts = ['admission_plugin_config',                              'kube_admission_plugin_config', -                            'image_policy_config'] +                            'image_policy_config', +                            "builddefaults", +                            "buildoverrides"]      facts = dict()      for key, value in iteritems(orig): @@ -1623,11 +1627,7 @@ def safe_get_bool(fact):  def set_proxy_facts(facts): -    """ Set global proxy facts and promote defaults from http_proxy, https_proxy, -        no_proxy to the more specific builddefaults and builddefaults_git vars. -           1. http_proxy, https_proxy, no_proxy -           2. builddefaults_* -           3. builddefaults_git_* +    """ Set global proxy facts          Args:              facts(dict): existing facts @@ -1649,6 +1649,21 @@ def set_proxy_facts(facts):              common['no_proxy'].append(common['hostname'])              common['no_proxy'] = sort_unique(common['no_proxy'])          facts['common'] = common +    return facts + + +def set_builddefaults_facts(facts): +    """ Set build defaults including setting proxy values from http_proxy, https_proxy, +        no_proxy to the more specific builddefaults and builddefaults_git vars. +           1. http_proxy, https_proxy, no_proxy +           2. builddefaults_* +           3. builddefaults_git_* + +        Args: +            facts(dict): existing facts +        Returns: +            facts(dict): Updated facts with missing values +    """      if 'builddefaults' in facts:          builddefaults = facts['builddefaults'] @@ -1658,24 +1673,42 @@ def set_proxy_facts(facts):              builddefaults['http_proxy'] = common['http_proxy']          if 'https_proxy' not in builddefaults and 'https_proxy' in common:              builddefaults['https_proxy'] = common['https_proxy'] -        # make no_proxy into a list if it's not -        if 'no_proxy' in builddefaults and isinstance(builddefaults['no_proxy'], string_types): -            builddefaults['no_proxy'] = builddefaults['no_proxy'].split(",")          if 'no_proxy' not in builddefaults and 'no_proxy' in common:              builddefaults['no_proxy'] = common['no_proxy'] + +        # Create git specific facts from generic values, if git specific values are +        # not defined.          if 'git_http_proxy' not in builddefaults and 'http_proxy' in builddefaults:              builddefaults['git_http_proxy'] = builddefaults['http_proxy']          if 'git_https_proxy' not in builddefaults and 'https_proxy' in builddefaults:              builddefaults['git_https_proxy'] = builddefaults['https_proxy'] -        # If we're actually defining a proxy config then create admission_plugin_config -        # if it doesn't exist, then merge builddefaults[config] structure -        # into admission_plugin_config -        if 'config' in builddefaults and ('http_proxy' in builddefaults or -                                          'https_proxy' in builddefaults): +        if 'git_no_proxy' not in builddefaults and 'no_proxy' in builddefaults: +            builddefaults['git_no_proxy'] = builddefaults['no_proxy'] +        # If we're actually defining a builddefaults config then create admission_plugin_config +        # then merge builddefaults[config] structure into admission_plugin_config +        if 'config' in builddefaults:              if 'admission_plugin_config' not in facts['master']:                  facts['master']['admission_plugin_config'] = dict()              facts['master']['admission_plugin_config'].update(builddefaults['config']) -        facts['builddefaults'] = builddefaults +    return facts + + +def set_buildoverrides_facts(facts): +    """ Set build overrides + +        Args: +            facts(dict): existing facts +        Returns: +            facts(dict): Updated facts with missing values +    """ +    if 'buildoverrides' in facts: +        buildoverrides = facts['buildoverrides'] +        # If we're actually defining a buildoverrides config then create admission_plugin_config +        # then merge buildoverrides[config] structure into admission_plugin_config +        if 'config' in buildoverrides: +            if 'admission_plugin_config' not in facts['master']: +                facts['master']['admission_plugin_config'] = dict() +            facts['master']['admission_plugin_config'].update(buildoverrides['config'])      return facts @@ -1814,6 +1847,7 @@ class OpenShiftFacts(object):              OpenShiftFactsUnsupportedRoleError:      """      known_roles = ['builddefaults', +                   'buildoverrides',                     'clock',                     'cloudprovider',                     'common', @@ -1918,6 +1952,8 @@ class OpenShiftFacts(object):          facts = set_aggregate_facts(facts)          facts = set_etcd_facts_if_unset(facts)          facts = set_proxy_facts(facts) +        facts = set_builddefaults_facts(facts) +        facts = set_buildoverrides_facts(facts)          if not safe_get_bool(facts['common']['is_containerized']):              facts = set_installed_variant_rpm_facts(facts)          facts = set_nodename(facts) diff --git a/roles/openshift_master/meta/main.yml b/roles/openshift_master/meta/main.yml index 3a595b2d1..56af0cf36 100644 --- a/roles/openshift_master/meta/main.yml +++ b/roles/openshift_master/meta/main.yml @@ -23,6 +23,7 @@ dependencies:  - role: openshift_clock  - role: openshift_cloud_provider  - role: openshift_builddefaults +- role: openshift_buildoverrides  - role: os_firewall    os_firewall_allow:    - service: api server https diff --git a/roles/openshift_master/templates/master.yaml.v1.j2 b/roles/openshift_master/templates/master.yaml.v1.j2 index 81546c829..fcb8125e9 100644 --- a/roles/openshift_master/templates/master.yaml.v1.j2 +++ b/roles/openshift_master/templates/master.yaml.v1.j2 @@ -123,7 +123,7 @@ kubernetesMasterConfig:      keyFile: master.proxy-client.key    schedulerArguments: {{ openshift_master_scheduler_args | default(None) | to_padded_yaml( level=3 ) }}    schedulerConfigFile: {{ openshift_master_scheduler_conf }} -  servicesNodePortRange: "" +  servicesNodePortRange: "{{ openshift_node_port_range | default("") }}"    servicesSubnet: {{ openshift.common.portal_net }}    staticNodeNames: {{ openshift_node_ips | default([], true) }}  {% endif %} diff --git a/roles/openshift_master_facts/vars/main.yml b/roles/openshift_master_facts/vars/main.yml index fa745eb66..bf6d2402d 100644 --- a/roles/openshift_master_facts/vars/main.yml +++ b/roles/openshift_master_facts/vars/main.yml @@ -2,24 +2,3 @@  openshift_master_config_dir: "{{ openshift.common.config_base }}/master"  openshift_master_config_file: "{{ openshift_master_config_dir }}/master-config.yaml"  openshift_master_scheduler_conf: "{{ openshift_master_config_dir }}/scheduler.json" - -builddefaults_yaml: -  BuildDefaults: -    configuration: -      apiVersion: v1 -      kind: BuildDefaultsConfig -      gitHTTPProxy: "{{ openshift.master.builddefaults_git_http_proxy | default(omit, true) }}" -      gitHTTPSProxy: "{{ openshift.master.builddefaults_git_https_proxy | default(omit, true) }}" -      env: -      - name: HTTP_PROXY -        value: "{{ openshift.master.builddefaults_http_proxy | default(omit, true) }}" -      - name: HTTPS_PROXY -        value: "{{ openshift.master.builddefaults_https_proxy | default(omit, true) }}" -      - name: NO_PROXY -        value: "{{ openshift.master.builddefaults_no_proxy | default(omit, true) | join(',') }}" -      - name: http_proxy -        value: "{{ openshift.master.builddefaults_http_proxy | default(omit, true) }}" -      - name: https_proxy -        value: "{{ openshift.master.builddefaults_https_proxy | default(omit, true) }}" -      - name: no_proxy -        value: "{{ openshift.master.builddefaults_no_proxy | default(omit, true) | join(',') }}" diff --git a/roles/openshift_node/meta/main.yml b/roles/openshift_node/meta/main.yml index 56dee2958..91f118191 100644 --- a/roles/openshift_node/meta/main.yml +++ b/roles/openshift_node/meta/main.yml @@ -31,6 +31,15 @@ dependencies:      port: 10255/tcp    - service: Openshift kubelet ReadOnlyPort udp      port: 10255/udp +- role: os_firewall +  os_firewall_allow:    - service: OpenShift OVS sdn      port: 4789/udp -    when: openshift.node.use_openshift_sdn | bool +  when: openshift.common.use_openshift_sdn | bool +- role: os_firewall +  os_firewall_allow: +  - service: Kubernetes service NodePort TCP +    port: "{{ openshift_node_port_range | default('') }}/tcp" +  - service: Kubernetes service NodePort UDP +    port: "{{ openshift_node_port_range | default('') }}/udp" +  when: openshift_node_port_range is defined diff --git a/roles/openshift_preflight/README.md b/roles/openshift_preflight/README.md new file mode 100644 index 000000000..b6d3542d3 --- /dev/null +++ b/roles/openshift_preflight/README.md @@ -0,0 +1,52 @@ +OpenShift Preflight Checks +========================== + +This role detects common problems prior to installing OpenShift. + +Requirements +------------ + +* Ansible 2.2+ + +Role Variables +-------------- + +None + +Dependencies +------------ + +None + +Example Playbook +---------------- + +```yaml +--- +- hosts: OSEv3 +  roles: +    - openshift_preflight/init + +- hosts: OSEv3 +  name: checks that apply to all hosts +  gather_facts: no +  ignore_errors: yes +  roles: +    - openshift_preflight/common + +- hosts: OSEv3 +  name: verify check results +  gather_facts: no +  roles: +    - openshift_preflight/verify_status +``` + +License +------- + +Apache License Version 2.0 + +Author Information +------------------ + +Customer Success team (dev@lists.openshift.redhat.com) diff --git a/roles/openshift_preflight/base/library/aos_version.py b/roles/openshift_preflight/base/library/aos_version.py new file mode 100755 index 000000000..f7fcb6da5 --- /dev/null +++ b/roles/openshift_preflight/base/library/aos_version.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# vim: expandtab:tabstop=4:shiftwidth=4 +''' +An ansible module for determining if more than one minor version +of any atomic-openshift package is available, which would indicate +that multiple repos are enabled for different versions of the same +thing which may cause problems. + +Also, determine if the version requested is available down to the +precision requested. +''' + +# import os +# import sys +import yum  # pylint: disable=import-error +from ansible.module_utils.basic import AnsibleModule + + +def main():  # pylint: disable=missing-docstring +    module = AnsibleModule( +        argument_spec=dict( +            version=dict(required=True) +        ), +        supports_check_mode=True +    ) + +    # NOTE(rhcarvalho): sosiouxme added _unmute, but I couldn't find a case yet +    # for when it is actually necessary. Leaving it commented out for now, +    # though this comment and the commented out code related to _unmute should +    # be deleted later if not proven necessary. + +    # sys.stdout = os.devnull  # mute yum so it doesn't break our output +    # sys.stderr = os.devnull  # mute yum so it doesn't break our output + +    # def _unmute():  # pylint: disable=missing-docstring +    #     sys.stdout = sys.__stdout__ + +    def bail(error):  # pylint: disable=missing-docstring +        # _unmute() +        module.fail_json(msg=error) + +    yb = yum.YumBase()  # pylint: disable=invalid-name + +    # search for package versions available for aos pkgs +    expected_pkgs = [ +        'atomic-openshift', +        'atomic-openshift-master', +        'atomic-openshift-node', +    ] +    try: +        pkgs = yb.pkgSack.returnPackages(patterns=expected_pkgs) +    except yum.Errors.PackageSackError as e:  # pylint: disable=invalid-name +        # you only hit this if *none* of the packages are available +        bail('Unable to find any atomic-openshift packages. \nCheck your subscription and repo settings. \n%s' % e) + +    # determine what level of precision we're expecting for the version +    expected_version = module.params['version'] +    if expected_version.startswith('v'):  # v3.3 => 3.3 +        expected_version = expected_version[1:] +    num_dots = expected_version.count('.') + +    pkgs_by_name_version = {} +    pkgs_precise_version_found = {} +    for pkg in pkgs: +        # get expected version precision +        match_version = '.'.join(pkg.version.split('.')[:num_dots + 1]) +        if match_version == expected_version: +            pkgs_precise_version_found[pkg.name] = True +        # get x.y version precision +        minor_version = '.'.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_version] = True + +    # see if any packages couldn't be found at requested version +    # see if any packages are available in more than one minor version +    not_found = [] +    multi_found = [] +    for name in expected_pkgs: +        if name not in pkgs_precise_version_found: +            not_found.append(name) +        if name in pkgs_by_name_version and len(pkgs_by_name_version[name]) > 1: +            multi_found.append(name) +    if not_found: +        msg = 'Not all of the required packages are available at requested version %s:\n' % expected_version +        for name in not_found: +            msg += '  %s\n' % name +        bail(msg + 'Please check your subscriptions and enabled repositories.') +    if multi_found: +        msg = 'Multiple minor versions of these packages are available\n' +        for name in multi_found: +            msg += '  %s\n' % name +        bail(msg + "There should only be one OpenShift version's repository enabled at a time.") + +    # _unmute() +    module.exit_json(changed=False) + + +if __name__ == '__main__': +    main() diff --git a/roles/openshift_preflight/base/library/check_yum_update.py b/roles/openshift_preflight/base/library/check_yum_update.py new file mode 100755 index 000000000..296ebd44f --- /dev/null +++ b/roles/openshift_preflight/base/library/check_yum_update.py @@ -0,0 +1,116 @@ +#!/usr/bin/python +# vim: expandtab:tabstop=4:shiftwidth=4 +''' +Ansible module to test whether a yum update or install will succeed, +without actually performing it or running yum. +parameters: +  packages: (optional) A list of package names to install or update. +            If omitted, all installed RPMs are considered for updates. +''' + +# import os +import sys +import yum  # pylint: disable=import-error +from ansible.module_utils.basic import AnsibleModule + + +def main():  # pylint: disable=missing-docstring,too-many-branches +    module = AnsibleModule( +        argument_spec=dict( +            packages=dict(type='list', default=[]) +        ), +        supports_check_mode=True +    ) + +    # NOTE(rhcarvalho): sosiouxme added _unmute, but I couldn't find a case yet +    # for when it is actually necessary. Leaving it commented out for now, +    # though this comment and the commented out code related to _unmute should +    # be deleted later if not proven necessary. + +    # sys.stdout = os.devnull  # mute yum so it doesn't break our output + +    # def _unmute():  # pylint: disable=missing-docstring +    #     sys.stdout = sys.__stdout__ + +    def bail(error):  # pylint: disable=missing-docstring +        # _unmute() +        module.fail_json(msg=error) + +    yb = yum.YumBase()  # pylint: disable=invalid-name +    # determine if the existing yum configuration is valid +    try: +        yb.repos.populateSack(mdtype='metadata', cacheonly=1) +    # for error of type: +    #   1. can't reach the repo URL(s) +    except yum.Errors.NoMoreMirrorsRepoError as e:  # pylint: disable=invalid-name +        bail('Error getting data from at least one yum repository: %s' % e) +    #   2. invalid repo definition +    except yum.Errors.RepoError as e:  # pylint: disable=invalid-name +        bail('Error with yum repository configuration: %s' % e) +    #   3. other/unknown +    #    * just report the problem verbatim +    except:  # pylint: disable=bare-except; # noqa +        bail('Unexpected error with yum repository: %s' % sys.exc_info()[1]) + +    packages = module.params['packages'] +    no_such_pkg = [] +    for pkg in packages: +        try: +            yb.install(name=pkg) +        except yum.Errors.InstallError as e:  # pylint: disable=invalid-name +            no_such_pkg.append(pkg) +        except:  # pylint: disable=bare-except; # noqa +            bail('Unexpected error with yum install/update: %s' % +                 sys.exc_info()[1]) +    if not packages: +        # no packages requested means test a yum update of everything +        yb.update() +    elif no_such_pkg: +        # wanted specific packages to install but some aren't available +        user_msg = 'Cannot install all of the necessary packages. Unavailable:\n' +        for pkg in no_such_pkg: +            user_msg += '  %s\n' % pkg +        user_msg += 'You may need to enable one or more yum repositories to make this content available.' +        bail(user_msg) + +    try: +        txn_result, txn_msgs = yb.buildTransaction() +    except:  # pylint: disable=bare-except; # noqa +        bail('Unexpected error during dependency resolution for yum update: \n %s' % +             sys.exc_info()[1]) + +    # find out if there are any errors with the update/install +    if txn_result == 0:  # 'normal exit' meaning there's nothing to install/update +        pass +    elif txn_result == 1:  # error with transaction +        user_msg = 'Could not perform a yum update.\n' +        if len(txn_msgs) > 0: +            user_msg += 'Errors from dependency resolution:\n' +            for msg in txn_msgs: +                user_msg += '  %s\n' % msg +            user_msg += 'You should resolve these issues before proceeding with an install.\n' +            user_msg += 'You may need to remove or downgrade packages or enable/disable yum repositories.' +        bail(user_msg) +    # TODO: it would be nice depending on the problem: +    #   1. dependency for update not found +    #    * construct the dependency tree +    #    * find the installed package(s) that required the missing dep +    #    * determine if any of these packages matter to openshift +    #    * build helpful error output +    #   2. conflicts among packages in available content +    #    * analyze dependency tree and build helpful error output +    #   3. other/unknown +    #    * report the problem verbatim +    #    * add to this list as we come across problems we can clearly diagnose +    elif txn_result == 2:  # everything resolved fine +        pass +    else: +        bail('Unknown error(s) from dependency resolution. Exit Code: %d:\n%s' % +             (txn_result, txn_msgs)) + +    # _unmute() +    module.exit_json(changed=False) + + +if __name__ == '__main__': +    main() diff --git a/roles/openshift_preflight/common/meta/main.yml b/roles/openshift_preflight/common/meta/main.yml new file mode 100644 index 000000000..6f23cbf3b --- /dev/null +++ b/roles/openshift_preflight/common/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: +  - role: openshift_preflight/base diff --git a/roles/openshift_preflight/common/tasks/main.yml b/roles/openshift_preflight/common/tasks/main.yml new file mode 100644 index 000000000..f1a4a160e --- /dev/null +++ b/roles/openshift_preflight/common/tasks/main.yml @@ -0,0 +1,21 @@ +--- +# check content available on all hosts +- when: not openshift.common.is_containerized | bool +  block: + +    - name: determine if yum update will work +      action: check_yum_update +      register: r + +    - set_fact: +        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'determine if yum update will work'})] }}" + +    - name: determine if expected version matches what is available +      aos_version: +        version: "{{ openshift_release }}" +      when: +        - deployment_type == "openshift-enterprise" +      register: r + +    - set_fact: +        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'determine if expected version matches what is available'})] }}" diff --git a/roles/openshift_preflight/init/meta/main.yml b/roles/openshift_preflight/init/meta/main.yml new file mode 100644 index 000000000..0bbeadd34 --- /dev/null +++ b/roles/openshift_preflight/init/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: +  - role: openshift_facts diff --git a/roles/openshift_preflight/init/tasks/main.yml b/roles/openshift_preflight/init/tasks/main.yml new file mode 100644 index 000000000..bf2d82196 --- /dev/null +++ b/roles/openshift_preflight/init/tasks/main.yml @@ -0,0 +1,4 @@ +--- +- name: set common variables +  set_fact: +    oo_preflight_check_results: "{{ oo_preflight_check_results | default([]) }}" diff --git a/roles/openshift_preflight/masters/meta/main.yml b/roles/openshift_preflight/masters/meta/main.yml new file mode 100644 index 000000000..6f23cbf3b --- /dev/null +++ b/roles/openshift_preflight/masters/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: +  - role: openshift_preflight/base diff --git a/roles/openshift_preflight/masters/tasks/main.yml b/roles/openshift_preflight/masters/tasks/main.yml new file mode 100644 index 000000000..35fb1e3ca --- /dev/null +++ b/roles/openshift_preflight/masters/tasks/main.yml @@ -0,0 +1,31 @@ +--- +# determine if yum install of master pkgs will work +- when: not openshift.common.is_containerized | bool +  block: + +    - name: main master packages availability +      check_yum_update: +        packages: +          - "{{ openshift.common.service_type }}" +          - "{{ openshift.common.service_type }}-clients" +          - "{{ openshift.common.service_type }}-master" +      register: r + +    - set_fact: +        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main master packages availability'})] }}" + +    - name: other master packages availability +      check_yum_update: +        packages: +          - etcd +          - bash-completion +          - cockpit-bridge +          - cockpit-docker +          - cockpit-kubernetes +          - cockpit-shell +          - cockpit-ws +          - httpd-tools +      register: r + +    - set_fact: +        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other master packages availability'})] }}" diff --git a/roles/openshift_preflight/nodes/meta/main.yml b/roles/openshift_preflight/nodes/meta/main.yml new file mode 100644 index 000000000..6f23cbf3b --- /dev/null +++ b/roles/openshift_preflight/nodes/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: +  - role: openshift_preflight/base diff --git a/roles/openshift_preflight/nodes/tasks/main.yml b/roles/openshift_preflight/nodes/tasks/main.yml new file mode 100644 index 000000000..a10e69024 --- /dev/null +++ b/roles/openshift_preflight/nodes/tasks/main.yml @@ -0,0 +1,41 @@ +--- +# determine if yum install of node pkgs will work +- when: not openshift.common.is_containerized | bool +  block: + +    - name: main node packages availability +      check_yum_update: +        packages: +          - "{{ openshift.common.service_type }}" +          - "{{ openshift.common.service_type }}-node" +          - "{{ openshift.common.service_type }}-sdn-ovs" +      register: r + +    - set_fact: +        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'main node packages availability'})] }}" + +    - name: other node packages availability +      check_yum_update: +        packages: +          - docker +          - PyYAML +          - firewalld +          - iptables +          - iptables-services +          - nfs-utils +          - ntp +          - yum-utils +          - dnsmasq +          - libselinux-python +          - ceph-common +          - glusterfs-fuse +          - iscsi-initiator-utils +          - pyparted +          - python-httplib2 +          - openssl +          - flannel +          - bind +      register: r + +    - set_fact: +        oo_preflight_check_results: "{{ oo_preflight_check_results + [r|combine({'_task': 'other node packages availability'})] }}" diff --git a/roles/openshift_preflight/verify_status/callback_plugins/zz_failure_summary.py b/roles/openshift_preflight/verify_status/callback_plugins/zz_failure_summary.py new file mode 100644 index 000000000..180ed8d8f --- /dev/null +++ b/roles/openshift_preflight/verify_status/callback_plugins/zz_failure_summary.py @@ -0,0 +1,96 @@ +# vim: expandtab:tabstop=4:shiftwidth=4 +''' +Ansible callback plugin. +''' + +from ansible.plugins.callback import CallbackBase +from ansible import constants as C +from ansible.utils.color import stringc + + +class CallbackModule(CallbackBase): +    ''' +    This callback plugin stores task results and summarizes failures. +    The file name is prefixed with `zz_` to make this plugin be loaded last by +    Ansible, thus making its output the last thing that users see. +    ''' + +    CALLBACK_VERSION = 2.0 +    CALLBACK_TYPE = 'aggregate' +    CALLBACK_NAME = 'failure_summary' +    CALLBACK_NEEDS_WHITELIST = False + +    def __init__(self): +        super(CallbackModule, self).__init__() +        self.__failures = [] + +    def v2_runner_on_failed(self, result, ignore_errors=False): +        super(CallbackModule, self).v2_runner_on_failed(result, ignore_errors) +        self.__failures.append(dict(result=result, ignore_errors=ignore_errors)) + +    def v2_playbook_on_stats(self, stats): +        super(CallbackModule, self).v2_playbook_on_stats(stats) +        # TODO: update condition to consider a host var or env var to +        # enable/disable the summary, so that we can control the output from a +        # play. +        if self.__failures: +            self._print_failure_summary() + +    def _print_failure_summary(self): +        '''Print a summary of failed tasks (including ignored failures).''' +        self._display.display(u'\nFailure summary:\n') + +        # TODO: group failures by host or by task. If grouped by host, it is +        # easy to see all problems of a given host. If grouped by task, it is +        # easy to see what hosts needs the same fix. + +        width = len(str(len(self.__failures))) +        initial_indent_format = u'  {{:>{width}}}. '.format(width=width) +        initial_indent_len = len(initial_indent_format.format(0)) +        subsequent_indent = u' ' * initial_indent_len +        subsequent_extra_indent = u' ' * (initial_indent_len + 10) + +        for i, failure in enumerate(self.__failures, 1): +            lines = _format_failure(failure) +            self._display.display(u'\n{}{}'.format(initial_indent_format.format(i), lines[0])) +            for line in lines[1:]: +                line = line.replace(u'\n', u'\n' + subsequent_extra_indent) +                indented = u'{}{}'.format(subsequent_indent, line) +                self._display.display(indented) + + +# Reason: disable pylint protected-access because we need to access _* +#         attributes of a task result to implement this method. +# Status: permanently disabled unless Ansible's API changes. +# pylint: disable=protected-access +def _format_failure(failure): +    '''Return a list of pretty-formatted lines describing a failure, including +    relevant information about it. Line separators are not included.''' +    result = failure['result'] +    host = result._host.get_name() +    play = _get_play(result._task) +    if play: +        play = play.get_name() +    task = result._task.get_name() +    msg = result._result.get('msg', u'???') +    rows = ( +        (u'Host', host), +        (u'Play', play), +        (u'Task', task), +        (u'Message', stringc(msg, C.COLOR_ERROR)), +    ) +    row_format = '{:10}{}' +    return [row_format.format(header + u':', body) for header, body in rows] + + +# Reason: disable pylint protected-access because we need to access _* +#         attributes of obj to implement this function. +#         This is inspired by ansible.playbook.base.Base.dump_me. +# Status: permanently disabled unless Ansible's API changes. +# pylint: disable=protected-access +def _get_play(obj): +    '''Given a task or block, recursively tries to find its parent play.''' +    if hasattr(obj, '_play'): +        return obj._play +    if getattr(obj, '_parent'): +        return _get_play(obj._parent) diff --git a/roles/openshift_preflight/verify_status/tasks/main.yml b/roles/openshift_preflight/verify_status/tasks/main.yml new file mode 100644 index 000000000..36ccf648a --- /dev/null +++ b/roles/openshift_preflight/verify_status/tasks/main.yml @@ -0,0 +1,8 @@ +--- +- name: find check failures +  set_fact: +    oo_preflight_check_failures: "{{ oo_preflight_check_results | select('failed', 'equalto', True) | list }}" + +- name: ensure all checks succeed +  action: fail +  when: oo_preflight_check_failures diff --git a/roles/openshift_storage_nfs_lvm/meta/main.yml b/roles/openshift_storage_nfs_lvm/meta/main.yml index ea7c9bb45..50d94f6a3 100644 --- a/roles/openshift_storage_nfs_lvm/meta/main.yml +++ b/roles/openshift_storage_nfs_lvm/meta/main.yml @@ -14,4 +14,5 @@ galaxy_info:      - all    categories:    - openshift -dependencies: [] +dependencies: +- role: openshift_facts diff --git a/roles/openshift_storage_nfs_lvm/tasks/main.yml b/roles/openshift_storage_nfs_lvm/tasks/main.yml index ea0cc2a94..49dd657b5 100644 --- a/roles/openshift_storage_nfs_lvm/tasks/main.yml +++ b/roles/openshift_storage_nfs_lvm/tasks/main.yml @@ -2,7 +2,7 @@  # TODO -- this may actually work on atomic hosts  - fail:      msg: "openshift_storage_nfs_lvm is not compatible with atomic host" -    when: openshift.common.is_atomic | true +  when: openshift.common.is_atomic | bool  - name: Create lvm volumes    lvol: vg={{osnl_volume_group}} lv={{ item }} size={{osnl_volume_size}}G diff --git a/roles/openshift_storage_nfs_lvm/templates/nfs.json.j2 b/roles/openshift_storage_nfs_lvm/templates/nfs.json.j2 index 19e150f7d..c273aca9f 100644 --- a/roles/openshift_storage_nfs_lvm/templates/nfs.json.j2 +++ b/roles/openshift_storage_nfs_lvm/templates/nfs.json.j2 @@ -14,8 +14,8 @@      "accessModes": [ "ReadWriteOnce", "ReadWriteMany" ],      "persistentVolumeReclaimPolicy": "{{ osnl_volume_reclaim_policy }}",      "nfs": { -      "Server": "{{ inventory_hostname }}", -      "Path": "{{ osnl_mount_dir }}/{{ item }}" +      "server": "{{ inventory_hostname }}", +      "path": "{{ osnl_mount_dir }}/{{ item }}"      }    }  } diff --git a/roles/os_firewall/library/os_firewall_manage_iptables.py b/roles/os_firewall/library/os_firewall_manage_iptables.py index b60e52dfe..8ba650994 100755 --- a/roles/os_firewall/library/os_firewall_manage_iptables.py +++ b/roles/os_firewall/library/os_firewall_manage_iptables.py @@ -127,9 +127,17 @@ class IpTablesManager(object):  # pylint: disable=too-many-instance-attributes          check_cmd = self.cmd + ['-C'] + rule          return True if subprocess.call(check_cmd) == 0 else False +    @staticmethod +    def port_as_argument(port): +        if isinstance(port, int): +            return str(port) +        if isinstance(port, basestring):  # noqa: F405 +            return port.replace('-', ":") +        return port +      def gen_rule(self, port, proto):          return [self.chain, '-p', proto, '-m', 'state', '--state', 'NEW', -                '-m', proto, '--dport', str(port), '-j', 'ACCEPT'] +                '-m', proto, '--dport', IpTablesManager.port_as_argument(port), '-j', 'ACCEPT']      def create_jump(self):          if self.check_mode: @@ -231,7 +239,7 @@ def main():              create_jump_rule=dict(required=False, type='bool', default=True),              jump_rule_chain=dict(required=False, default='INPUT'),              protocol=dict(required=False, choices=['tcp', 'udp']), -            port=dict(required=False, type='int'), +            port=dict(required=False, type='str'),              ip_version=dict(required=False, default='ipv4',                              choices=['ipv4', 'ipv6']),          ), | 
