memgraph/tests/integration/telemetry/server.py
Jure Bajic ff21c0705c
Add multiple license support (#618)
Make license info available through LicenseChecker
Add LicenseInfoSender
Move license library from utils
Rename telemetry_lib to mg-telemetry
2022-11-04 15:23:43 +01:00

199 lines
6.7 KiB
Python
Executable File

#!/usr/bin/python3 -u
# Copyright 2021 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import argparse
import itertools
import json
import os
import signal
import sys
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
def build_handler(storage, args):
class Handler(BaseHTTPRequestHandler):
def do_HEAD(self):
assert False
def do_GET(self):
assert False
def do_PUT(self):
assert False
def do_POST(self):
if args.redirect and self.path == args.path:
# 307 is used instead of 301 to preserve body data
# https://stackoverflow.com/questions/19070801/curl-loses-body-when-a-post-redirected-from-http-to-https
self.send_response(307)
self.send_header("Location", args.redirect_path)
self.end_headers()
return
assert self.headers["user-agent"] == "memgraph/telemetry"
assert self.headers["accept"] == "application/json"
assert self.headers["content-type"] == "application/json"
content_len = int(self.headers.get("content-length", 0))
data = json.loads(self.rfile.read(content_len).decode("utf-8"))
if self.path not in [args.path, args.redirect_path]:
self.send_response(404)
self.end_headers()
return
if args.no_response_count > 0:
args.no_response_count -= 1
return
if args.wrong_code_count > 0:
args.wrong_code_count -= 1
self.send_response(500)
self.end_headers()
return
assert type(data) == list
for item in data:
assert type(item) == dict
assert "event" in item
assert "run_id" in item
assert "type" in item
assert "machine_id" in item
assert "data" in item
assert "timestamp" in item
storage.append(item)
if args.hang:
time.sleep(20)
self.send_response(200)
self.end_headers()
return Handler
class Server(HTTPServer):
def handle_error(self, request, client_address):
super().handle_error(request, client_address)
os._exit(1)
def shutdown(self):
# TODO: this is a hack. The parent object implementation of this
# function sets the shutdown flag and then waits for the shutdown to
# complete. We only need to set the shutdown flag because we don't
# want to run the server in another thread. The parent implementation
# can be seen here:
# https://github.com/python/cpython/blob/3.5/Lib/socketserver.py#L241
self._BaseServer__shutdown_request = True
def item_sort_key(obj):
if type(obj) != dict:
return -1
if "timestamp" not in obj:
return -1
return obj["timestamp"]
def verify_storage(storage, args):
rid = storage[0]["run_id"]
timestamp = 0
for i, item in enumerate(storage):
assert item["run_id"] == rid
assert item["timestamp"] >= timestamp
timestamp = item["timestamp"]
if i == 0:
assert item["event"] == "startup"
elif i == len(storage) - 1:
assert item["event"] == "shutdown"
else:
assert item["event"] == i - 1
if i == 0:
assert "architecture" in item["data"]
assert "cpu_count" in item["data"]
assert "cpu_model" in item["data"]
assert "kernel" in item["data"]
assert "memory" in item["data"]
assert "os" in item["data"]
assert "swap" in item["data"]
else:
assert item["data"]["db"]["vertices"] == i
assert item["data"]["db"]["edges"] == i
assert "resources" in item["data"]
assert "cpu" in item["data"]["resources"]
assert "memory" in item["data"]["resources"]
assert "uptime" in item["data"]
uptime = item["data"]["uptime"]
expected = i * args.interval
if i == len(storage) - 1:
if not args.no_check_duration:
expected = args.duration
else:
expected = uptime
assert uptime >= expected - 4 and uptime <= expected + 4
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--address", type=str, default="127.0.0.1")
parser.add_argument("--port", type=int, default=9000)
parser.add_argument("--path", type=str, default="/")
parser.add_argument("--redirect", action="store_true")
parser.add_argument("--no-response-count", type=int, default=0)
parser.add_argument("--wrong-code-count", type=int, default=0)
parser.add_argument("--no-check", action="store_true")
parser.add_argument("--hang", action="store_true")
parser.add_argument("--interval", type=int, default=1)
parser.add_argument("--duration", type=int, default=10)
parser.add_argument("--startups", type=int, default=1)
parser.add_argument("--no-check-duration", action="store_true")
args = parser.parse_args()
args.redirect_path = os.path.join(args.path, "redirect")
storage = []
handler = build_handler(storage, args)
httpd = Server((args.address, args.port), handler)
signal.signal(signal.SIGTERM, lambda signum, frame: httpd.shutdown())
httpd.serve_forever()
httpd.server_close()
if args.no_check:
sys.exit(0)
# Order the received data.
storage.sort(key=item_sort_key)
# Split the data into individual startups.
startups = [[storage[0]]]
for item in storage[1:]:
if item["run_id"] != startups[-1][-1]["run_id"]:
startups.append([])
startups[-1].append(item)
# Check that there were the correct number of startups.
assert len(startups) == args.startups, f"Expected: {args.startups}, actual: {len(startups)}"
# Verify each startup.
for startup in startups:
verify_storage(startup, args)
# machine id has to be same for every run on the same machine
assert len(set(map(lambda x: x["machine_id"], itertools.chain(*startups)))) == 1