diff options
31 files changed, 530 insertions, 101 deletions
diff --git a/README_OSE.md b/README_OSE.md index 41a6f2935..dffabc714 100644 --- a/README_OSE.md +++ b/README_OSE.md @@ -80,7 +80,7 @@ ansible_ssh_user=root deployment_type=enterprise # Pre-release registry URL -openshift_registry_url=docker-buildvm-rhose.usersys.redhat.com:5000/openshift3_beta/ose-${component}:${version} +oreg_url=docker-buildvm-rhose.usersys.redhat.com:5000/openshift3_beta/ose-${component}:${version} # Pre-release additional repo openshift_additional_repos=[{'id': 'ose-devel', 'name': 'ose-devel', diff --git a/git/.pylintrc b/git/.pylintrc index 6cf68f7d6..af8f1656f 100644 --- a/git/.pylintrc +++ b/git/.pylintrc @@ -286,7 +286,7 @@ notes=FIXME,XXX,TODO [FORMAT] # Maximum number of characters on a single line. -max-line-length=100 +max-line-length=120 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )?<?https?://\S+>?$ diff --git a/inventory/byo/hosts b/inventory/byo/hosts index 728eec8aa..9a1cbce29 100644 --- a/inventory/byo/hosts +++ b/inventory/byo/hosts @@ -17,7 +17,7 @@ ansible_ssh_user=root deployment_type=enterprise # Pre-release registry URL -openshift_registry_url=docker-buildvm-rhose.usersys.redhat.com:5000/openshift3_beta/ose-${component}:${version} +oreg_url=docker-buildvm-rhose.usersys.redhat.com:5000/openshift3_beta/ose-${component}:${version} # Pre-release additional repo #openshift_additional_repos=[{'id': 'ose-devel', 'name': 'ose-devel', 'baseurl': 'http://buildvm-devops.usersys.redhat.com/puddle/build/OpenShiftEnterprise/3.0/latest/RH7-RHOSE-3.0/$basearch/os', 'enabled': 1, 'gpgcheck': 0}] diff --git a/playbooks/aws/openshift-cluster/launch.yml b/playbooks/aws/openshift-cluster/launch.yml index 3eb5496e4..33e1ec25d 100644 --- a/playbooks/aws/openshift-cluster/launch.yml +++ b/playbooks/aws/openshift-cluster/launch.yml @@ -25,6 +25,14 @@ cluster: "{{ cluster_id }}" type: "{{ k8s_type }}" + - set_fact: + a_master: "{{ master_names[0] }}" + - add_host: name={{ a_master }} groups=service_master + - include: update.yml +- include: ../../common/openshift-cluster/create_services.yml + vars: + g_svc_master: "{{ service_master }}" + - include: list.yml diff --git a/playbooks/common/openshift-cluster/create_services.yml b/playbooks/common/openshift-cluster/create_services.yml new file mode 100644 index 000000000..e70709d19 --- /dev/null +++ b/playbooks/common/openshift-cluster/create_services.yml @@ -0,0 +1,8 @@ +--- +- name: Deploy OpenShift Services + hosts: "{{ g_svc_master }}" + connection: ssh + gather_facts: yes + roles: + - openshift_registry + - openshift_router diff --git a/playbooks/common/openshift-master/config.yml b/playbooks/common/openshift-master/config.yml index 05822d118..4df64e95f 100644 --- a/playbooks/common/openshift-master/config.yml +++ b/playbooks/common/openshift-master/config.yml @@ -6,6 +6,7 @@ roles: - openshift_master - { role: openshift_sdn_master, when: openshift.common.use_openshift_sdn | bool } + - { role: fluentd_master, when openshift.common.use_fluentd | bool } tasks: - name: Create group for deployment type group_by: key=oo_masters_deployment_type_{{ openshift.common.deployment_type }} diff --git a/playbooks/common/openshift-node/config.yml b/playbooks/common/openshift-node/config.yml index 96641a274..70711e39b 100644 --- a/playbooks/common/openshift-node/config.yml +++ b/playbooks/common/openshift-node/config.yml @@ -96,6 +96,7 @@ roles: - openshift_node - { role: openshift_sdn_node, when: openshift.common.use_openshift_sdn | bool } + - { role: fluentd_node, when: openshift.common.use_fluentd | bool } tasks: - name: Create group for deployment type group_by: key=oo_nodes_deployment_type_{{ openshift.common.deployment_type }} diff --git a/playbooks/gce/openshift-cluster/launch.yml b/playbooks/gce/openshift-cluster/launch.yml index 771f51e91..35737f03d 100644 --- a/playbooks/gce/openshift-cluster/launch.yml +++ b/playbooks/gce/openshift-cluster/launch.yml @@ -23,6 +23,22 @@ cluster: "{{ cluster_id }}" type: "{{ k8s_type }}" + - set_fact: + a_master: "{{ master_names[0] }}" + - add_host: name={{ a_master }} groups=service_master + - include: update.yml +- name: Deploy OpenShift Services + hosts: service_master + connection: ssh + gather_facts: yes + roles: + - openshift_registry + - openshift_router + +- include: ../../common/openshift-cluster/create_services.yml + vars: + g_svc_master: "{{ service_master }}" + - include: list.yml diff --git a/playbooks/gce/openshift-cluster/list.yml b/playbooks/gce/openshift-cluster/list.yml index 962381306..5ba0f5a48 100644 --- a/playbooks/gce/openshift-cluster/list.yml +++ b/playbooks/gce/openshift-cluster/list.yml @@ -16,7 +16,7 @@ ansible_sudo: "{{ deployment_vars[deployment_type].sudo }}" with_items: groups[scratch_group] | default([]) | difference(['localhost']) | difference(groups.status_terminated) -- name: List Hosts +- name: List instance(s) hosts: oo_list_hosts gather_facts: no tasks: diff --git a/roles/fluentd_master/tasks/main.yml b/roles/fluentd_master/tasks/main.yml new file mode 100644 index 000000000..28caaa5b8 --- /dev/null +++ b/roles/fluentd_master/tasks/main.yml @@ -0,0 +1,46 @@ +--- +# TODO: Update fluentd install and configuration when packaging is complete +- name: download and install td-agent + yum: + name: 'http://packages.treasuredata.com/2/redhat/7/x86_64/td-agent-2.2.0-0.x86_64.rpm' + state: present + +- name: Verify fluentd plugin installed + command: '/opt/td-agent/embedded/bin/gem query -i fluent-plugin-kubernetes' + register: _fluent_plugin_check + ignore_errors: yes + +- name: install Kubernetes fluentd plugin + command: '/opt/td-agent/embedded/bin/gem install fluent-plugin-kubernetes' + when: _fluent_plugin_check.rc == 1 + +- name: Creates directories + file: + path: "{{ item }}" + state: directory + group: 'td-agent' + owner: 'td-agent' + mode: 0755 + with_items: ['/etc/td-agent/config.d'] + +- name: Add include to td-agent configuration + lineinfile: + dest: '/etc/td-agent/td-agent.conf' + regexp: '^@include config.d' + line: '@include config.d/*.conf' + state: present + +- name: install Kubernetes fluentd configuration file + template: + src: kubernetes.conf.j2 + dest: /etc/td-agent/config.d/kubernetes.conf + group: 'td-agent' + owner: 'td-agent' + mode: 0444 + +- name: ensure td-agent is running + service: + name: 'td-agent' + state: started + enabled: yes + diff --git a/roles/fluentd_master/templates/kubernetes.conf.j2 b/roles/fluentd_master/templates/kubernetes.conf.j2 new file mode 100644 index 000000000..7b5c86062 --- /dev/null +++ b/roles/fluentd_master/templates/kubernetes.conf.j2 @@ -0,0 +1,9 @@ +<match kubernetes.**> + type file + path /var/log/td-agent/containers.log + time_slice_format %Y%m%d + time_slice_wait 10m + time_format %Y%m%dT%H%M%S%z + compress gzip + utc +</match> diff --git a/roles/fluentd_node/tasks/main.yml b/roles/fluentd_node/tasks/main.yml new file mode 100644 index 000000000..2526057cb --- /dev/null +++ b/roles/fluentd_node/tasks/main.yml @@ -0,0 +1,54 @@ +--- +# TODO: Update fluentd install and configuration when packaging is complete +- name: download and install td-agent + yum: + name: 'http://packages.treasuredata.com/2/redhat/7/x86_64/td-agent-2.2.0-0.x86_64.rpm' + state: present + +- name: Verify fluentd plugin installed + command: '/opt/td-agent/embedded/bin/gem query -i fluent-plugin-kubernetes' + register: _fluent_plugin_check + ignore_errors: yes + +- name: install Kubernetes fluentd plugin + command: '/opt/td-agent/embedded/bin/gem install fluent-plugin-kubernetes' + when: _fluent_plugin_check.rc == 1 + +- name: Override td-agent configuration file + template: + src: td-agent.j2 + dest: /etc/sysconfig/td-agent + group: 'td-agent' + owner: 'td-agent' + mode: 0444 + +- name: Creates directories + file: + path: "{{ item }}" + state: directory + group: 'td-agent' + owner: 'td-agent' + mode: 0755 + with_items: ['/etc/td-agent/config.d', '/var/log/td-agent/tmp'] + +- name: Add include to td-agent configuration + lineinfile: + dest: '/etc/td-agent/td-agent.conf' + regexp: '^@include config.d' + line: '@include config.d/*.conf' + state: present + +- name: install Kubernetes fluentd configuration file + template: + src: kubernetes.conf.j2 + dest: /etc/td-agent/config.d/kubernetes.conf + group: 'td-agent' + owner: 'td-agent' + mode: 0444 + +- name: ensure td-agent is running + service: + name: 'td-agent' + state: started + enabled: yes + diff --git a/roles/fluentd_node/templates/kubernetes.conf.j2 b/roles/fluentd_node/templates/kubernetes.conf.j2 new file mode 100644 index 000000000..5f1eecb20 --- /dev/null +++ b/roles/fluentd_node/templates/kubernetes.conf.j2 @@ -0,0 +1,53 @@ +<source> + type tail + path /var/lib/docker/containers/*/*-json.log + pos_file /var/log/td-agent/tmp/fluentd-docker.pos + time_format %Y-%m-%dT%H:%M:%S + tag docker.* + format json + read_from_head true +</source> + +<match docker.var.lib.docker.containers.*.*.log> + type kubernetes + container_id ${tag_parts[5]} + tag docker.${name} +</match> + +<match kubernetes> + type copy + + <store> + type forward + send_timeout 60s + recover_wait 10s + heartbeat_interval 1s + phi_threshold 16 + hard_timeout 60s + log_level trace + require_ack_response true + heartbeat_type tcp + + <server> + name {{groups['oo_first_master'][0]}} + host {{hostvars[groups['oo_first_master'][0]].openshift.common.hostname}} + port 24224 + weight 60 + </server> + + <secondary> + type file + path /var/log/td-agent/forward-failed + </secondary> + </store> + + <store> + type file + path /var/log/td-agent/containers.log + time_slice_format %Y%m%d + time_slice_wait 10m + time_format %Y%m%dT%H%M%S%z + compress gzip + utc + </store> +</match> diff --git a/roles/fluentd_node/templates/td-agent.j2 b/roles/fluentd_node/templates/td-agent.j2 new file mode 100644 index 000000000..7245e11ec --- /dev/null +++ b/roles/fluentd_node/templates/td-agent.j2 @@ -0,0 +1,2 @@ +DAEMON_ARGS= +TD_AGENT_ARGS="/usr/sbin/td-agent --log /var/log/td-agent/td-agent.log --use-v1-config" diff --git a/roles/openshift_common/tasks/main.yml b/roles/openshift_common/tasks/main.yml index c55677c3f..5bd8690a7 100644 --- a/roles/openshift_common/tasks/main.yml +++ b/roles/openshift_common/tasks/main.yml @@ -10,6 +10,7 @@ public_hostname: "{{ openshift_public_hostname | default(None) }}" public_ip: "{{ openshift_public_ip | default(None) }}" use_openshift_sdn: "{{ openshift_use_openshift_sdn | default(None) }}" + use_fluentd: "{{ openshift_use_fluentd | default(True) }}" deployment_type: "{{ openshift_deployment_type }}" - name: Set hostname hostname: name={{ openshift.common.hostname }} diff --git a/roles/openshift_master/README.md b/roles/openshift_master/README.md index 9f9d0a613..3178e318c 100644 --- a/roles/openshift_master/README.md +++ b/roles/openshift_master/README.md @@ -17,7 +17,7 @@ From this role: |-------------------------------------|-----------------------|--------------------------------------------------| | openshift_master_debug_level | openshift_debug_level | Verbosity of the debug logs for openshift-master | | openshift_node_ips | [] | List of the openshift node ip addresses to pre-register when openshift-master starts up | -| openshift_registry_url | UNDEF | Default docker registry to use | +| oreg_url | UNDEF | Default docker registry to use | | openshift_master_api_port | UNDEF | | | openshift_master_console_port | UNDEF | | | openshift_master_api_url | UNDEF | | diff --git a/roles/openshift_master/defaults/main.yml b/roles/openshift_master/defaults/main.yml index 56cf43531..11195e83e 100644 --- a/roles/openshift_master/defaults/main.yml +++ b/roles/openshift_master/defaults/main.yml @@ -11,6 +11,10 @@ os_firewall_allow: port: 53/tcp - service: OpenShift dns udp port: 53/udp +- service: Fluentd td-agent tcp + port: 24224/tcp +- service: Fluentd td-agent udp + port: 24224/udp os_firewall_deny: - service: OpenShift api http port: 8080/tcp diff --git a/roles/openshift_master/tasks/main.yml b/roles/openshift_master/tasks/main.yml index f9e6199a5..ac96e2b48 100644 --- a/roles/openshift_master/tasks/main.yml +++ b/roles/openshift_master/tasks/main.yml @@ -49,15 +49,15 @@ # TODO: should probably use a template lookup for this # TODO: should allow for setting --etcd, --kubernetes options # TODO: recreate config if values change -- name: Use enterprise default for openshift_registry_url if not set +- name: Use enterprise default for oreg_url if not set set_fact: - openshift_registry_url: "openshift3_beta/ose-${component}:${version}" - when: openshift.common.deployment_type == 'enterprise' and openshift_registry_url is not defined + oreg_url: "openshift3_beta/ose-${component}:${version}" + when: openshift.common.deployment_type == 'enterprise' and oreg_url is not defined -- name: Use online default for openshift_registry_url if not set +- name: Use online default for oreg_url if not set set_fact: - openshift_registry_url: "docker-registry.ops.rhcloud.com/openshift3_beta/ose-${component}:${version}" - when: openshift.common.deployment_type == 'online' and openshift_registry_url is not defined + oreg_url: "docker-registry.ops.rhcloud.com/openshift3_beta/ose-${component}:${version}" + when: openshift.common.deployment_type == 'online' and oreg_url is not defined - name: Create master config command: > @@ -67,7 +67,7 @@ --master={{ openshift.master.api_url }} --public-master={{ openshift.master.public_api_url }} --listen={{ 'https' if openshift.master.api_use_ssl else 'http' }}://0.0.0.0:{{ openshift.master.api_port }} - {{ ('--images=' ~ openshift_registry_url) if (openshift_registry_url | default('', true) != '') else '' }} + {{ ('--images=' ~ oreg_url) if (oreg_url | default('', true) != '') else '' }} {{ ('--nodes=' ~ openshift_node_ips | join(',')) if (openshift_node_ips | default('', true) != '') else '' }} args: chdir: "{{ openshift_cert_parent_dir }}" diff --git a/roles/openshift_node/README.md b/roles/openshift_node/README.md index 83359f164..c3c17b848 100644 --- a/roles/openshift_node/README.md +++ b/roles/openshift_node/README.md @@ -17,7 +17,7 @@ From this role: | Name | Default value | | |------------------------------------------|-----------------------|----------------------------------------| | openshift_node_debug_level | openshift_debug_level | Verbosity of the debug logs for openshift-node | -| openshift_registry_url | UNDEF (Optional) | Default docker registry to use | +| oreg_url | UNDEF (Optional) | Default docker registry to use | From openshift_common: | Name | Default Value | | diff --git a/roles/openshift_register_nodes/tasks/main.yml b/roles/openshift_register_nodes/tasks/main.yml index d4d72d126..dcb96bbf9 100644 --- a/roles/openshift_register_nodes/tasks/main.yml +++ b/roles/openshift_register_nodes/tasks/main.yml @@ -6,15 +6,15 @@ # TODO: use a template lookup here # TODO: create a failed_when condition -- name: Use enterprise default for openshift_registry_url if not set +- name: Use enterprise default for oreg_url if not set set_fact: - openshift_registry_url: "openshift3_beta/ose-${component}:${version}" - when: openshift.common.deployment_type == 'enterprise' and openshift_registry_url is not defined + oreg_url: "openshift3_beta/ose-${component}:${version}" + when: openshift.common.deployment_type == 'enterprise' and oreg_url is not defined -- name: Use online default for openshift_registry_url if not set +- name: Use online default for oreg_url if not set set_fact: - openshift_registry_url: "docker-registry.ops.rhcloud.com/openshift3_beta/ose-${component}:${version}" - when: openshift.common.deployment_type == 'online' and openshift_registry_url is not defined + oreg_url: "docker-registry.ops.rhcloud.com/openshift3_beta/ose-${component}:${version}" + when: openshift.common.deployment_type == 'online' and oreg_url is not defined - name: Create node config command: > @@ -30,7 +30,7 @@ --certificate-authority={{ openshift_master_ca_cert }} --signer-serial={{ openshift_master_ca_dir }}/serial.txt --node-client-certificate-authority={{ openshift_master_ca_cert }} - {{ ('--images=' ~ openshift_registry_url) if openshift_registry_url is defined else '' }} + {{ ('--images=' ~ oreg_url) if oreg_url is defined else '' }} --listen=https://0.0.0.0:10250 args: chdir: "{{ openshift_cert_parent_dir }}" diff --git a/roles/openshift_registry/README.md b/roles/openshift_registry/README.md new file mode 100644 index 000000000..202c818b8 --- /dev/null +++ b/roles/openshift_registry/README.md @@ -0,0 +1,42 @@ +OpenShift Container Docker Registry +=================================== + +OpenShift Docker Registry service installation + +Requirements +------------ + +Running OpenShift cluster + +Role Variables +-------------- + +From this role: +| Name | Default value | | +|--------------------|-------------------------------------------------------|---------------------| +| | | | + +From openshift_common: +| Name | Default value | | +|-----------------------|---------------|--------------------------------------| +| openshift_debug_level | 0 | Global openshift debug log verbosity | + + +Dependencies +------------ + +Example Playbook +---------------- + +TODO + +License +------- + +Apache License, Version 2.0 + +Author Information +------------------ + +Red Hat openshift@redhat.com + diff --git a/roles/openshift_registry/handlers/main.yml b/roles/openshift_registry/handlers/main.yml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/roles/openshift_registry/handlers/main.yml diff --git a/roles/openshift_registry/meta/main.yml b/roles/openshift_registry/meta/main.yml new file mode 100644 index 000000000..93b6797d1 --- /dev/null +++ b/roles/openshift_registry/meta/main.yml @@ -0,0 +1,13 @@ +--- +galaxy_info: + author: OpenShift Red Hat + description: OpenShift Embedded Docker Registry + company: Red Hat, Inc. + license: Apache License, Version 2.0 + min_ansible_version: 1.7 + platforms: + - name: EL + versions: + - 7 + categories: + - cloud diff --git a/roles/openshift_registry/tasks/main.yml b/roles/openshift_registry/tasks/main.yml new file mode 100644 index 000000000..7e6982d99 --- /dev/null +++ b/roles/openshift_registry/tasks/main.yml @@ -0,0 +1,8 @@ +--- +- set_fact: _oreg_images="--images={{ oreg_url|quote }}" + when: oreg_url is defined + +- name: Deploy OpenShift Registry + command: openshift admin registry --create --credentials=/var/lib/openshift/openshift.local.certificates/openshift-registry/.kubeconfig {{ _oreg_images|default() }} + register: _oreg_results + changed_when: "'service exists' not in _oreg_results.stdout" diff --git a/roles/openshift_registry/vars/main.yml b/roles/openshift_registry/vars/main.yml new file mode 100644 index 000000000..cd21505a4 --- /dev/null +++ b/roles/openshift_registry/vars/main.yml @@ -0,0 +1,2 @@ +--- + diff --git a/roles/openshift_router/README.md b/roles/openshift_router/README.md new file mode 100644 index 000000000..6d8ee25c6 --- /dev/null +++ b/roles/openshift_router/README.md @@ -0,0 +1,41 @@ +OpenShift Container Router +========================== + +OpenShift Router service installation + +Requirements +------------ + +Running OpenShift cluster + +Role Variables +-------------- + +From this role: +| Name | Default value | | +|--------------------|-------------------------------------------------------|---------------------| +| | | | + +From openshift_common: +| Name | Default value | | +|-----------------------|---------------|--------------------------------------| +| openshift_debug_level | 0 | Global openshift debug log verbosity | + +Dependencies +------------ + +Example Playbook +---------------- + +TODO + +License +------- + +Apache License, Version 2.0 + +Author Information +------------------ + +Red Hat openshift@redhat.com + diff --git a/roles/openshift_router/handlers/main.yml b/roles/openshift_router/handlers/main.yml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/roles/openshift_router/handlers/main.yml diff --git a/roles/openshift_router/meta/main.yml b/roles/openshift_router/meta/main.yml new file mode 100644 index 000000000..0471e5e14 --- /dev/null +++ b/roles/openshift_router/meta/main.yml @@ -0,0 +1,13 @@ +--- +galaxy_info: + author: OpenShift Red Hat + description: OpenShift Embedded Router + company: Red Hat, Inc. + license: Apache License, Version 2.0 + min_ansible_version: 1.7 + platforms: + - name: EL + versions: + - 7 + categories: + - cloud diff --git a/roles/openshift_router/tasks/main.yml b/roles/openshift_router/tasks/main.yml new file mode 100644 index 000000000..f1ee99dd3 --- /dev/null +++ b/roles/openshift_router/tasks/main.yml @@ -0,0 +1,8 @@ +--- +- set_fact: _ortr_images="--images={{ oreg_url|quote }}" + when: oreg_url is defined + +- name: Deploy OpenShift Router + command: openshift ex router --create --credentials=/var/lib/openshift/openshift.local.certificates/openshift-router/.kubeconfig {{ _ortr_images|default() }} + register: _ortr_results + changed_when: "'service exists' not in _ortr_results.stdout" diff --git a/roles/openshift_router/vars/main.yml b/roles/openshift_router/vars/main.yml new file mode 100644 index 000000000..cd21505a4 --- /dev/null +++ b/roles/openshift_router/vars/main.yml @@ -0,0 +1,2 @@ +--- + diff --git a/roles/os_zabbix/library/zbxapi.py b/roles/os_zabbix/library/zbxapi.py index f4f52909b..b5fa5ee2b 100755 --- a/roles/os_zabbix/library/zbxapi.py +++ b/roles/os_zabbix/library/zbxapi.py @@ -1,4 +1,8 @@ #!/usr/bin/env python +# vim: expandtab:tabstop=4:shiftwidth=4 +''' + ZabbixAPI ansible module +''' # Copyright 2015 Red Hat Inc. # @@ -17,11 +21,22 @@ # Purpose: An ansible module to communicate with zabbix. # +# pylint: disable=line-too-long +# Disabling line length for readability + import json import httplib2 import sys import os import re +import copy + +class ZabbixAPIError(Exception): + ''' + ZabbixAPIError + Exists to propagate errors up from the api + ''' + pass class ZabbixAPI(object): ''' @@ -69,23 +84,26 @@ class ZabbixAPI(object): 'Usermedia': ['get'], } - def __init__(self, data={}): - self.server = data['server'] or None - self.username = data['user'] or None - self.password = data['password'] or None - if any(map(lambda value: value == None, [self.server, self.username, self.password])): + def __init__(self, data=None): + if not data: + data = {} + self.server = data.get('server', None) + self.username = data.get('user', None) + self.password = data.get('password', None) + if any([value == None for value in [self.server, self.username, self.password]]): print 'Please specify zabbix server url, username, and password.' sys.exit(1) - self.verbose = data.has_key('verbose') + self.verbose = data.get('verbose', False) self.use_ssl = data.has_key('use_ssl') self.auth = None - for class_name, method_names in self.classes.items(): - #obj = getattr(self, class_name)(self) - #obj.__dict__ - setattr(self, class_name.lower(), getattr(self, class_name)(self)) + for cname, _ in self.classes.items(): + setattr(self, cname.lower(), getattr(self, cname)(self)) + # pylint: disable=no-member + # This method does not exist until the metaprogramming executed + # This is permanently disabled. results = self.user.login(user=self.username, password=self.password) if results[0]['status'] == '200': @@ -98,48 +116,40 @@ class ZabbixAPI(object): print "Error in call to zabbix. Http status: {0}.".format(results[0]['status']) sys.exit(1) - def perform(self, method, params): + def perform(self, method, rpc_params): ''' This method calls your zabbix server. It requires the following parameters in order for a proper request to be processed: - - jsonrpc - the version of the JSON-RPC protocol used by the API; the Zabbix API implements JSON-RPC version 2.0; + jsonrpc - the version of the JSON-RPC protocol used by the API; + the Zabbix API implements JSON-RPC version 2.0; method - the API method being called; - params - parameters that will be passed to the API method; + rpc_params - parameters that will be passed to the API method; id - an arbitrary identifier of the request; auth - a user authentication token; since we don't have one yet, it's set to null. ''' http_method = "POST" - if params.has_key("http_method"): - http_method = params['http_method'] - jsonrpc = "2.0" - if params.has_key('jsonrpc'): - jsonrpc = params['jsonrpc'] - rid = 1 - if params.has_key('id'): - rid = params['id'] http = None if self.use_ssl: http = httplib2.Http() else: - http = httplib2.Http( disable_ssl_certificate_validation=True,) + http = httplib2.Http(disable_ssl_certificate_validation=True,) - headers = params.get('headers', {}) + headers = {} headers["Content-type"] = "application/json" body = { "jsonrpc": jsonrpc, "method": method, - "params": params, + "params": rpc_params.get('params', {}), "id": rid, 'auth': self.auth, } - if method in ['user.login','api.version']: + if method in ['user.login', 'api.version']: del body['auth'] body = json.dumps(body) @@ -150,48 +160,70 @@ class ZabbixAPI(object): print headers httplib2.debuglevel = 1 - response, results = http.request(self.server, http_method, body, headers) + response, content = http.request(self.server, http_method, body, headers) + + if response['status'] not in ['200', '201']: + raise ZabbixAPIError('Error calling zabbix. Zabbix returned %s' % response['status']) if self.verbose: print response - print results + print content try: - results = json.loads(results) - except ValueError as e: - results = {"error": e.message} + content = json.loads(content) + except ValueError as err: + content = {"error": err.message} - return response, results + return response, content - ''' - This bit of metaprogramming is where the ZabbixAPI subclasses are created. - For each of ZabbixAPI.classes we create a class from the key and methods - from the ZabbixAPI.classes values. We pass a reference to ZabbixAPI class - to each subclass in order for each to be able to call the perform method. - ''' @staticmethod - def meta(class_name, method_names): - # This meta method allows a class to add methods to it. - def meta_method(Class, method_name): + def meta(cname, method_names): + ''' + This bit of metaprogramming is where the ZabbixAPI subclasses are created. + For each of ZabbixAPI.classes we create a class from the key and methods + from the ZabbixAPI.classes values. We pass a reference to ZabbixAPI class + to each subclass in order for each to be able to call the perform method. + ''' + def meta_method(_class, method_name): + ''' + This meta method allows a class to add methods to it. + ''' # This template method is a stub method for each of the subclass # methods. - def template_method(self, **params): - return self.parent.perform(class_name.lower()+"."+method_name, params) - template_method.__doc__ = "https://www.zabbix.com/documentation/2.4/manual/api/reference/%s/%s" % (class_name.lower(), method_name) + def template_method(self, params=None, **rpc_params): + ''' + This template method is a stub method for each of the subclass methods. + ''' + if params: + rpc_params['params'] = params + else: + rpc_params['params'] = copy.deepcopy(rpc_params) + + return self.parent.perform(cname.lower()+"."+method_name, rpc_params) + + template_method.__doc__ = \ + "https://www.zabbix.com/documentation/2.4/manual/api/reference/%s/%s" % \ + (cname.lower(), method_name) template_method.__name__ = method_name # this is where the template method is placed inside of the subclass # e.g. setattr(User, "create", stub_method) - setattr(Class, template_method.__name__, template_method) + setattr(_class, template_method.__name__, template_method) # This class call instantiates a subclass. e.g. User - Class=type(class_name, (object,), { '__doc__': "https://www.zabbix.com/documentation/2.4/manual/api/reference/%s" % class_name.lower() }) - # This init method gets placed inside of the Class - # to allow it to be instantiated. A reference to the parent class(ZabbixAPI) - # is passed in to allow each class access to the perform method. + _class = type(cname, + (object,), + {'__doc__': \ + "https://www.zabbix.com/documentation/2.4/manual/api/reference/%s" % cname.lower()}) def __init__(self, parent): + ''' + This init method gets placed inside of the _class + to allow it to be instantiated. A reference to the parent class(ZabbixAPI) + is passed in to allow each class access to the perform method. + ''' self.parent = parent + # This attaches the init to the subclass. e.g. Create - setattr(Class, __init__.__name__, __init__) + setattr(_class, __init__.__name__, __init__) # For each of our ZabbixAPI.classes dict values # Create a method and attach it to our subclass. # e.g. 'User': ['delete', 'get', 'updatemedia', 'updateprofile', @@ -200,25 +232,54 @@ class ZabbixAPI(object): # User.delete # User.get for method_name in method_names: - meta_method(Class, method_name) + meta_method(_class, method_name) # Return our subclass with all methods attached - return Class + return _class # Attach all ZabbixAPI.classes to ZabbixAPI class through metaprogramming -for class_name, method_names in ZabbixAPI.classes.items(): - setattr(ZabbixAPI, class_name, ZabbixAPI.meta(class_name, method_names)) +for _class_name, _method_names in ZabbixAPI.classes.items(): + setattr(ZabbixAPI, _class_name, ZabbixAPI.meta(_class_name, _method_names)) + +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 diff_content(from_zabbix, from_user): + ''' Compare passed in object to results returned from zabbix + ''' + terms = ['search', 'output', 'groups', 'select', 'expand'] + regex = '(' + '|'.join(terms) + ')' + retval = {} + for key, value in from_user.items(): + if re.findall(regex, key): + continue + + if from_zabbix[key] != str(value): + retval[key] = str(value) + + return retval def main(): + ''' + This main method runs the ZabbixAPI Ansible Module + ''' module = AnsibleModule( - argument_spec = dict( + argument_spec=dict( server=dict(default='https://localhost/zabbix/api_jsonrpc.php', type='str'), user=dict(default=None, type='str'), password=dict(default=None, type='str'), zbx_class=dict(choices=ZabbixAPI.classes.keys()), - action=dict(default=None, type='str'), params=dict(), debug=dict(default=False, type='bool'), + state=dict(default='present', type='str'), ), #supports_check_mode=True ) @@ -227,47 +288,83 @@ def main(): if not user: user = os.environ['ZABBIX_USER'] - pw = module.params.get('password', None) - if not pw: - pw = os.environ['ZABBIX_PASSWORD'] + passwd = module.params.get('password', None) + if not passwd: + passwd = os.environ['ZABBIX_PASSWORD'] - server = module.params['server'] - if module.params['debug']: - options['debug'] = True api_data = { 'user': user, - 'password': pw, - 'server': server, + 'password': passwd, + 'server': module.params['server'], + 'verbose': module.params['debug'] } - if not user or not pw or not server: - module.fail_json('Please specify the user, password, and the zabbix server.') + if not user or not passwd or not module.params['server']: + module.fail_json(msg='Please specify the user, password, and the zabbix server.') zapi = ZabbixAPI(api_data) zbx_class = module.params.get('zbx_class') - action = module.params.get('action') - params = module.params.get('params', {}) - + rpc_params = module.params.get('params', {}) + state = module.params.get('state') # Get the instance we are trying to call zbx_class_inst = zapi.__getattribute__(zbx_class.lower()) - # Get the instance's method we are trying to call - zbx_action_method = zapi.__getattribute__(zbx_class.capitalize()).__dict__[action] - # Make the call with the incoming params - results = zbx_action_method(zbx_class_inst, **params) - - # Results Section - changed_state = False - status = results[0]['status'] - if status not in ['200', '201']: - #changed_state = False - module.fail_json(msg="Http response: [%s] - Error: %s" % (str(results[0]), results[1])) - module.exit_json(**{'results': results[1]['result']}) + # perform get + # Get the instance's method we are trying to call + zbx_action_method = zapi.__getattribute__(zbx_class.capitalize()).__dict__['get'] + _, content = zbx_action_method(zbx_class_inst, rpc_params) + + if state == 'list': + module.exit_json(changed=False, results=content['result'], state="list") + + if state == 'absent': + if not exists(content): + module.exit_json(changed=False, state="absent") + # If we are coming from a query, we need to pass in the correct rpc_params for delete. + # specifically the zabbix class name + 'id' + # if rpc_params is a list then we need to pass it. (list of ids to delete) + idname = zbx_class.lower() + "id" + if not isinstance(rpc_params, list) and content['result'][0].has_key(idname): + rpc_params = [content['result'][0][idname]] + + zbx_action_method = zapi.__getattribute__(zbx_class.capitalize()).__dict__['delete'] + _, content = zbx_action_method(zbx_class_inst, rpc_params) + module.exit_json(changed=True, results=content['result'], state="absent") + + if state == 'present': + # It's not there, create it! + if not exists(content): + zbx_action_method = zapi.__getattribute__(zbx_class.capitalize()).__dict__['create'] + _, content = zbx_action_method(zbx_class_inst, rpc_params) + module.exit_json(changed=True, results=content['result'], state='present') + + # It's there and the same, do nothing! + diff_params = diff_content(content['result'][0], rpc_params) + if not diff_params: + module.exit_json(changed=False, results=content['result'], state="present") + + # Add the id to update with + idname = zbx_class.lower() + "id" + diff_params[idname] = content['result'][0][idname] + + + ## It's there and not the same, update it! + zbx_action_method = zapi.__getattribute__(zbx_class.capitalize()).__dict__['update'] + _, content = zbx_action_method(zbx_class_inst, diff_params) + module.exit_json(changed=True, results=content, 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() |