From 67a918d0e05e506155842991193cf587c3b1396f Mon Sep 17 00:00:00 2001 From: Mathieu Bultel Date: Wed, 27 Jan 2021 23:53:46 +0100 Subject: [PATCH] Add external http logging callback Add callback to send VF logs and results in json to a http server. Related BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1902738 Change-Id: I09e0257daa1512698a2ec74ce2dca0e778aee966 --- README.rst | 34 ++++++- tools/http_server.py | 54 +++++++++++ .../callback_plugins/http_json.py | 94 +++++++++++++++++++ 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 tools/http_server.py create mode 100644 validations_common/callback_plugins/http_json.py diff --git a/README.rst b/README.rst index a1ec94f..dd81bdb 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,36 @@ +================== Validations-common ================== -A collection of common Ansible libraries and plugins for the Validation Framework +A collection of common Ansible libraries and plugins for the Validation +Framework + + +Validations Callbacks +===================== + +****************** +http_json callback +****************** + +The callback `http_json` sends Validations logs and information to an HTTP +server as a JSON format in order to get caught and analysed with external +tools for log parsing (as Fluentd or others). + +This callback inherits from `validation_json` the format of the logging +remains the same as the other logger that the Validation Framework is using +by default. + +To enable this callback, you need to add it to the callback whitelist. +Then you need to export your http server url and port:: + + export HTTP_JSON_SERVER=http://localhost + export HTTP_JSON_PORT=8989 + +The callback will post JSON log to the URL provided. +This repository has a simple HTTP server for testing purpose under:: + + tools/http_server.py + +The default host and port are localhost and 8989, feel free to adjust those +values to your needs. diff --git a/tools/http_server.py b/tools/http_server.py new file mode 100644 index 0000000..e8b1aa4 --- /dev/null +++ b/tools/http_server.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# 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. +from http.server import BaseHTTPRequestHandler, HTTPServer +import logging + + +class SimpleHandler(BaseHTTPRequestHandler): + def _set_headers(self): + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + + def do_GET(self): + logging.info("Received GET request:\n" + "Headers: {}\n".format(str(self.headers))) + self._set_headers() + self.wfile.write("GET request: {}".format(self.path).encode('utf-8')) + + def do_POST(self): + content_length = int(self.headers['Content-Length']) + data = self.rfile.read(content_length) + logging.info("Received POST request:\n" + "Headers: {}\n" + "Body: \n{}\n".format(self.headers, data.decode('utf-8'))) + self._set_headers() + self.wfile.write("POST request: {}".format(self.path).encode('utf-8')) + + +def run(host='localhost', port=8989): + logging.basicConfig(level=logging.INFO) + http_server = HTTPServer((host, port), SimpleHandler) + logging.info("Starting http server...\n") + try: + http_server.serve_forever() + except KeyboardInterrupt: + pass + http_server.server_close() + logging.info('Stopping http server...\n') + + +if __name__ == '__main__': + run() diff --git a/validations_common/callback_plugins/http_json.py b/validations_common/callback_plugins/http_json.py new file mode 100644 index 0000000..1001184 --- /dev/null +++ b/validations_common/callback_plugins/http_json.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# 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. +__metaclass__ = type + +DOCUMENTATION = ''' + requirements: + - whitelist in configuration + short_description: sends JSON events to a HTTP server + description: + - This plugin logs ansible-playbook and ansible runs to an HTTP server in JSON format + options: + server: + description: remote server that will receive the event + env: + - name: HTTP_JSON_SERVER + default: http://localhost + ini: + - section: callback_http_json + key: http_json_server + port: + description: port on which the remote server is listening + env: + - name: HTTP_JSON_PORT + default: 8989 + ini: + - section: callback_http_json + key: http_json_port +''' +import datetime +import json +import os + +from urllib import request + +from validations_common.callback_plugins import validation_json + +url = '{}:{}'.format(os.getenv('HTTP_JSON_SERVER', 'http://localhost'), + os.getenv('HTTP_JSON_PORT', '8989')) + + +def http_post(data): + req = request.Request(url) + req.add_header('Content-Type', 'application/json; charset=utf-8') + json_data = json.dumps(data) + json_bytes = json_data.encode('utf-8') + req.add_header('Content-Length', len(json_bytes)) + response = request.urlopen(req, json_bytes) + + +def current_time(): + return '%sZ' % datetime.datetime.utcnow().isoformat() + + +class CallbackModule(validation_json.CallbackModule): + + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'aggregate' + CALLBACK_NAME = 'http_json' + CALLBACK_NEEDS_WHITELIST = True + + def __init__(self): + super(validation_json.CallbackModule, self).__init__() + self.results = [] + self.simple_results = [] + self.env = {} + self.t0 = None + self.current_time = current_time() + + def v2_playbook_on_stats(self, stats): + """Display info about playbook statistics""" + + hosts = sorted(stats.processed.keys()) + + summary = {} + for h in hosts: + s = stats.summarize(h) + summary[h] = s + + http_post({ + 'plays': self.results, + 'stats': summary, + 'validation_output': self.simple_results + })