diff options
29 files changed, 1314 insertions, 78 deletions
| diff --git a/README_AWS.md b/README_AWS.md index c511741b9..3a5790eb3 100644 --- a/README_AWS.md +++ b/README_AWS.md @@ -154,18 +154,10 @@ Note: If no deployment type is specified, then the default is origin.  ## Post-ansible steps -Create the default router -------------------------- -On the master host: -```sh -oadm router --create=true \ -  --credentials=/etc/openshift/master/openshift-router.kubeconfig -``` - -Create the default docker-registry ----------------------------------- -On the master host: -```sh -oadm registry --create=true \ -  --credentials=/etc/openshift/master/openshift-registry.kubeconfig -```
\ No newline at end of file + +You should now be ready to follow the **What's Next?** section of the advanced installation guide to deploy your router, registry, and other components. + +Refer to the advanced installation guide for your deployment type: + +* [OpenShift Enterprise](https://docs.openshift.com/enterprise/3.0/install_config/install/advanced_install.html#what-s-next) +* [OpenShift Origin](https://docs.openshift.org/latest/install_config/install/advanced_install.html#what-s-next) diff --git a/README_OSE.md b/README_OSE.md index cce1ec030..79ad07044 100644 --- a/README_OSE.md +++ b/README_OSE.md @@ -101,6 +101,7 @@ ose3-master.example.com  # host group for nodes  [nodes] +ose3-master.example.com  ose3-node[1:2].example.com  ``` @@ -116,22 +117,8 @@ ansible-playbook playbooks/byo/config.yml  inventory file use the -i option for ansible-playbook.  ## Post-ansible steps -#### Create the default router -On the master host: -```sh -oadm router --create=true \ -  --credentials=/etc/openshift/master/openshift-router.kubeconfig \ -  --images='rcm-img-docker01.build.eng.bos.redhat.com:5001/openshift3/ose-${component}:${version}' -``` -#### Create the default docker-registry -On the master host: -```sh -oadm registry --create=true \ -  --credentials=/etc/openshift/master/openshift-registry.kubeconfig \ -  --images='rcm-img-docker01.build.eng.bos.redhat.com:5001/openshift3/ose-${component}:${version}' \ -  --mount-host=/var/lib/openshift/docker-registry -``` +You should now be ready to follow the [What's Next?](https://docs.openshift.com/enterprise/3.0/install_config/install/advanced_install.html#what-s-next) section of the advanced installation guide to deploy your router, registry, and other components.  ## Overriding detected ip addresses and hostnames  Some deployments will require that the user override the detected hostnames diff --git a/README_libvirt.md b/README_libvirt.md index 60af0ac88..1a710ff3b 100644 --- a/README_libvirt.md +++ b/README_libvirt.md @@ -68,7 +68,12 @@ If your `$HOME` is world readable, everything is fine. If your `$HOME` is privat  error: Cannot access storage file '$HOME/libvirt-storage-pool-openshift/lenaic-master-216d8.qcow2' (as uid:99, gid:78): Permission denied  ``` -In order to fix that issue, you have several possibilities:* set `libvirt_storage_pool_path` inside `playbooks/libvirt/openshift-cluster/launch.yml` and `playbooks/libvirt/openshift-cluster/terminate.yml` to a directory: * backed by a filesystem with a lot of free disk space * writable by your user; * accessible by the qemu user.* Grant the qemu user access to the storage pool. +In order to fix that issue, you have several possibilities: + * set `libvirt_storage_pool_path` inside `playbooks/libvirt/openshift-cluster/launch.yml` and `playbooks/libvirt/openshift-cluster/terminate.yml` to a directory: +   * backed by a filesystem with a lot of free disk space +   * writable by your user; +   * accessible by the qemu user. + * Grant the qemu user access to the storage pool.  On Arch: diff --git a/README_origin.md b/README_origin.md index f13fe660a..cb213a93a 100644 --- a/README_origin.md +++ b/README_origin.md @@ -73,6 +73,7 @@ osv3-master.example.com  # host group for nodes  [nodes] +osv3-master.example.com  osv3-node[1:2].example.com  ``` @@ -88,23 +89,8 @@ ansible-playbook playbooks/byo/config.yml  inventory file use the -i option for ansible-playbook.  ## Post-ansible steps -#### Create the default router -On the master host: -```sh -oadm router --create=true \ -  --credentials=/etc/openshift/master/openshift-router.kubeconfig -``` - -#### Create the default docker-registry -On the master host: -```sh -oadm registry --create=true \ -  --credentials=/etc/openshift/master/openshift-registry.kubeconfig -``` -If you would like persistent storage, refer to the -[OpenShift documentation](https://docs.openshift.org/latest/admin_guide/install/docker_registry.html) -for more information on deployment options for the built in docker-registry. +You should now be ready to follow the [What's Next?](https://docs.openshift.org/latest/install_config/install/advanced_install.html#what-s-next) section of the advanced installation guide to deploy your router, registry, and other components.  ## Overriding detected ip addresses and hostnames  Some deployments will require that the user override the detected hostnames diff --git a/bin/cluster b/bin/cluster index a19434e21..582327415 100755 --- a/bin/cluster +++ b/bin/cluster @@ -260,7 +260,7 @@ if __name__ == '__main__':                               choices=['origin', 'online', 'enterprise'],                               help='Deployment type. (default: origin)')      meta_parser.add_argument('-T', '--product-type', -                             choices=['openshift' 'atomic-enterprise'], +                             choices=['openshift', 'atomic-enterprise'],                               help='Product type. (default: openshift)')      meta_parser.add_argument('-o', '--option', action='append',                               help='options') diff --git a/playbooks/adhoc/grow_docker_vg/filter_plugins/oo_filters.py b/playbooks/adhoc/grow_docker_vg/filter_plugins/oo_filters.py new file mode 100644 index 000000000..d0264cde9 --- /dev/null +++ b/playbooks/adhoc/grow_docker_vg/filter_plugins/oo_filters.py @@ -0,0 +1,41 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vim: expandtab:tabstop=4:shiftwidth=4 +''' +Custom filters for use in openshift-ansible +''' + +import pdb + + +class FilterModule(object): +    ''' Custom ansible filters ''' + +    @staticmethod +    def oo_pdb(arg): +        ''' This pops you into a pdb instance where arg is the data passed in +            from the filter. +            Ex: "{{ hostvars | oo_pdb }}" +        ''' +        pdb.set_trace() +        return arg + +    @staticmethod +    def translate_volume_name(volumes, target_volume): +        ''' +            This filter matches a device string /dev/sdX to /dev/xvdX +            It will then return the AWS volume ID +        ''' +        for vol in volumes: +            translated_name = vol["attachment_set"]["device"].replace("/dev/sd", "/dev/xvd") +            if target_volume.startswith(translated_name): +                return vol["id"] + +        return None + + +    def filters(self): +        ''' returns a mapping of filters to methods ''' +        return { +            "translate_volume_name": self.translate_volume_name, +        } diff --git a/playbooks/adhoc/grow_docker_vg/grow_docker_vg.yml b/playbooks/adhoc/grow_docker_vg/grow_docker_vg.yml new file mode 100644 index 000000000..ef9b45abd --- /dev/null +++ b/playbooks/adhoc/grow_docker_vg/grow_docker_vg.yml @@ -0,0 +1,206 @@ +--- +# This playbook grows the docker VG on a node by: +#  * add a new volume +#  * add volume to the existing VG. +#  * pv move to the new volume. +#  * remove old volume +#  * detach volume +#  * mark old volume in AWS with "REMOVE ME" tag +#  * grow docker LVM to 90% of the VG +# +#  To run: +#  1. Source your AWS credentials (make sure it's the corresponding AWS account) into your environment +#    export AWS_ACCESS_KEY_ID='XXXXX' +#    export AWS_SECRET_ACCESS_KEY='XXXXXX' +# +# 2. run the playbook: +#   ansible-playbook -e 'cli_tag_name=<tag-name>' grow_docker_vg.yml +# +#  Example: +#   ansible-playbook -e 'cli_tag_name=ops-compute-12345' grow_docker_vg.yml +# +#  Notes: +#  * By default this will do a 55GB GP2 volume.  The can be overidden with the "-e 'cli_volume_size=100'" variable +#  * This does a GP2 by default.  Support for Provisioned IOPS has not been added +#  * This will assign the new volume to /dev/xvdc. This is not variablized, yet. +#  * This can be done with NO downtime on the host +#  * This playbook assumes that there is a Logical Volume that is installed and called "docker-pool".  This is +#      the LV that gets created via the "docker-storage-setup" command +# + +- name: Grow the docker volume group +  hosts: "tag_Name_{{ cli_tag_name }}" +  user: root +  connection: ssh +  gather_facts: no + +  vars: +    cli_volume_type: gp2 +    cli_volume_size: 55 +#    cli_volume_iops: "{{ 30 * cli_volume_size }}" + +  pre_tasks: +  - fail: +      msg: "This playbook requires {{item}} to be set." +    when: "{{ item }} is not defined or {{ item }} == ''" +    with_items: +    - cli_tag_name +    - cli_volume_size + +  - debug: +      var: hosts + +  - name: start docker +    service: +      name: docker +      state: started + +  - name: Determine if Storage Driver (docker info) is devicemapper +    shell: docker info | grep 'Storage Driver:.*devicemapper' +    register: device_mapper_check +    ignore_errors: yes + +  - debug: +      var: device_mapper_check + +  - name: fail if we don't detect devicemapper +    fail: +      msg:  The "Storage Driver" in "docker info" is not set to "devicemapper"! Please investigate manually. +    when: device_mapper_check.rc == 1 + +  # docker-storage-setup creates a docker-pool as the lvm.  I am using docker-pool lvm to test +  # and find the volume group. +  - name: Attempt to find the Volume Group that docker is using +    shell: lvs | grep docker-pool | awk '{print $2}' +    register: docker_vg_name +    ignore_errors: yes + +  - debug: +      var: docker_vg_name + +  - name: fail if we don't find a docker volume group +    fail: +      msg:  Unable to find docker volume group. Please investigate manually. +    when: docker_vg_name.stdout_lines|length != 1 + +  # docker-storage-setup creates a docker-pool as the lvm.  I am using docker-pool lvm to test +  # and find the physical volume. +  - name: Attempt to find the Phyisical Volume that docker is using +    shell: "pvs | grep {{ docker_vg_name.stdout }} | awk '{print $1}'" +    register: docker_pv_name +    ignore_errors: yes + +  - debug: +      var: docker_pv_name + +  - name: fail if we don't find a docker physical volume +    fail: +      msg:  Unable to find docker physical volume. Please investigate manually. +    when: docker_pv_name.stdout_lines|length != 1 + + +  - name: get list of volumes from AWS +    delegate_to: localhost +    ec2_vol: +      state: list +      instance: "{{ ec2_id }}" +      region: "{{ ec2_region }}" +    register: attached_volumes + +  - debug: var=attached_volumes + +  - name: get volume id of current docker volume +    set_fact: +      old_docker_volume_id: "{{ attached_volumes.volumes | translate_volume_name(docker_pv_name.stdout) }}" + +  - debug: var=old_docker_volume_id + +  - name: check to see if /dev/xvdc exists +    command: test -e /dev/xvdc +    register: xvdc_check +    ignore_errors: yes + +  - debug: var=xvdc_check + +  - name: fail if /dev/xvdc already exists +    fail: +      msg: /dev/xvdc already exists.  Please investigate +    when: xvdc_check.rc == 0 + +  - name: Create a volume and attach it +    delegate_to: localhost +    ec2_vol: +      state: present +      instance: "{{ ec2_id }}" +      region: "{{ ec2_region }}" +      volume_size: "{{ cli_volume_size | default(30, True)}}" +      volume_type: "{{ cli_volume_type }}" +      device_name: /dev/xvdc +    register: create_volume + +  - debug: var=create_volume + +  - name: Fail when problems creating volumes and attaching +    fail: +      msg: "Failed to create or attach volume msg: {{ create_volume.msg }}" +    when: create_volume.msg is defined + +  - name: tag the vol with a name +    delegate_to: localhost +    ec2_tag: region={{ ec2_region }} resource={{ create_volume.volume_id }} +    args: +      tags: +        Name: "{{ ec2_tag_Name }}" +        env: "{{ ec2_tag_environment }}" +    register: voltags + +  - name: check for attached drive +    command: test -b /dev/xvdc +    register: attachment_check +    until: attachment_check.rc == 0 +    retries: 30 +    delay: 2 + +  - name: partition the new drive and make it lvm +    command: parted /dev/xvdc --script -- mklabel msdos mkpart primary 0% 100% set 1 lvm + +  - name: pvcreate /dev/xvdc +    command: pvcreate /dev/xvdc1 + +  - name: Extend the docker volume group +    command: vgextend "{{ docker_vg_name.stdout }}" /dev/xvdc1 + +  - name: pvmove onto new volume +    command: "pvmove {{ docker_pv_name.stdout }} /dev/xvdc1" +    async: 3600 +    poll: 10 + +  - name: Remove the old docker drive from the volume group +    command: "vgreduce {{ docker_vg_name.stdout }} {{ docker_pv_name.stdout }}" + +  - name: Remove the pv from the old drive +    command: "pvremove {{ docker_pv_name.stdout }}" + +  - name: Extend the docker lvm +    command: "lvextend -l '90%VG' /dev/{{ docker_vg_name.stdout }}/docker-pool" + +  - name: detach  old docker volume +    delegate_to: localhost +    ec2_vol: +      region: "{{ ec2_region }}" +      id: "{{ old_docker_volume_id }}" +      instance: None + +  - name: tag the old vol valid label +    delegate_to: localhost +    ec2_tag: region={{ ec2_region }} resource={{old_docker_volume_id}} +    args: +      tags: +        Name: "{{ ec2_tag_Name }} REMOVE ME" +    register: voltags + +  - name: Update the /etc/sysconfig/docker-storage-setup with new device +    lineinfile: +      dest: /etc/sysconfig/docker-storage-setup +      regexp: ^DEVS= +      line: DEVS=/dev/xvdc diff --git a/playbooks/adhoc/upgrades/README.md b/playbooks/adhoc/upgrades/README.md new file mode 100644 index 000000000..6de8a970f --- /dev/null +++ b/playbooks/adhoc/upgrades/README.md @@ -0,0 +1,21 @@ +# [NOTE] +This playbook will re-run installation steps overwriting any local +modifications. You should ensure that your inventory has been updated with any +modifications you've made after your initial installation. If you find any items +that cannot be configured via ansible please open an issue at +https://github.com/openshift/openshift-ansible + +# Overview +This playbook is available as a technical preview. It currently performs the +following steps. + + * Upgrade and restart master services + * Upgrade and restart node services + * Applies latest configuration by re-running the installation playbook + * Applies the latest cluster policies + * Updates the default router if one exists + * Updates the default registry if one exists + * Updates image streams and quickstarts + +# Usage +ansible-playbook -i ~/ansible-inventory openshift-ansible/playbooks/adhoc/upgrades/upgrade.yml diff --git a/playbooks/adhoc/upgrades/filter_plugins b/playbooks/adhoc/upgrades/filter_plugins new file mode 120000 index 000000000..b0b7a3414 --- /dev/null +++ b/playbooks/adhoc/upgrades/filter_plugins @@ -0,0 +1 @@ +../../../filter_plugins/
\ No newline at end of file diff --git a/playbooks/adhoc/upgrades/lookup_plugins b/playbooks/adhoc/upgrades/lookup_plugins new file mode 120000 index 000000000..73cafffe5 --- /dev/null +++ b/playbooks/adhoc/upgrades/lookup_plugins @@ -0,0 +1 @@ +../../../lookup_plugins/
\ No newline at end of file diff --git a/playbooks/adhoc/upgrades/roles b/playbooks/adhoc/upgrades/roles new file mode 120000 index 000000000..e2b799b9d --- /dev/null +++ b/playbooks/adhoc/upgrades/roles @@ -0,0 +1 @@ +../../../roles/
\ No newline at end of file diff --git a/playbooks/adhoc/upgrades/upgrade.yml b/playbooks/adhoc/upgrades/upgrade.yml new file mode 100644 index 000000000..e666f0472 --- /dev/null +++ b/playbooks/adhoc/upgrades/upgrade.yml @@ -0,0 +1,115 @@ +--- +- name: Re-Run cluster configuration to apply latest configuration changes +  include: ../../common/openshift-cluster/config.yml +  vars: +    g_etcd_group: "{{ 'etcd' }}" +    g_masters_group: "{{ 'masters' }}" +    g_nodes_group: "{{ 'nodes' }}" +    openshift_cluster_id: "{{ cluster_id | default('default') }}" +    openshift_deployment_type: "{{ deployment_type }}" + +- name: Upgrade masters +  hosts: masters +  vars: +    openshift_version: "{{ openshift_pkg_version | default('') }}" +  tasks: +    - name: Upgrade master packages +      yum: pkg={{ openshift.common.service_type }}-master{{ openshift_version }} state=latest +    - name: Restart master services +      service: name="{{ openshift.common.service_type}}-master" state=restarted + +- name: Upgrade nodes +  hosts: nodes +  vars: +    openshift_version: "{{ openshift_pkg_version | default('') }}" +  tasks: +    - name: Upgrade node packages +      yum: pkg={{ openshift.common.service_type }}-node{{ openshift_version }} state=latest +    - name: Restart node services +      service: name="{{ openshift.common.service_type }}-node" state=restarted + +- name: Determine new master version +  hosts: oo_first_master +  tasks: +    - name: Determine new version +      command: > +        rpm -q --queryformat '%{version}' {{ openshift.common.service_type }}-master +      register: _new_version + +- name: Ensure AOS 3.0.2 or Origin 1.0.6 +  hosts: oo_first_master +  tasks: +    fail: This playbook requires Origin 1.0.6 or Atomic OpenShift 3.0.2 or later +    when: _new_version.stdout < 1.0.6 or (_new_version.stdout >= 3.0 and _new_version.stdout < 3.0.2) + +- name: Update cluster policy +  hosts: oo_first_master +  tasks: +    - name: oadm policy reconcile-cluster-roles --confirm +      command: > +        {{ openshift.common.admin_binary}} --config={{ openshift.common.config_base }}/master/admin.kubeconfig +        policy reconcile-cluster-roles --confirm + +- name: Upgrade default router +  hosts: oo_first_master +  vars: +    - router_image: "{{ openshift.master.registry_url | replace( '${component}', 'haproxy-router' ) | replace ( '${version}', 'v' + _new_version.stdout ) }}" +    - oc_cmd: "{{ openshift.common.client_binary }} --config={{ openshift.common.config_base }}/master/admin.kubeconfig" +  tasks: +    - name: Check for default router +      command: > +        {{ oc_cmd }} get -n default dc/router +      register: _default_router +      failed_when: false +      changed_when: false +    - name: Check for allowHostNetwork and allowHostPorts +      when: _default_router.rc == 0 +      shell: > +        {{ oc_cmd }} get -o yaml scc/privileged | /usr/bin/grep -e allowHostPorts -e allowHostNetwork +      register: _scc +    - name: Grant allowHostNetwork and allowHostPorts +      when: +        - _default_router.rc == 0 +        - "'false' in _scc.stdout" +      command: > +        {{ oc_cmd }} patch scc/privileged -p '{"allowHostPorts":true,"allowHostNetwork":true}' --loglevel=9 +    - name: Update deployment config to 1.0.4/3.0.1 spec +      when: _default_router.rc == 0 +      command: > +        {{ oc_cmd }} patch dc/router -p +        '{"spec":{"strategy":{"rollingParams":{"updatePercent":-10},"spec":{"serviceAccount":"router","serviceAccountName":"router"}}}}' +    - name: Switch to hostNetwork=true +      when: _default_router.rc == 0 +      command: > +        {{ oc_cmd }} patch dc/router -p '{"spec":{"template":{"spec":{"hostNetwork":true}}}}' +    - name: Update router image to current version +      when: _default_router.rc == 0 +      command: > +        {{ oc_cmd }} patch dc/router -p +        '{"spec":{"template":{"spec":{"containers":[{"name":"router","image":"{{ router_image }}"}]}}}}' + +- name: Upgrade default +  hosts: oo_first_master +  vars: +    - registry_image: "{{  openshift.master.registry_url | replace( '${component}', 'docker-registry' )  | replace ( '${version}', 'v' + _new_version.stdout  ) }}" +    - oc_cmd: "{{ openshift.common.client_binary }} --config={{ openshift.common.config_base }}/master/admin.kubeconfig" +  tasks: +    - name: Check for default registry +      command: > +          {{ oc_cmd }} get -n default dc/docker-registry +      register: _default_registry +      failed_when: false +      changed_when: false +    - name: Update registry image to current version +      when: _default_registry.rc == 0 +      command: > +        {{ oc_cmd }} patch dc/docker-registry -p +        '{"spec":{"template":{"spec":{"containers":[{"name":"registry","image":"{{ registry_image }}"}]}}}}' + +- name: Update image streams and templates +  hosts: oo_first_master +  vars: +    openshift_examples_import_command: "update" +    openshift_deployment_type: "{{ deployment_type }}" +  roles: +    - openshift_examples diff --git a/playbooks/aws/openshift-cluster/tasks/launch_instances.yml b/playbooks/aws/openshift-cluster/tasks/launch_instances.yml index b77bcdc1a..9c699120b 100644 --- a/playbooks/aws/openshift-cluster/tasks/launch_instances.yml +++ b/playbooks/aws/openshift-cluster/tasks/launch_instances.yml @@ -172,6 +172,7 @@              - rotate 7              - compress              - sharedscripts +            - missingok            scripts:              postrotate: "/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true" diff --git a/roles/ansible_tower/tasks/main.yaml b/roles/ansible_tower/tasks/main.yaml index c110a3b70..b7757214d 100644 --- a/roles/ansible_tower/tasks/main.yaml +++ b/roles/ansible_tower/tasks/main.yaml @@ -9,6 +9,7 @@    - ansible    - telnet    - ack +  - pylint  - name: download Tower setup    get_url: url=http://releases.ansible.com/ansible-tower/setup/ansible-tower-setup-2.1.1.tar.gz dest=/opt/ force=no @@ -38,5 +39,3 @@      regexp: "^({{ item.option }})( *)="      line: '\1\2= {{ item.value }}'    with_items: config_changes | default([], true) -     - diff --git a/roles/etcd/tasks/main.yml b/roles/etcd/tasks/main.yml index 27bfb7de9..656901409 100644 --- a/roles/etcd/tasks/main.yml +++ b/roles/etcd/tasks/main.yml @@ -38,6 +38,7 @@    template:      src: etcd.conf.j2      dest: /etc/etcd/etcd.conf +    backup: true    notify:      - restart etcd diff --git a/roles/etcd_ca/tasks/main.yml b/roles/etcd_ca/tasks/main.yml index 8a266f732..625756867 100644 --- a/roles/etcd_ca/tasks/main.yml +++ b/roles/etcd_ca/tasks/main.yml @@ -18,6 +18,7 @@  - template:      dest: "{{ etcd_ca_dir }}/fragments/openssl_append.cnf"      src: openssl_append.j2 +    backup: true  - assemble:      src: "{{ etcd_ca_dir }}/fragments" diff --git a/roles/fluentd_master/tasks/main.yml b/roles/fluentd_master/tasks/main.yml index 69f8eceab..55cd94460 100644 --- a/roles/fluentd_master/tasks/main.yml +++ b/roles/fluentd_master/tasks/main.yml @@ -52,4 +52,3 @@      name: 'td-agent'      state: started      enabled: yes - diff --git a/roles/lib_zabbix/library/zbx_action.py b/roles/lib_zabbix/library/zbx_action.py new file mode 100644 index 000000000..d64cebae1 --- /dev/null +++ b/roles/lib_zabbix/library/zbx_action.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python +''' + Ansible module for zabbix actions +''' +# vim: expandtab:tabstop=4:shiftwidth=4 +# +#   Zabbix action ansible module +# +# +#   Copyright 2015 Red Hat Inc. +# +#   Licensed under the Apache License, Version 2.0 (the "License"); +#   you may not use this file except in compliance with the License. +#   You may obtain a copy of the License at +# +#       http://www.apache.org/licenses/LICENSE-2.0 +# +#   Unless required by applicable law or agreed to in writing, software +#   distributed under the License is distributed on an "AS IS" BASIS, +#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#   See the License for the specific language governing permissions and +#   limitations under the License. +# + +# This is in place because each module looks similar to each other. +# These need duplicate code as their behavior is very similar +# but different for each zabbix class. +# pylint: disable=duplicate-code + +# pylint: disable=import-error +from openshift_tools.monitoring.zbxapi import ZabbixAPI, ZabbixConnection, ZabbixAPIError + +def exists(content, key='result'): +    ''' Check if key exists in content or the size of content[key] > 0 +    ''' +    if not content.has_key(key): +        return False + +    if not content[key]: +        return False + +    return True + +def conditions_equal(zab_conditions, user_conditions): +    '''Compare two lists of conditions''' +    c_type = 'conditiontype' +    _op = 'operator' +    val = 'value' +    if len(user_conditions) != len(zab_conditions): +        return False + +    for zab_cond, user_cond in zip(zab_conditions, user_conditions): +        if zab_cond[c_type] != str(user_cond[c_type]) or zab_cond[_op] != str(user_cond[_op]) or \ +           zab_cond[val] != str(user_cond[val]): +            return False + +    return True + +def filter_differences(zabbix_filters, user_filters): +    '''Determine the differences from user and zabbix for operations''' +    rval = {} +    for key, val in user_filters.items(): + +        if key == 'conditions': +            if not conditions_equal(zabbix_filters[key], val): +                rval[key] = val + +        elif zabbix_filters[key] != str(val): +            rval[key] = val + +    return rval + +# This logic is quite complex.  We are comparing two lists of dictionaries. +# The outer for-loops allow us to descend down into both lists at the same time +# and then walk over the key,val pairs of the incoming user dict's changes +# or updates.  The if-statements are looking at different sub-object types and +# comparing them.  The other suggestion on how to write this is to write a recursive +# compare function but for the time constraints and for complexity I decided to go +# this route. +# pylint: disable=too-many-branches +def operation_differences(zabbix_ops, user_ops): +    '''Determine the differences from user and zabbix for operations''' + +    # if they don't match, take the user options +    if len(zabbix_ops) != len(user_ops): +        return user_ops + +    rval = {} +    for zab, user in zip(zabbix_ops, user_ops): +        for key, val in user.items(): +            if key == 'opconditions': +                for z_cond, u_cond in zip(zab[key], user[key]): +                    if not all([str(u_cond[op_key]) == z_cond[op_key] for op_key in \ +                                ['conditiontype', 'operator', 'value']]): +                        rval[key] = val +                        break +            elif key == 'opmessage': +                # Verify each passed param matches +                for op_msg_key, op_msg_val in val.items(): +                    if zab[key][op_msg_key] != str(op_msg_val): +                        rval[key] = val +                        break + +            elif key == 'opmessage_grp': +                zab_grp_ids = set([ugrp['usrgrpid'] for ugrp in zab[key]]) +                usr_grp_ids = set([ugrp['usrgrpid'] for ugrp in val]) +                if usr_grp_ids != zab_grp_ids: +                    rval[key] = val + +            elif key == 'opmessage_usr': +                zab_usr_ids = set([usr['userid'] for usr in zab[key]]) +                usr_ids = set([usr['userid'] for usr in val]) +                if usr_ids != zab_usr_ids: +                    rval[key] = val + +            elif zab[key] != str(val): +                rval[key] = val +    return rval + +def get_users(zapi, users): +    '''get the mediatype id from the mediatype name''' +    rval_users = [] + +    for user in users: +        content = zapi.get_content('user', +                                   'get', +                                   {'filter': {'alias': user}}) +        rval_users.append({'userid': content['result'][0]['userid']}) + +    return rval_users + +def get_user_groups(zapi, groups): +    '''get the mediatype id from the mediatype name''' +    user_groups = [] + +    content = zapi.get_content('usergroup', +                               'get', +                               {'search': {'name': groups}}) + +    for usr_grp in content['result']: +        user_groups.append({'usrgrpid': usr_grp['usrgrpid']}) + +    return user_groups + +def get_mediatype_id_by_name(zapi, m_name): +    '''get the mediatype id from the mediatype name''' +    content = zapi.get_content('mediatype', +                               'get', +                               {'filter': {'description': m_name}}) + +    return content['result'][0]['mediatypeid'] + +def get_priority(priority): +    ''' determine priority +    ''' +    prior = 0 +    if 'info' in priority: +        prior = 1 +    elif 'warn' in priority: +        prior = 2 +    elif 'avg' == priority or 'ave' in priority: +        prior = 3 +    elif 'high' in priority: +        prior = 4 +    elif 'dis' in priority: +        prior = 5 + +    return prior + +def get_event_source(from_src): +    '''Translate even str into value''' +    choices = ['trigger', 'discovery', 'auto', 'internal'] +    rval = 0 +    try: +        rval = choices.index(from_src) +    except ValueError as _: +        ZabbixAPIError('Value not found for event source [%s]' % from_src) + +    return rval + +def get_status(inc_status): +    '''determine status for action''' +    rval = 1 +    if inc_status == 'enabled': +        rval = 0 + +    return rval + +def get_condition_operator(inc_operator): +    ''' determine the condition operator''' +    vals = {'=': 0, +            '<>': 1, +            'like': 2, +            'not like': 3, +            'in': 4, +            '>=': 5, +            '<=': 6, +            'not in': 7, +           } + +    return vals[inc_operator] + +def get_host_id_by_name(zapi, host_name): +    '''Get host id by name''' +    content = zapi.get_content('host', +                               'get', +                               {'filter': {'name': host_name}}) + +    return content['result'][0]['hostid'] + +def get_trigger_value(inc_trigger): +    '''determine the proper trigger value''' +    rval = 1 +    if inc_trigger == 'PROBLEM': +        rval = 1 +    else: +        rval = 0 + +    return rval + +def get_template_id_by_name(zapi, t_name): +    '''get the template id by name''' +    content = zapi.get_content('template', +                               'get', +                               {'filter': {'host': t_name}}) + +    return content['result'][0]['templateid'] + + +def get_host_group_id_by_name(zapi, hg_name): +    '''Get hostgroup id by name''' +    content = zapi.get_content('hostgroup', +                               'get', +                               {'filter': {'name': hg_name}}) + +    return content['result'][0]['groupid'] + +def get_condition_type(event_source, inc_condition): +    '''determine the condition type''' +    c_types = {} +    if event_source == 'trigger': +        c_types = {'host group': 0, +                   'host': 1, +                   'trigger': 2, +                   'trigger name': 3, +                   'trigger severity': 4, +                   'trigger value': 5, +                   'time period': 6, +                   'host template': 13, +                   'application': 15, +                   'maintenance status': 16, +                  } + +    elif event_source == 'discovery': +        c_types = {'host IP': 7, +                   'discovered service type': 8, +                   'discovered service port': 9, +                   'discovery status': 10, +                   'uptime or downtime duration': 11, +                   'received value': 12, +                   'discovery rule': 18, +                   'discovery check': 19, +                   'proxy': 20, +                   'discovery object': 21, +                  } + +    elif event_source == 'auto': +        c_types = {'proxy': 20, +                   'host name': 22, +                   'host metadata': 24, +                  } + +    elif event_source == 'internal': +        c_types = {'host group': 0, +                   'host': 1, +                   'host template': 13, +                   'application': 15, +                   'event type': 23, +                  } +    else: +        raise ZabbixAPIError('Unkown event source %s' % event_source) + +    return c_types[inc_condition] + +def get_operation_type(inc_operation): +    ''' determine the correct operation type''' +    o_types = {'send message': 0, +               'remote command': 1, +               'add host': 2, +               'remove host': 3, +               'add to host group': 4, +               'remove from host group': 5, +               'link to template': 6, +               'unlink from template': 7, +               'enable host': 8, +               'disable host': 9, +              } + +    return o_types[inc_operation] + +def get_action_operations(zapi, inc_operations): +    '''Convert the operations into syntax for api''' +    for operation in inc_operations: +        operation['operationtype'] = get_operation_type(operation['operationtype']) +        if operation['operationtype'] == 0: # send message.  Need to fix the +            operation['opmessage']['mediatypeid'] = \ +             get_mediatype_id_by_name(zapi, operation['opmessage']['mediatypeid']) +            operation['opmessage_grp'] = get_user_groups(zapi, operation.get('opmessage_grp', [])) +            operation['opmessage_usr'] = get_users(zapi, operation.get('opmessage_usr', [])) +            if operation['opmessage']['default_msg']: +                operation['opmessage']['default_msg'] = 1 +            else: +                operation['opmessage']['default_msg'] = 0 + +        # NOT supported for remote commands +        elif operation['operationtype'] == 1: +            continue + +        # Handle Operation conditions: +        # Currently there is only 1 available which +        # is 'event acknowledged'.  In the future +        # if there are any added we will need to pass this +        # option to a function and return the correct conditiontype +        if operation.has_key('opconditions'): +            for condition in operation['opconditions']: +                if condition['conditiontype'] == 'event acknowledged': +                    condition['conditiontype'] = 14 + +                if condition['operator'] == '=': +                    condition['operator'] = 0 + +                if condition['value'] == 'acknowledged': +                    condition['operator'] = 1 +                else: +                    condition['operator'] = 0 + + +    return inc_operations + +def get_operation_evaltype(inc_type): +    '''get the operation evaltype''' +    rval = 0 +    if inc_type == 'and/or': +        rval = 0 +    elif inc_type == 'and': +        rval = 1 +    elif inc_type == 'or': +        rval = 2 +    elif inc_type == 'custom': +        rval = 3 + +    return rval + +def get_action_conditions(zapi, event_source, inc_conditions): +    '''Convert the conditions into syntax for api''' + +    calc_type = inc_conditions.pop('calculation_type') +    inc_conditions['evaltype'] = get_operation_evaltype(calc_type) +    for cond in inc_conditions['conditions']: + +        cond['operator'] = get_condition_operator(cond['operator']) +        # Based on conditiontype we need to set the proper value +        # e.g. conditiontype = hostgroup then the value needs to be a hostgroup id +        # e.g. conditiontype = host the value needs to be a host id +        cond['conditiontype'] = get_condition_type(event_source, cond['conditiontype']) +        if cond['conditiontype'] == 0: +            cond['value'] = get_host_group_id_by_name(zapi, cond['value']) +        elif cond['conditiontype'] == 1: +            cond['value'] = get_host_id_by_name(zapi, cond['value']) +        elif cond['conditiontype'] == 4: +            cond['value'] = get_priority(cond['value']) + +        elif cond['conditiontype'] == 5: +            cond['value'] = get_trigger_value(cond['value']) +        elif cond['conditiontype'] == 13: +            cond['value'] = get_template_id_by_name(zapi, cond['value']) +        elif cond['conditiontype'] == 16: +            cond['value'] = '' + +    return inc_conditions + + +def get_send_recovery(send_recovery): +    '''Get the integer value''' +    rval = 0 +    if send_recovery: +        rval = 1 + +    return rval + +# The branches are needed for CRUD and error handling +# pylint: disable=too-many-branches +def main(): +    ''' +    ansible zabbix module for zbx_item +    ''' + + +    module = AnsibleModule( +        argument_spec=dict( +            zbx_server=dict(default='https://localhost/zabbix/api_jsonrpc.php', type='str'), +            zbx_user=dict(default=os.environ.get('ZABBIX_USER', None), type='str'), +            zbx_password=dict(default=os.environ.get('ZABBIX_PASSWORD', None), type='str'), +            zbx_debug=dict(default=False, type='bool'), + +            name=dict(default=None, type='str'), +            event_source=dict(default='trigger', choices=['trigger', 'discovery', 'auto', 'internal'], type='str'), +            action_subject=dict(default="{TRIGGER.NAME}: {TRIGGER.STATUS}", type='str'), +            action_message=dict(default="{TRIGGER.NAME}: {TRIGGER.STATUS}\r\n" + +                                "Last value: {ITEM.LASTVALUE}\r\n\r\n{TRIGGER.URL}", type='str'), +            reply_subject=dict(default="{TRIGGER.NAME}: {TRIGGER.STATUS}", type='str'), +            reply_message=dict(default="Trigger: {TRIGGER.NAME}\r\nTrigger status: {TRIGGER.STATUS}\r\n" + +                               "Trigger severity: {TRIGGER.SEVERITY}\r\nTrigger URL: {TRIGGER.URL}\r\n\r\n" + +                               "Item values:\r\n\r\n1. {ITEM.NAME1} ({HOST.NAME1}:{ITEM.KEY1}): " + +                               "{ITEM.VALUE1}\r\n2. {ITEM.NAME2} ({HOST.NAME2}:{ITEM.KEY2}): " + +                               "{ITEM.VALUE2}\r\n3. {ITEM.NAME3} ({HOST.NAME3}:{ITEM.KEY3}): " + +                               "{ITEM.VALUE3}", type='str'), +            send_recovery=dict(default=False, type='bool'), +            status=dict(default=None, type='str'), +            escalation_time=dict(default=60, type='int'), +            conditions_filter=dict(default=None, type='dict'), +            operations=dict(default=None, type='list'), +            state=dict(default='present', type='str'), +        ), +        #supports_check_mode=True +    ) + +    zapi = ZabbixAPI(ZabbixConnection(module.params['zbx_server'], +                                      module.params['zbx_user'], +                                      module.params['zbx_password'], +                                      module.params['zbx_debug'])) + +    #Set the instance and the template for the rest of the calls +    zbx_class_name = 'action' +    state = module.params['state'] + +    content = zapi.get_content(zbx_class_name, +                               'get', +                               {'search': {'name': module.params['name']}, +                                'selectFilter': 'extend', +                                'selectOperations': 'extend', +                               }) + +    #******# +    # GET +    #******# +    if state == 'list': +        module.exit_json(changed=False, results=content['result'], state="list") + +    #******# +    # DELETE +    #******# +    if state == 'absent': +        if not exists(content): +            module.exit_json(changed=False, state="absent") + +        content = zapi.get_content(zbx_class_name, 'delete', [content['result'][0]['itemid']]) +        module.exit_json(changed=True, results=content['result'], state="absent") + +    # Create and Update +    if state == 'present': + +        conditions = get_action_conditions(zapi, module.params['event_source'], module.params['conditions_filter']) +        operations = get_action_operations(zapi, module.params['operations']) +        params = {'name': module.params['name'], +                  'esc_period': module.params['escalation_time'], +                  'eventsource': get_event_source(module.params['event_source']), +                  'status': get_status(module.params['status']), +                  'def_shortdata': module.params['action_subject'], +                  'def_longdata': module.params['action_message'], +                  'r_shortdata': module.params['reply_subject'], +                  'r_longdata': module.params['reply_message'], +                  'recovery_msg': get_send_recovery(module.params['send_recovery']), +                  'filter': conditions, +                  'operations': operations, +                 } + +        # Remove any None valued params +        _ = [params.pop(key, None) for key in params.keys() if params[key] is None] + +        #******# +        # CREATE +        #******# +        if not exists(content): +            content = zapi.get_content(zbx_class_name, 'create', params) + +            if content.has_key('error'): +                module.exit_json(failed=True, changed=True, results=content['error'], state="present") + +            module.exit_json(changed=True, results=content['result'], state='present') + + +        ######## +        # UPDATE +        ######## +        _ = params.pop('hostid', None) +        differences = {} +        zab_results = content['result'][0] +        for key, value in params.items(): + +            if key == 'operations': +                ops = operation_differences(zab_results[key], value) +                if ops: +                    differences[key] = ops + +            elif key == 'filter': +                filters = filter_differences(zab_results[key], value) +                if filters: +                    differences[key] = filters + +            elif zab_results[key] != value and zab_results[key] != str(value): +                differences[key] = value + +        if not differences: +            module.exit_json(changed=False, results=zab_results, state="present") + +        # We have differences and need to update. +        # action update requires an id, filters, and operations +        differences['actionid'] = zab_results['actionid'] +        differences['operations'] = params['operations'] +        differences['filter'] = params['filter'] +        content = zapi.get_content(zbx_class_name, 'update', differences) + +        if content.has_key('error'): +            module.exit_json(failed=True, changed=False, results=content['error'], state="present") + +        module.exit_json(changed=True, results=content['result'], state="present") + +    module.exit_json(failed=True, +                     changed=False, +                     results='Unknown state passed. %s' % state, +                     state="unknown") + +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled +# import module snippets.  This are required +from ansible.module_utils.basic import * + +main() diff --git a/roles/lib_zabbix/library/zbx_trigger.py b/roles/lib_zabbix/library/zbx_trigger.py index a05de7e68..21d0fcfd2 100644 --- a/roles/lib_zabbix/library/zbx_trigger.py +++ b/roles/lib_zabbix/library/zbx_trigger.py @@ -74,6 +74,18 @@ def get_deps(zapi, deps):      return results + +def get_trigger_status(inc_status): +    ''' Determine the trigger's status +        0 is enabled +        1 is disabled +    ''' +    r_status = 0 +    if inc_status == 'disabled': +        r_status = 1 + +    return r_status +  def main():      '''      Create a trigger in zabbix @@ -103,6 +115,7 @@ def main():              dependencies=dict(default=[], type='list'),              priority=dict(default='avg', type='str'),              url=dict(default=None, type='str'), +            status=dict(default=None, type='str'),              state=dict(default='present', type='str'),          ),          #supports_check_mode=True @@ -145,6 +158,7 @@ def main():                    'dependencies': get_deps(zapi, module.params['dependencies']),                    'priority': get_priority(module.params['priority']),                    'url': module.params['url'], +                  'status': get_trigger_status(module.params['status']),                   }          # Remove any None valued params @@ -156,6 +170,10 @@ def main():          if not exists(content):              # if we didn't find it, create it              content = zapi.get_content(zbx_class_name, 'create', params) + +            if content.has_key('error'): +                module.exit_json(failed=True, changed=True, results=content['error'], state="present") +              module.exit_json(changed=True, results=content['result'], state='present')          ######## diff --git a/roles/lib_zabbix/library/zbx_triggerprototype.py b/roles/lib_zabbix/library/zbx_triggerprototype.py new file mode 100644 index 000000000..c1224b268 --- /dev/null +++ b/roles/lib_zabbix/library/zbx_triggerprototype.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python +''' +ansible module for zabbix triggerprototypes +''' +# vim: expandtab:tabstop=4:shiftwidth=4 +# +#   Zabbix triggerprototypes ansible module +# +# +#   Copyright 2015 Red Hat Inc. +# +#   Licensed under the Apache License, Version 2.0 (the "License"); +#   you may not use this file except in compliance with the License. +#   You may obtain a copy of the License at +# +#       http://www.apache.org/licenses/LICENSE-2.0 +# +#   Unless required by applicable law or agreed to in writing, software +#   distributed under the License is distributed on an "AS IS" BASIS, +#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#   See the License for the specific language governing permissions and +#   limitations under the License. +# + +# This is in place because each module looks similar to each other. +# These need duplicate code as their behavior is very similar +# but different for each zabbix class. +# pylint: disable=duplicate-code + +# pylint: disable=import-error +from openshift_tools.monitoring.zbxapi import ZabbixAPI, ZabbixConnection + +def exists(content, key='result'): +    ''' Check if key exists in content or the size of content[key] > 0 +    ''' +    if not content.has_key(key): +        return False + +    if not content[key]: +        return False + +    return True + +def get_priority(priority): +    ''' determine priority +    ''' +    prior = 0 +    if 'info' in priority: +        prior = 1 +    elif 'warn' in priority: +        prior = 2 +    elif 'avg' == priority or 'ave' in priority: +        prior = 3 +    elif 'high' in priority: +        prior = 4 +    elif 'dis' in priority: +        prior = 5 + +    return prior + +def get_trigger_status(inc_status): +    ''' Determine the trigger's status +        0 is enabled +        1 is disabled +    ''' +    r_status = 0 +    if inc_status == 'disabled': +        r_status = 1 + +    return r_status + + +def main(): +    ''' +    Create a triggerprototype in zabbix +    ''' + +    module = AnsibleModule( +        argument_spec=dict( +            zbx_server=dict(default='https://localhost/zabbix/api_jsonrpc.php', type='str'), +            zbx_user=dict(default=os.environ.get('ZABBIX_USER', None), type='str'), +            zbx_password=dict(default=os.environ.get('ZABBIX_PASSWORD', None), type='str'), +            zbx_debug=dict(default=False, type='bool'), +            name=dict(default=None, type='str'), +            expression=dict(default=None, type='str'), +            description=dict(default=None, type='str'), +            priority=dict(default='avg', type='str'), +            url=dict(default=None, type='str'), +            status=dict(default=None, type='str'), +            state=dict(default='present', type='str'), +        ), +        #supports_check_mode=True +    ) + +    zapi = ZabbixAPI(ZabbixConnection(module.params['zbx_server'], +                                      module.params['zbx_user'], +                                      module.params['zbx_password'], +                                      module.params['zbx_debug'])) + +    #Set the instance and the template for the rest of the calls +    zbx_class_name = 'triggerprototype' +    idname = "triggerid" +    state = module.params['state'] +    tname = module.params['name'] + +    content = zapi.get_content(zbx_class_name, +                               'get', +                               {'filter': {'description': tname}, +                                'expandExpression': True, +                                'selectDependencies': 'triggerid', +                               }) + +    # Get +    if state == 'list': +        module.exit_json(changed=False, results=content['result'], state="list") + +    # Delete +    if state == 'absent': +        if not exists(content): +            module.exit_json(changed=False, state="absent") +        content = zapi.get_content(zbx_class_name, 'delete', [content['result'][0][idname]]) +        module.exit_json(changed=True, results=content['result'], state="absent") + +    # Create and Update +    if state == 'present': +        params = {'description': tname, +                  'comments':  module.params['description'], +                  'expression':  module.params['expression'], +                  'priority': get_priority(module.params['priority']), +                  'url': module.params['url'], +                  'status': get_trigger_status(module.params['status']), +                 } + +        # Remove any None valued params +        _ = [params.pop(key, None) for key in params.keys() if params[key] is None] + +        #******# +        # CREATE +        #******# +        if not exists(content): +            # if we didn't find it, create it +            content = zapi.get_content(zbx_class_name, 'create', params) + +            if content.has_key('error'): +                module.exit_json(failed=True, changed=True, results=content['error'], state="present") + +            module.exit_json(changed=True, results=content['result'], state='present') + +        ######## +        # UPDATE +        ######## +        differences = {} +        zab_results = content['result'][0] +        for key, value in params.items(): + +            if zab_results[key] != value and zab_results[key] != str(value): +                differences[key] = value + +        if not differences: +            module.exit_json(changed=False, results=zab_results, state="present") + +        # We have differences and need to update +        differences[idname] = zab_results[idname] +        content = zapi.get_content(zbx_class_name, 'update', differences) +        module.exit_json(changed=True, results=content['result'], state="present") + + +    module.exit_json(failed=True, +                     changed=False, +                     results='Unknown state passed. %s' % state, +                     state="unknown") + +# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled +# import module snippets.  This are required +from ansible.module_utils.basic import * + +main() diff --git a/roles/lib_zabbix/library/zbx_user_media.py b/roles/lib_zabbix/library/zbx_user_media.py index 3f7760475..9ed838f81 100644 --- a/roles/lib_zabbix/library/zbx_user_media.py +++ b/roles/lib_zabbix/library/zbx_user_media.py @@ -54,8 +54,8 @@ def get_mtype(zapi, mtype):      except ValueError:          pass -    content = zapi.get_content('mediatype', 'get', {'search': {'description': mtype}}) -    if content.has_key['result'] and content['result']: +    content = zapi.get_content('mediatype', 'get', {'filter': {'description': mtype}}) +    if content.has_key('result') and content['result']:          return content['result'][0]['mediatypeid']      return None @@ -63,7 +63,7 @@ def get_mtype(zapi, mtype):  def get_user(zapi, user):      ''' Get userids from user aliases      ''' -    content = zapi.get_content('user', 'get', {'search': {'alias': user}}) +    content = zapi.get_content('user', 'get', {'filter': {'alias': user}})      if content['result']:          return content['result'][0] @@ -104,15 +104,17 @@ def find_media(medias, user_media):      ''' Find the user media in the list of medias      '''      for media in medias: -        if all([media[key] == user_media[key] for key in user_media.keys()]): +        if all([media[key] == str(user_media[key]) for key in user_media.keys()]):              return media      return None -def get_active(in_active): +def get_active(is_active):      '''Determine active value +       0 - enabled +       1 - disabled      '''      active = 1 -    if in_active: +    if is_active:          active = 0      return active @@ -128,6 +130,21 @@ def get_mediatype(zapi, mediatype, mediatype_desc):      return mtypeid +def preprocess_medias(zapi, medias): +    ''' Insert the correct information when processing medias ''' +    for media in medias: +        # Fetch the mediatypeid from the media desc (name) +        if media.has_key('mediatype'): +            media['mediatypeid'] = get_mediatype(zapi, mediatype=None, mediatype_desc=media.pop('mediatype')) + +        media['active'] = get_active(media.get('active')) +        media['severity'] = int(get_severity(media['severity'])) + +    return medias + +# Disabling branching as the logic requires branches. +# I've also added a few safeguards which required more branches. +# pylint: disable=too-many-branches  def main():      '''      Ansible zabbix module for mediatype @@ -166,11 +183,17 @@ def main():      # User media is fetched through the usermedia.get      zbx_user_query = get_zbx_user_query_data(zapi, module.params['login']) -    content = zapi.get_content('usermedia', 'get', zbx_user_query) - +    content = zapi.get_content('usermedia', 'get', +                               {'userids': [uid for user, uid in zbx_user_query.items()]}) +    ##### +    # Get +    #####      if state == 'list':          module.exit_json(changed=False, results=content['result'], state="list") +    ######## +    # Delete +    ########      if state == 'absent':          if not exists(content) or len(content['result']) == 0:              module.exit_json(changed=False, state="absent") @@ -178,13 +201,14 @@ def main():          if not module.params['login']:              module.exit_json(failed=True, changed=False, results='Must specifiy a user login.', state="absent") -        content = zapi.get_content(zbx_class_name, 'deletemedia', [content['result'][0][idname]]) +        content = zapi.get_content(zbx_class_name, 'deletemedia', [res[idname] for res in content['result']])          if content.has_key('error'):              module.exit_json(changed=False, results=content['error'], state="absent")          module.exit_json(changed=True, results=content['result'], state="absent") +    # Create and Update      if state == 'present':          active = get_active(module.params['active'])          mtypeid = get_mediatype(zapi, module.params['mediatype'], module.params['mediatype_desc']) @@ -197,13 +221,21 @@ def main():                         'severity': int(get_severity(module.params['severity'])),                         'period': module.params['period'],                        }] +        else: +            medias = preprocess_medias(zapi, medias)          params = {'users': [zbx_user_query],                    'medias': medias,                    'output': 'extend',                   } +        ######## +        # Create +        ########          if not exists(content): +            if not params['medias']: +                module.exit_json(changed=False, results=content['result'], state='present') +              # if we didn't find it, create it              content = zapi.get_content(zbx_class_name, 'addmedia', params) @@ -216,6 +248,9 @@ def main():          # If user params exists, check to see if they already exist in zabbix          # if they exist, then return as no update          # elif they do not exist, then take user params only +        ######## +        # Update +        ########          diff = {'medias': [], 'users': {}}          _ = [diff['medias'].append(media) for media in params['medias'] if not find_media(content['result'], media)] diff --git a/roles/lib_zabbix/tasks/create_template.yml b/roles/lib_zabbix/tasks/create_template.yml index fd0cdd46f..62259b680 100644 --- a/roles/lib_zabbix/tasks/create_template.yml +++ b/roles/lib_zabbix/tasks/create_template.yml @@ -52,3 +52,44 @@      url: "{{ item.url | default(None, True) }}"    with_items: template.ztriggers    when: template.ztriggers is defined + +- name: Create Discoveryrules +  zbx_discoveryrule: +    zbx_server: "{{ server }}" +    zbx_user: "{{ user }}" +    zbx_password: "{{ password }}" +    name: "{{ item.name }}" +    key: "{{ item.key }}" +    lifetime: "{{ item.lifetime }}" +    template_name: "{{ template.name }}" +    description: "{{ item.description | default('', True) }}" +  with_items: template.zdiscoveryrules +  when: template.zdiscoveryrules is defined + +- name: Create Item Prototypes +  zbx_itemprototype: +    zbx_server: "{{ server }}" +    zbx_user: "{{ user }}" +    zbx_password: "{{ password }}" +    name: "{{ item.name }}" +    key: "{{ item.key }}" +    discoveryrule_key: "{{ item.discoveryrule_key }}" +    value_type: "{{ item.value_type }}" +    template_name: "{{ template.name }}" +    applications: "{{ item.applications }}" +    description: "{{ item.description | default('', True) }}" +  with_items: template.zitemprototypes +  when: template.zitemprototypes is defined + +- name: Create Trigger Prototypes +  zbx_triggerprototype: +    zbx_server: "{{ server }}" +    zbx_user: "{{ user }}" +    zbx_password: "{{ password }}" +    name: "{{ item.name }}" +    expression: "{{ item.expression }}" +    url: "{{ item.url | default('', True) }}" +    priority: "{{ item.priority | default('average', True) }}" +    description: "{{ item.description | default('', True) }}" +  with_items: template.ztriggerprototypes +  when: template.ztriggerprototypes is defined diff --git a/roles/openshift_examples/defaults/main.yml b/roles/openshift_examples/defaults/main.yml index 3246790aa..7d4f100e3 100644 --- a/roles/openshift_examples/defaults/main.yml +++ b/roles/openshift_examples/defaults/main.yml @@ -14,3 +14,5 @@ db_templates_base: "{{ examples_base }}/db-templates"  xpaas_image_streams: "{{ examples_base }}/xpaas-streams/jboss-image-streams.json"  xpaas_templates_base: "{{ examples_base }}/xpaas-templates"  quickstarts_base: "{{ examples_base }}/quickstart-templates" + +openshift_examples_import_command: "create" diff --git a/roles/openshift_examples/tasks/main.yml b/roles/openshift_examples/tasks/main.yml index bfc6dfb0a..3a829a4c6 100644 --- a/roles/openshift_examples/tasks/main.yml +++ b/roles/openshift_examples/tasks/main.yml @@ -7,7 +7,7 @@  # RHEL and Centos image streams are mutually exclusive  - name: Import RHEL streams    command: > -    {{ openshift.common.client_binary }} create -n openshift -f {{ rhel_image_streams }} +    {{ openshift.common.client_binary }} {{ openshift_examples_import_command }} -n openshift -f {{ rhel_image_streams }}    when: openshift_examples_load_rhel    register: oex_import_rhel_streams    failed_when: "'already exists' not in oex_import_rhel_streams.stderr and oex_import_rhel_streams.rc != 0" @@ -15,7 +15,7 @@  - name: Import Centos Image streams    command: > -    {{ openshift.common.client_binary }} create -n openshift -f {{ centos_image_streams }} +    {{ openshift.common.client_binary }} {{ openshift_examples_import_command }} -n openshift -f {{ centos_image_streams }}    when: openshift_examples_load_centos | bool    register: oex_import_centos_streams    failed_when: "'already exists' not in oex_import_centos_streams.stderr and oex_import_centos_streams.rc != 0" @@ -23,7 +23,7 @@  - name: Import db templates    command: > -    {{ openshift.common.client_binary }} create -n openshift -f {{ db_templates_base }} +    {{ openshift.common.client_binary }} {{ openshift_examples_import_command }} -n openshift -f {{ db_templates_base }}    when: openshift_examples_load_db_templates | bool    register: oex_import_db_templates    failed_when: "'already exists' not in oex_import_db_templates.stderr and oex_import_db_templates.rc != 0" @@ -31,7 +31,7 @@  - name: Import quickstart-templates    command: > -    {{ openshift.common.client_binary }} create -n openshift -f {{ quickstarts_base }} +    {{ openshift.common.client_binary }} {{ openshift_examples_import_command }} -n openshift -f {{ quickstarts_base }}    when: openshift_examples_load_quickstarts    register: oex_import_quickstarts    failed_when: "'already exists' not in oex_import_quickstarts.stderr and oex_import_quickstarts.rc != 0" @@ -40,7 +40,7 @@  - name: Import xPaas image streams    command: > -    {{ openshift.common.client_binary }} create -n openshift -f {{ xpaas_image_streams }} +    {{ openshift.common.client_binary }} {{ openshift_examples_import_command }} -n openshift -f {{ xpaas_image_streams }}    when: openshift_examples_load_xpaas | bool    register: oex_import_xpaas_streams    failed_when: "'already exists' not in oex_import_xpaas_streams.stderr and oex_import_xpaas_streams.rc != 0" @@ -48,7 +48,7 @@  - name: Import xPaas templates    command: > -    {{ openshift.common.client_binary }} create -n openshift -f {{ xpaas_templates_base }} +    {{ openshift.common.client_binary }} {{ openshift_examples_import_command }} -n openshift -f {{ xpaas_templates_base }}    when: openshift_examples_load_xpaas | bool    register: oex_import_xpaas_templates    failed_when: "'already exists' not in oex_import_xpaas_templates.stderr and oex_import_xpaas_templates.rc != 0" diff --git a/roles/openshift_facts/library/openshift_facts.py b/roles/openshift_facts/library/openshift_facts.py index 991b8da66..aeab7152c 100755 --- a/roles/openshift_facts/library/openshift_facts.py +++ b/roles/openshift_facts/library/openshift_facts.py @@ -296,9 +296,8 @@ def set_fluentd_facts_if_unset(facts):      """      if 'common' in facts: -        deployment_type = facts['common']['deployment_type']          if 'use_fluentd' not in facts['common']: -            use_fluentd = True if deployment_type == 'online' else False +            use_fluentd = False              facts['common']['use_fluentd'] = use_fluentd      return facts @@ -475,6 +474,7 @@ def set_deployment_facts_if_unset(facts):              if deployment_type in ['enterprise', 'online']:                  data_dir = '/var/lib/openshift'              facts['common']['data_dir'] = data_dir +        facts['common']['version'] = get_openshift_version()      for role in ('master', 'node'):          if role in facts: @@ -599,6 +599,19 @@ def get_current_config(facts):      return current_config +def get_openshift_version(): +    """ Get current version of openshift on the host + +        Returns: +            version: the current openshift version +    """ +    if os.path.isfile('/usr/bin/openshift'): +        _, output, _ = module.run_command(['/usr/bin/openshift', 'version']) +        versions = dict(e.split(' v') for e in output.splitlines()) +        version = versions.get('openshift', '') + +        #TODO: acknowledge the possility of a containerized install +    return version  def apply_provider_facts(facts, provider_facts):      """ Apply provider facts to supplied facts dict @@ -644,7 +657,7 @@ def merge_facts(orig, new):      facts = dict()      for key, value in orig.iteritems():          if key in new: -            if isinstance(value, dict): +            if isinstance(value, dict) and isinstance(new[key], dict):                  facts[key] = merge_facts(value, new[key])              else:                  facts[key] = copy.copy(new[key]) diff --git a/roles/openshift_master/tasks/main.yml b/roles/openshift_master/tasks/main.yml index b57711b58..fa12005ab 100644 --- a/roles/openshift_master/tasks/main.yml +++ b/roles/openshift_master/tasks/main.yml @@ -100,6 +100,7 @@    template:      dest: "{{ openshift_master_scheduler_conf }}"      src: scheduler.json.j2 +    backup: true    notify:    - restart master @@ -129,6 +130,7 @@    template:      dest: "{{ openshift_master_config_file }}"      src: master.yaml.v1.j2 +    backup: true    notify:    - restart master diff --git a/roles/openshift_node/tasks/main.yml b/roles/openshift_node/tasks/main.yml index 1986b631e..e8cc499c0 100644 --- a/roles/openshift_node/tasks/main.yml +++ b/roles/openshift_node/tasks/main.yml @@ -47,6 +47,7 @@    template:      dest: "{{ openshift_node_config_file }}"      src: node.yaml.v1.j2 +    backup: true    notify:    - restart node diff --git a/roles/openshift_serviceaccounts/tasks/main.yml b/roles/openshift_serviceaccounts/tasks/main.yml index 9665d0a72..d93a25a21 100644 --- a/roles/openshift_serviceaccounts/tasks/main.yml +++ b/roles/openshift_serviceaccounts/tasks/main.yml @@ -23,4 +23,4 @@    with_items: accounts  - name: Apply new scc rules for service accounts -  command: "{{ openshift.common.client_binary }} replace -f /tmp/scc.yaml" +  command: "{{ openshift.common.client_binary }} update -f /tmp/scc.yaml" diff --git a/roles/os_zabbix/vars/template_os_linux.yml b/roles/os_zabbix/vars/template_os_linux.yml index 3173c79b2..c81f39c58 100644 --- a/roles/os_zabbix/vars/template_os_linux.yml +++ b/roles/os_zabbix/vars/template_os_linux.yml @@ -10,17 +10,20 @@ g_template_os_linux:    - key: kernel.all.cpu.wait.total      applications:      - Kernel -    value_type: int +    value_type: float +    units: '%'    - key: kernel.all.cpu.irq.hard      applications:      - Kernel -    value_type: int +    value_type: float +    units: '%'    - key: kernel.all.cpu.idle      applications:      - Kernel -    value_type: int +    value_type: float +    units: '%'    - key: kernel.uname.distro      applications: @@ -35,7 +38,8 @@ g_template_os_linux:    - key: kernel.all.cpu.irq.soft      applications:      - Kernel -    value_type: int +    value_type: float +    units: '%'    - key: kernel.all.load.15_minute      applications: @@ -45,7 +49,8 @@ g_template_os_linux:    - key: kernel.all.cpu.sys      applications:      - Kernel -    value_type: int +    value_type: float +    units: '%'    - key: kernel.all.load.5_minute      applications: @@ -55,7 +60,8 @@ g_template_os_linux:    - key: kernel.all.cpu.nice      applications:      - Kernel -    value_type: int +    value_type: float +    units: '%'    - key: kernel.all.load.1_minute      applications: @@ -75,7 +81,8 @@ g_template_os_linux:    - key: kernel.all.cpu.user      applications:      - Kernel -    value_type: int +    value_type: float +    units: '%'    - key: kernel.uname.machine      applications: @@ -90,7 +97,8 @@ g_template_os_linux:    - key: kernel.all.cpu.steal      applications:      - Kernel -    value_type: int +    value_type: float +    units: '%'    - key: kernel.all.pswitch      applications: @@ -191,6 +199,35 @@ g_template_os_linux:      - Disk      value_type: float + +  zdiscoveryrules: +  - name: disc.filesys +    key: disc.filesys +    lifetime: 1 +    template_name: Template OS Linux +    description: "Dynamically register the filesystems" + +  zitemprototypes: +  - discoveryrule_key: disc.filesys +    template_name: Template OS Linux +    name: "disc.filesys.full.{#OSO_FILESYS}" +    key: "disc.filesys.full[{#OSO_FILESYS}]" +    value_type: float +    description: "PCP filesys.full option.  This is the percent full returned from pcp filesys.full" +    applications: +    - Disk + +  ztriggerprototypes: +  - name: 'Filesystem: {#OSO_FILESYS} has less than 10% free on {HOST.NAME}' +    expression: '{Template OS Linux:disc.filesys.full[{#OSO_FILESYS}].last()}>90' +    url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_filesys_full.asciidoc' +    priority: warn + +  - name: 'Filesystem: {#OSO_FILESYS} has less than 5% free on {HOST.NAME}' +    expression: '{Template OS Linux:disc.filesys.full[{#OSO_FILESYS}].last()}>95' +    url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_filesys_full.asciidoc' +    priority: high +    ztriggers:    - name: 'Filesystem: / has less than 10% free on {HOST.NAME}'      expression: '{Template OS Linux:filesys.full.xvda2.last()}>90' @@ -222,3 +259,18 @@ g_template_os_linux:      url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_memory.asciidoc'      priority: warn      description: 'Alert on less than 30MegaBytes.  This is 30 Million Bytes.  30000 KB x 1024' + +    #  CPU Utilization # +  - name: 'CPU idle less than 5% on {HOST.NAME}' +    expression: '{Template OS Linux:kernel.all.cpu.idle.last()}<5 and {Template OS Linux:kernel.all.cpu.idle.last(#2)}<5' +    url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_cpu_idle.asciidoc' +    priority: high +    description: 'CPU is less than 5% idle' + +  - name: 'CPU idle less than 10% on {HOST.NAME}' +    expression: '{Template OS Linux:kernel.all.cpu.idle.last()}<10 and {Template OS Linux:kernel.all.cpu.idle.last(#2)}<10' +    url: 'https://github.com/openshift/ops-sop/blob/master/V3/Alerts/check_cpu_idle.asciidoc' +    priority: warn +    description: 'CPU is less than 10% idle' +    dependencies: +    - 'CPU idle less than 5% on {HOST.NAME}' | 
