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
This commit is contained in:
parent
87bc59af5b
commit
67a918d0e0
34
README.rst
34
README.rst
|
@ -1,4 +1,36 @@
|
||||||
|
==================
|
||||||
Validations-common
|
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.
|
||||||
|
|
|
@ -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()
|
|
@ -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
|
||||||
|
})
|
Loading…
Reference in New Issue