From b1e3b25a380deef92fb4e01ec08932e9e0e94656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20MOHIER?= Date: Mon, 16 Jan 2017 13:35:43 +0100 Subject: [PATCH] Update for StatsD / Graphite / Grafana set-up Set version to 0.1.11 --- README.rst | 169 ++--- .../realms/South/contacts/contacts.cfg | 2 +- .../sample/backend}/example_grafana.json | 2 +- .../sample/backend}/example_graphite.json | 6 +- .../backend/example_graphite_statsd.json | 11 + .../sample/backend}/example_host_data.json | 0 .../etc/sample/backend/example_statsd.json | 6 + alignak_demo/libexec/python/backend_client.py | 692 ------------------ version.py | 2 +- 9 files changed, 76 insertions(+), 814 deletions(-) rename alignak_demo/{libexec/python => etc/sample/backend}/example_grafana.json (86%) rename alignak_demo/{libexec/python => etc/sample/backend}/example_graphite.json (65%) create mode 100644 alignak_demo/etc/sample/backend/example_graphite_statsd.json rename alignak_demo/{libexec/python => etc/sample/backend}/example_host_data.json (100%) create mode 100644 alignak_demo/etc/sample/backend/example_statsd.json delete mode 100755 alignak_demo/libexec/python/backend_client.py diff --git a/README.rst b/README.rst index 8ba750c..58a22c9 100644 --- a/README.rst +++ b/README.rst @@ -264,7 +264,6 @@ This Alignak demo project installs some shell scripts into the Alignak libexec f mkdir ~/demo cp /usr/local/var/libexec/alignak/bash/* ~/demo - cp /usr/local/var/libexec/alignak/python/* ~/demo **Note** *a next version may install those scripts in the home directory but it is not yet possible;)* @@ -329,11 +328,11 @@ As of now, Alignak is ready to start... let us go! Run Alignak: :: - cd ~/demo - # Detach several screen sessions identified as "alignak-daemon_name" - ./alignak_demo_start.sh + cd ~/demo + # Detach several screen sessions identified as "alignak-daemon_name" + ./alignak_demo_start.sh - # Stopping Alignak is './alignak_demo_stop.sh' + # Stopping Alignak is './alignak_demo_stop.sh' Alignak runs many processes that you can check with: :: @@ -591,18 +590,42 @@ The default configuration is suitable for this demonstration but you may update Configure Alignak backend for timeseries ---------------------------------------- -The Alignak backend allows to send collected performance data but it must be configured to know about where to send the timeseries data. Using the backend_client CLI script make it easy to configure this: +The Alignak backend allows to send collected performance data to a timeseries database. It must be configured to know where to send the timeseries data. Using the backend_client CLI script makes it easy to configure this: :: cd ~/demo - # Use python CLI to add a Grafana instance - python2.7 backend_client.py -v add -t grafana --data=example_grafana.json my_grafana + # Get the example configuration files + cp /usr/local/etc/alignak/sample/backend/* ~/demo + + +**Note** that it is recommended to stop Alignak when editing the backend configuration :) + +If you **do not** intend to use the StatsD daemon, execute these commands: +:: + + # Use Alignak backend CLI to add a Grafana instance + alignak-backend-cli -v add -t grafana --data=example_grafana.json grafana_demo + + # Use Alignak backend to add a Graphite instance + alignak-backend-cli -v add -t graphite --data=example_graphite.json graphite_demo + + +If you **do** intend to use the StatsD daemon, execute these commands: +:: + + # Use Alignak backend CLI to add a Grafana instance + alignak-backend-cli -v add -t grafana --data=example_grafana.json grafana_demo - # Use python CLI to add a Graphite instance - python2.7 backend_client.py -v add -t graphite --data=example_graphite.json my_graphite + # Use Alignak backend CLI to add a StatsD instance + alignak-backend-cli -v add -t statsd --data=example_statsd.json statsd_demo -You can edit the *example_grafana.json* and *example_graphite.json* provided files to include your own Graphite / Grafana (or InfluxDB) parameters. For more information see the `Alignak backend documentation `_. + # Use Alignak backend to add a Graphite instance + alignak-backend-cli -v add -t graphite --data=example_graphite_statsd.json graphite_demo + +You can edit the *example_*.json* provided files to include your own Graphite / Grafana (or InfluxDB) parameters. For more information see the `Alignak backend documentation `_. It will be mandatory to update the Grafana configuration with your own Grafana API key else the backend will not be able to create the Grafana dashboards and panels automatically? + +**Note**: `alignak-backend-cli` is coming with the installation of the Alignak backend client. Upgrading --------- @@ -630,22 +653,35 @@ What we see? Monitored system status ----------------------- -TBC... - http://demo.alignak.net + +The `Alignak Web UI `_ running on our demo server allows to view the monitored system status. Have a look here: `http://demo.alignak.net `_. Several login may be used depending on the user role: + +* admin / admin, to get logged-in as an Administrator. You will see all the hosts and will be able to execute some commands (acknowledge a problem, schedule a downtime,...) + +* northman / north, to get logged-in as a power user in the North realm. You will see all the hosts of the All and North realms and will be able to execute commands. + +* southman / south, to get logged-in as a power user in the South realm. You will see all the hosts of the All and South realms and will be able to execute commands. Alignak internal metrics ------------------------ - http://grafana.demo.alignak.net -TBC + +Alignak maintains its own internal metrics and it is able to send them to a `StatsD server `_. + +We are running a `demo Grafana server `_ that allows to see tha Alignak internal metrics. Several dashboards are available: + +* `Alignak internal metrics `_ shows the statistics provided by Alignak. + +* `Graphite server `_ reports on Carbon/Graphite own monitoring. Install node.js on your server according to the recommended installation process. On FreeBSD: :: + pkg install node - cd /usr/ports/www/node/ && make install clean To get the most recent StatsD (if you distro packaging do not provide it, you must clone the git repository: :: + cd ~/repos git clone https://github.com/etsy/statsd @@ -665,103 +701,4 @@ To get the most recent StatsD (if you distro packaging do not provide it, you mu # And leave the screen... $Ctrl+AD - -What's behind the backend script -================================ - -This simple script may be used to make simple operations with the Alignak backend: - -- create a new element based (or not) on a template - -- update a backend element - -- delete an element - -- get an element and dump its properties to the console or a file (in /tmp) - -- get (and dump) a list of elements - -A simple usage example for this script: -:: - - # Assuming that you installed: alignak, alignak-backend and alignak-backend-import - - # From the root of this repository - cd tests/cfg_passive_templates - # Import the test configuration in the Alignak backend - alignak-backend-import -d ./cfg_passive_templates.cfg - # The script imports the configuration and makes some console logs: - alignak_backend_import, inserted elements: - - 6 command(s) - - 3 host(s) - - 3 host_template(s) - - no hostdependency(s) - - no hostescalation(s) - - 12 hostgroup(s) - - 1 realm(s) - - 1 service(s) - - 14 service_template(s) - - no servicedependency(s) - - no serviceescalation(s) - - 12 servicegroup(s) - - 2 timeperiod(s) - - 2 user(s) - - 3 usergroup(s) - - # Get an host from the backend - backend_client -t host get test_host_0 - - # The script dumps the json host on the console and creates a file: */tmp/alignak-object-dump-host-test_host_0.json* - { - ... - "active_checks_enabled": true, - "address": "127.0.0.1", - "address6": "", - "alias": "test_host_0", - ... - "customs": { - "_OSLICENSE": "gpl", - "_OSTYPE": "gnulinux" - }, - ... - } - - # Get the list of all hosts from the backend - backend_client --list -t host get - - # The script dumps the json list of hosts on the console and creates a file: */tmp/alignak-object-list-hosts.json* - { - ... - "active_checks_enabled": true, - "address": "127.0.0.1", - "address6": "", - "alias": "test_host_0", - ... - "customs": { - "_OSLICENSE": "gpl", - "_OSTYPE": "gnulinux" - }, - ... - } - - # Create an host into the backend - backend_client -T windows-nsca-host -t host add myHost - # The script inform on the console - Created host 'myHost' - - # Create an host into the backend with extra data - backend_client -T windows-nsca-host -t host --data='/tmp/create_data.json' add myHost - # The script reads the JSON content of the file /tmp/create_data.json and tries to create - # the host named myHost with the template and the read data - - # Update an host into the backend - backend_client -t host --data='/tmp/update_data.json' update myHost - # The script reads the JSON content of the file /tmp/update_data.json and tries to update - # the host named myHost with the read data - - # Delete an host from the backend - backend_client -T windows-nsca-host -t host delete myHost - # The script inform on the console - Deleted host 'myHost' - - +As of now you have a running StatsD daemon that will collect the Alignak internal metrics to feed Graphite. \ No newline at end of file diff --git a/alignak_demo/etc/arbiter/realms/South/contacts/contacts.cfg b/alignak_demo/etc/arbiter/realms/South/contacts/contacts.cfg index 60c1291..0802e4d 100755 --- a/alignak_demo/etc/arbiter/realms/South/contacts/contacts.cfg +++ b/alignak_demo/etc/arbiter/realms/South/contacts/contacts.cfg @@ -14,7 +14,7 @@ define contactgroup{ alias South contacts } -# This is a North contact +# This is a South contact define contact{ use south-contact contact_name southhman diff --git a/alignak_demo/libexec/python/example_grafana.json b/alignak_demo/etc/sample/backend/example_grafana.json similarity index 86% rename from alignak_demo/libexec/python/example_grafana.json rename to alignak_demo/etc/sample/backend/example_grafana.json index deb03aa..22af755 100644 --- a/alignak_demo/libexec/python/example_grafana.json +++ b/alignak_demo/etc/sample/backend/example_grafana.json @@ -1,5 +1,5 @@ { - "name": "my_grafana", + "name": "grafana_demo", "address": "10.0.0.8", "port": 3000, "apikey": "eyJrIjoiak9Ta1ZrdXJhdDZWaEVqQkROS0toU1NlZjlMT0hQTFIiLCJuIjoiYWxpZ25hay1iYWNrZW5kIiwiaWQiOjJ9", diff --git a/alignak_demo/libexec/python/example_graphite.json b/alignak_demo/etc/sample/backend/example_graphite.json similarity index 65% rename from alignak_demo/libexec/python/example_graphite.json rename to alignak_demo/etc/sample/backend/example_graphite.json index 3af08c5..db0b9d9 100644 --- a/alignak_demo/libexec/python/example_graphite.json +++ b/alignak_demo/etc/sample/backend/example_graphite.json @@ -1,10 +1,10 @@ { - "name": "my_graphite", + "name": "graphite_demo", "carbon_address": "10.0.0.10", "carbon_port": 2004, "graphite_address": "10.0.0.10", "graphite_port": 8080, - "prefix": "demo", - "grafana": "my_grafana", + "prefix": "", + "grafana": "grafana_demo", "_sub_realm": true } diff --git a/alignak_demo/etc/sample/backend/example_graphite_statsd.json b/alignak_demo/etc/sample/backend/example_graphite_statsd.json new file mode 100644 index 0000000..df3a1fe --- /dev/null +++ b/alignak_demo/etc/sample/backend/example_graphite_statsd.json @@ -0,0 +1,11 @@ +{ + "name": "graphite_demo", + "carbon_address": "10.0.0.10", + "carbon_port": 2004, + "graphite_address": "10.0.0.10", + "graphite_port": 8080, + "prefix": "", + "grafana": "grafana_demo", + "statsd": "statsd_demo", + "_sub_realm": true +} diff --git a/alignak_demo/libexec/python/example_host_data.json b/alignak_demo/etc/sample/backend/example_host_data.json similarity index 100% rename from alignak_demo/libexec/python/example_host_data.json rename to alignak_demo/etc/sample/backend/example_host_data.json diff --git a/alignak_demo/etc/sample/backend/example_statsd.json b/alignak_demo/etc/sample/backend/example_statsd.json new file mode 100644 index 0000000..0bd9c78 --- /dev/null +++ b/alignak_demo/etc/sample/backend/example_statsd.json @@ -0,0 +1,6 @@ +{ + "address": "127.0.0.1", + "port": 8125, + "name": "statsd_demo", + "_sub_realm": true +} \ No newline at end of file diff --git a/alignak_demo/libexec/python/backend_client.py b/alignak_demo/libexec/python/backend_client.py deleted file mode 100755 index fe9a8ae..0000000 --- a/alignak_demo/libexec/python/backend_client.py +++ /dev/null @@ -1,692 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# -# Copyright (C) 2015-2016: Frédéric Mohier -# -# Alignak Backend Client script is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any later version. -# -# Alignak Backend Client is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this script. If not, see . - -""" -backend_client command line interface:: - - Usage: - backend_client [-h] - backend_client [-V] - backend_client [-v] [-c] [-l] - [-b=url] [-u=username] [-p=password] - [-d=data] - [-T=template] [-t=type] [] [] - - Options: - -h, --help Show this screen. - -V, --version Show application version. - -v, --verbose Run in verbose mode (more info to display) - -c, --check Check only (dry run), do not change the backend. - -l, --list Get an items list - -b, --backend url Specify backend URL [default: http://127.0.0.1:5000] - -u, --username=username Backend login username [default: admin] - -p, --password=password Backend login password [default: admin] - -d, --data=data Data for the new item to create [default: none] - -t, --type=host Type of the provided item [default: host] - -T, --template=template Template to use for the new item - - Use cases: - Display help message: - backend_client (-h | --help) - - Display current version: - backend_client -V - backend_client --version - - Get an items list from the backend: - backend_client -l - Try to get the list of all hosts and copy the JSON dump in a file named - '/tmp/alignak-object-list-hosts' - - backend_client -l -t user - Try to get the list of all users and copy the JSON dump in a file named - '/tmp/alignak-object-list-users' - - Get an item from the backend: - backend_client get host_name - Try to get the definition of an host named 'host_name' and copy the JSON dump - in a file named '/tmp/alignak-object-dump-host-host_name' - - backend_client -t user get contact_name - Try to get the definition of a user (contact) contact named 'contact_name' and - copy the JSON dump in a file named '/tmp/alignak-object-dump-contact-contact_name' - - Add an item to the backend (without templating): - backend_client new_host - This will add an host named new_host - - backend_client -t user new_contact - This will add a user named new_contact - - Add an item to the backend (with some data): - backend_client --data="/tmp/input_host.json" add new_host - This will add an host named new_host with the data that are read from the - JSON file /tmp/input_host.json - - backend_client -t user new_contact --data="stdin" - This will add a user named new_contact with the JSON data read from the - stdin. You can 'cat file > backend_client -t user new_contact --data="stdin"' - - Add an item to the backend based on a template: - backend_client -T host_template add new_host - This will add an host named new_host with the data existing in the template - host_template - - Update an item into the backend (with some data): - backend_client --data="/tmp/update_host.json" update test_host - This will update an host named test_host with the data that are read from the - JSON file /tmp/update_host.json - - Specify you backend parameters if they are different from the default - backend_client -b=http://127.0.0.1:5000 -u=admin -p=admin get host_name - - Exit code: - 0 if required operation succeeded - 1 if backend access is denied (check provided username/password) - 2 if element creation failed (missing template,...) - - 64 if command line parameters are not used correctly -""" -from __future__ import print_function - -import os -import sys -import json -import tempfile -import logging - -from docopt import docopt, DocoptExit - -from alignak_backend_client.client import Backend, BackendException - -# Configure logger -logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s - %(levelname)8s - %(message)s') -# Name the logger to get the backend client logs -logger = logging.getLogger('alignak_backend_client.client') - -__version__ = "0.4.1" - -class BackendUpdate(object): - """ - Class to interface the Alignak backend to make some operations - """ - embedded_resources = { - 'realm': { - '_parent': 1, - }, - 'command': { - '_realm': 1, - }, - 'timeperiod': { - '_realm': 1, - }, - 'usergroup': { - '_realm': 1, '_parent': 1, - }, - 'hostgroup': { - '_realm': 1, '_parent': 1, 'hostgroups': 1, 'hosts': 1 - }, - 'servicegroup': { - '_realm': 1, '_parent': 1, 'hostgroups': 1, 'hosts': 1 - }, - 'user': { - '_realm': 1, - 'host_notification_period': 1, 'host_notification_commands': 1, - 'service_notification_period': 1, 'service_notification_commands': 1 - }, - 'host': { - '_realm': 1, '_templates': 1, - 'check_command': 1, 'snapshot_command': 1, 'event_handler': 1, - 'check_period': 1, 'notification_period': 1, - 'snapshot_period': 1, 'maintenance_period': 1, - 'parents': 1, 'hostgroups': 1, 'users': 1, 'usergroups': 1 - }, - 'service': { - '_realm': 1, - 'host': 1, - 'check_command': 1, 'snapshot_command': 1, 'event_handler': 1, - 'check_period': 1, 'notification_period': 1, - 'snapshot_period': 1, 'maintenance_period': 1, - 'service_dependencies': 1, 'servicegroups': 1, 'users': 1, 'usergroups': 1 - }, - 'hostdependency': { - '_realm': 1, - 'hosts': 1, 'hostgroups': 1, - 'dependent_hosts': 1, 'dependent_hostgroups': 1, - 'dependency_period': 1 - }, - 'servicedependency': { - '_realm': 1, - 'hosts': 1, 'hostgroups': 1, - 'dependent_hosts': 1, 'dependent_hostgroups': 1, - 'services': 1, 'dependent_services': 1, - 'dependency_period': 1 - } - } - - def __init__(self): - self.logged_in = False - - # Get command line parameters - args = None - try: - args = docopt(__doc__, version=__version__) - except DocoptExit as exp: - print("Command line parsing error:\n%s." % (exp)) - print("~~~~~~~~~~~~~~~~~~~~~~~~~~") - print("Exiting with error code: 64") - exit(64) - - logger.debug("Test") - # Verbose - self.verbose = False - if '--verbose' in args and args['--verbose']: - logger.setLevel('DEBUG') - self.verbose = True - - # Dry-run mode? - self.dry_run = args['--check'] - logger.info("Dry-run mode (check only): %s", self.dry_run) - - # Backend URL - self.backend = None - self.backend_url = args['--backend'] - logger.info("Backend URL: %s", self.backend_url) - - # Backend authentication - self.username = args['--username'] - self.password = args['--password'] - logger.info("Backend login with credentials: %s/%s", self.username, self.password) - - # Get a list - self.list = args['--list'] - logger.info("Get a list: %s", self.list) - - # Get the item type - self.item_type = args['--type'] - logger.info("Item type: %s", self.item_type) - - # Get the action to execute - self.action = args[''] - if self.action is None: - self.action = 'get' - logger.info("Action to execute: %s", self.action) - if self.action not in ['add', 'update', 'get', 'delete']: - print("Action '%s' is not authorized." % (self.action)) - exit(64) - - # Get the targeted item - self.item = args[''] - logger.info("Targeted item name: %s", self.item) - - # Get the template to use - self.template = args['--template'] - logger.info("Using the template: %s", self.template) - - if self.list and not self.item_type: - self.item_type = self.item - logger.info("Item type (computed): %s", self.item_type) - - # Get the targeted item - self.data = args['--data'] - logger.info("Item data provided: %s", self.data) - - def initialize(self): - """ - Login on backend with username and password - - :return: None - """ - try: - logger.info("Authenticating...") - # Backend authentication with token generation - # headers = {'Content-Type': 'application/json'} - # payload = {'username': self.username, 'password': self.password, 'action': 'generate'} - self.backend = Backend(self.backend_url) - self.backend.login(self.username, self.password) - except BackendException as e: - print("Backend exception: %s" % str(e)) - - if self.backend.token is None: - print("Access denied!") - print("~~~~~~~~~~~~~~~~~~~~~~~~~~") - print("Exiting with error code: 1") - exit(1) - - logger.info("Authenticated.") - - # Default realm - self.realm_all = '' - self.default_realm = '' - realms = self.backend.get_all('realm') - for r in realms['_items']: - if r['name'] == 'All' and r['_level'] == 0: - self.realm_all = r['_id'] - logger.info("Found realm 'All': %s", self.realm_all) - - # Default timeperiods - self.tp_always = None - self.tp_never = None - timeperiods = self.backend.get_all('timeperiod') - for tp in timeperiods['_items']: - if tp['name'] == '24x7': - self.tp_always = tp['_id'] - logger.info("Found TP '24x7': %s", self.tp_always) - if tp['name'].lower() == 'none' or tp['name'].lower() == 'none': - self.tp_never = tp['_id'] - logger.info("Found TP 'Never': %s", self.tp_never) - - if self.verbose: - users = self.backend.get_all('user') - self.users_names = sorted([user['name'] for user in users['_items']]) - logger.info("Existing users: %s", ','.join(self.users_names)) - - hosts = self.backend.get_all('host') - self.hosts_names = sorted([host['name'] for host in hosts['_items']]) - logger.info("Existing hosts: %s", ','.join(self.hosts_names)) - - params = {'where': json.dumps({'_is_template': True})} - templates = self.backend.get_all('host', params=params) - self.host_templates_names = sorted([template['name'] for template in templates['_items']]) - logger.info("Existing host templates: %s", ','.join(self.hosts_names)) - - params = {'where': json.dumps({'_is_template': True})} - templates = self.backend.get_all('host', params=params) - self.service_templates_names = sorted([template['name'] for template in templates['_items']]) - logger.info("Existing service templates: %s", ','.join(self.service_templates_names )) - - def get_resource_list(self, resource_name): - try: - logger.info("Trying to get %s list", resource_name) - - params = {} - if resource_name in self.embedded_resources: - params.update({'embedded': json.dumps(self.embedded_resources[resource_name])}) - - response = self.backend.get_all(resource_name, params=params) - if len(response['_items']) > 0 and response['_status'] == 'OK': - response = response['_items'] - - logger.info("-> found %ss", resource_name) - - # Exists in the backend, we got the element - if not self.dry_run: - logger.info("-> dumping %ss list", resource_name) - for item in response: - # Filter fields prefixed with an _ (internal backend fields) - for field in item.keys(): - if field.startswith('_'): - if field not in ['_realm', '_sub_realm']: - item.pop(field) - continue - - # Filter fields prefixed with an _ in embedded items - if resource_name in self.embedded_resources and \ - field in self.embedded_resources[resource_name]: - # Embedded items may be a list or a simple dictionary, - # always make it a list - embedded_items = item[field] - if not isinstance(item[field], list): - embedded_items = [item[field]] - # Filter fields in each embedded item - for embedded_item in embedded_items: - if not embedded_item: - continue - for embedded_field in embedded_item.keys(): - if embedded_field.startswith('_'): - embedded_item.pop(embedded_field) - - dump = json.dumps(response, indent=4, - separators=(',', ': '), sort_keys=True) - if self.verbose: - print(dump) - try: - temp_d = tempfile.gettempdir() - path = os.path.join(temp_d, 'alignak-object-list-%ss.json' % (resource_name)) - dfile = open(path, "wb") - dfile.write(dump) - dfile.close() - except (OSError, IndexError) as exp: - logger.exception("Error when writing the list dump file %s : %s", path, str(exp)) - - logger.info("-> dumped %ss list", resource_name) - else: - logger.info("Dry-run mode: should have dumped an %s list", resource_name) - - return True - else: - logger.warning("-> %s list is empty", resource_name) - return False - - except BackendException as e: - print("Get error for '%s' list" % (resource_name)) - logger.exception(e) - print("~~~~~~~~~~~~~~~~~~~~~~~~~~") - print("Exiting with error code: 5") - return False - - def get_resource(self, resource_name, name): - try: - logger.info("Trying to get %s: '%s'", resource_name, name) - - params = {'where': json.dumps({'name': name})} - if resource_name in self.embedded_resources: - params.update({'embedded': json.dumps(self.embedded_resources[resource_name])}) - - response = self.backend.get(resource_name, params=params) - if len(response['_items']) > 0: - response = response['_items'][0] - - logger.info("-> found %s '%s': %s", resource_name, name, response['_id']) - - # Exists in the backend, we got the element - if not self.dry_run: - logger.info("-> dumping %s: %s", resource_name, name) - # Filter fields prefixed with an _ (internal backend fields) - for field in response.keys(): - if field.startswith('_'): - response.pop(field) - continue - - # Filter fields prefixed with an _ in embedded items - if resource_name in self.embedded_resources and \ - field in self.embedded_resources[resource_name]: - # Embedded items may be a list or a simple dictionary, - # always make it a list - embedded_items = response[field] - if not isinstance(response[field], list): - embedded_items = [response[field]] - # Filter fields in each embedded item - for embedded_item in embedded_items: - for embedded_field in embedded_item.keys(): - if embedded_field.startswith('_'): - embedded_item.pop(embedded_field) - - dump = json.dumps(response, indent=4, - separators=(',', ': '), sort_keys=True) - print(dump) - try: - temp_d = tempfile.gettempdir() - path = os.path.join(temp_d, 'alignak-object-dump-%s-%s.json' % - (resource_name, name)) - dfile = open(path, "wb") - dfile.write(dump) - dfile.close() - except (OSError, IndexError) as exp: - logger.exception("Error when writing the dump file %s : %s", path, str(exp)) - - logger.info("-> dumped %s: %s", resource_name, name) - else: - logger.info("Dry-run mode: should have dumped an %s '%s'", - resource_name, name) - - return True - else: - logger.warning("-> %s '%s' not found", resource_name, name) - return False - - except BackendException as e: - print("Get error for '%s' : %s" % (resource_name, name)) - logger.exception(e) - print("~~~~~~~~~~~~~~~~~~~~~~~~~~") - print("Exiting with error code: 5") - return False - - def delete_resource(self, resource_name, name): - try: - logger.info("Trying to get %s: '%s'", resource_name, name) - - params = {'where': json.dumps({'name': name})} - response = self.backend.get(resource_name, params=params) - if len(response['_items']) > 0: - response = response['_items'][0] - - logger.info("-> found %s '%s': %s", resource_name, name, response['_id']) - - # Exists in the backend, we must delete the element... - if not self.dry_run: - headers = { - 'Content-Type': 'application/json', - 'If-Match': response['_etag'] - } - logger.info("-> deleting %s: %s", resource_name, name) - self.backend.delete(resource_name + '/' + response['_id'], headers) - logger.info("-> deleted %s: %s", resource_name, name) - else: - response = {'_id': '_fake', '_etag': '_fake'} - logger.info("Dry-run mode: should have deleted an %s '%s'", - resource_name, name) - logger.info("-> deleted: '%s': %s", - resource_name, response['_id']) - - return True - else: - logger.warning("-> %s %s template '%s' not found", resource_name, name) - return False - - except BackendException as e: - print("Deletion error for '%s' : %s" % (resource_name, name)) - logger.exception(e) - print("~~~~~~~~~~~~~~~~~~~~~~~~~~") - print("Exiting with error code: 5") - return False - - return True - - def create_update_resource(self, resource_name, name, update=False): - """ - - :param resource_name: backend resource endpoint (eg. host, user, ...) - :param name: name of the resource to create/update - :param update: True to update an existing resource, else will try to create - :return: - """ - self.update_backend_data = True - - if self.data is None: - data = {} - - # If some data are provided, try to get them - json_data = None - if self.data != 'none': - try: - if self.data == 'stdin': - inf = sys.stdin - else: - inf = open(self.data) - - print(inf) - json_data = json.load(inf) - logger.info("Got provided data: %s", json_data) - if inf is not sys.stdin: - inf.close() - except IOError as e: - logger.exception("Error reading data file: %s", e) - return False - except ValueError as e: - logger.exception("Error malformed data file: %s", e) - return False - - try: - logger.info("Trying to get %s: '%s'", resource_name, name) - - params = {'where': json.dumps({'name': name})} - response = self.backend.get(resource_name, params=params) - if len(response['_items']) > 0: - response = response['_items'][0] - - logger.info("-> found %s '%s': %s", resource_name, name, response['_id']) - - if not update: - logger.warning("-> %s should be updated and not created: %s", - resource_name, name) - return False - - # Item data updated with provided information if some - headers = { - 'Content-Type': 'application/json', - 'If-Match': response['_etag'] - } - item_data = response - if json_data is not None: - item_data.update(json_data) - - for field in item_data.copy(): - logger.debug("Field: %s = %s", field, item_data[field]) - # Filter Eve extra fields - if field in ['_created', '_updated', '_etag', '_links', '_status']: - item_data.pop(field) - continue - # Manage potential object link fields - if field in ['realm', 'command', 'timeperiod', 'host', 'grafana']: - try: - id_value = int(item_data[field]) - except ValueError: - # Not an integer, consider an item name - params = {'where': json.dumps({'name': item_data[field]})} - response = self.backend.get(field, params=params) - if len(response['_items']) > 0: - response = response['_items'][0] - logger.info("Replaced %s = %s with found item _id", - field, item_data[field], response['_id']) - item_data[field] = response['_id'] - continue - - if '_realm' not in item_data: - item_data.update({ '_realm': self.realm_all }) - - # Exists in the backend, we should update if required... - if not self.dry_run: - response = self.backend.patch( - resource_name + '/' + response['_id'], item_data, - headers=headers, inception=True - ) - else: - response = {'_id': '_fake', '_etag': '_fake'} - logger.info("Dry-run mode: should have updated an %s '%s' with %s", - resource_name, name, item_data) - - logger.info("-> updated: '%s': %s, with %s", - resource_name, response['_id'], item_data) - - return True - else: - logger.info("-> %s '%s' not existing, it can be created.", resource_name, name) - - host_template = None - if self.template is not None: - logger.info("Trying to find the %s template: %s", resource_name, self.template) - - params = {'where': json.dumps({'name': self.template, '_is_template': True})} - response = self.backend.get(resource_name, params=params) - if len(response['_items']) > 0: - host_template = response['_items'][0] - - logger.info("-> %s template '%s': %s", - resource_name, self.template, host_template['_id']) - else: - print("-> %s template '%s' not found" % (resource_name, self.template)) - return False - - # Host data and template information if templating is required - item_data = { - 'name': name, - } - if host_template is not None: - item_data.update({'_templates': [host_template['_id']], - '_templates_with_services': True}) - if json_data is not None: - item_data.update(json_data) - - for field in item_data.copy(): - logger.debug("Field: %s = %s", field, item_data[field]) - # Filter Eve extra fields - if field in ['_created', '_updated', '_etag', '_links', '_status']: - item_data.pop(field) - continue - # Manage potential object link fields - if field in ['realm', 'command', 'timeperiod', 'grafana']: - try: - id_value = int(item_data[field]) - except ValueError: - # Not an integer, consider an item name - params = {'where': json.dumps({'name': item_data[field]})} - response = self.backend.get(field, params=params) - if len(response['_items']) > 0: - response = response['_items'][0] - logger.info("Replaced %s = %s with found item _id", - field, item_data[field]) - item_data[field] = response['_id'] - continue - - if '_realm' not in item_data: - item_data.update({ '_realm': self.realm_all }) - - if not self.dry_run: - logger.info("-> trying to create the %s: %s, with: %s", - resource_name, name, item_data) - response = self.backend.post(resource_name, item_data, headers=None) - else: - response = {'_id': '_fake', '_etag': '_fake'} - logger.info("Dry-run mode: should have created an %s '%s' with %s", - resource_name, name, item_data) - logger.info("-> created: '%s': %s, with %s", - resource_name, response['_id'], item_data) - - return True - except BackendException as e: - print("Creation error for '%s' : %s" % (resource_name, name)) - logger.exception(e) - print("~~~~~~~~~~~~~~~~~~~~~~~~~~") - print("Exiting with error code: 5") - return False - - -def main(): - """ - Main function - """ - bc = BackendUpdate() - bc.initialize() - logger.debug("backend_client, version: %s" % __version__) - logger.debug("~~~~~~~~~~~~~~~~~~~~~~~~~~~~") - - success = False - if bc.item_type and bc.action == 'get': - if bc.list: - success = bc.get_resource_list(bc.item_type) - else: - success = bc.get_resource(bc.item_type, bc.item) - - if bc.item and bc.action in ['add', 'update']: - success = bc.create_update_resource(bc.item_type, bc.item, bc.action == 'update') - - if bc.item and bc.action == 'delete': - success = bc.delete_resource(bc.item_type, bc.item) - - if not success: - logger.error("%s '%s' %s failed" % (bc.item_type, bc.item, bc.action)) - if not bc.verbose: - logger.warning("Set verbose mode to have more information (-v)") - exit(2) - - exit(0) - - -if __name__ == "__main__": # pragma: no cover - main() diff --git a/version.py b/version.py index 311ccce..3a417df 100755 --- a/version.py +++ b/version.py @@ -18,7 +18,7 @@ __checks_type__ = u"demo" # Application manifest -__version__ = u"0.1.10" +__version__ = u"0.1.11" __author__ = u"Frédéric Mohier" __author_email__ = u"frederic.mohier@alignak.net" __copyright__ = u"(c) 2015-2017 - %s" % __author__