diff --git a/warden_server/README.test b/warden_server/README.test index 36246a7049e859f9ae3b360c02f219e5a92f6263..af6b90f53d7317524a5ee8c7953624efcf1bdf04 100644 --- a/warden_server/README.test +++ b/warden_server/README.test @@ -21,7 +21,8 @@ B. Compatibility * The test suite, just like the Warden Server, is compatible with both Python2 (tested on 2.7) and Python3 (tested on 3.6). -* Just like Warden Server, the test suite requires a local MySQL installation. +* Just like Warden Server, the test suite requires a local MySQL or PostgreSQL + installation. * It is safe to run the test suite on a production system. For testing, a database distinct from the default production one is used. Also, the user account used for accessing the testing database is set for local login only. @@ -56,7 +57,11 @@ D. Usage Before running the tests (for the first time), a DB user with required rights must be created. An easy way to do it is: ./test_warden_server.py --init -This will prompt for MySQL root password. +This will prompt for the database administrator account ('root' for MySQL and +'postgres' for PostgreSQL) password. Please note that by default, the user +'postgres' can only be authenticated using the peer authentication method, +which requires that the script is run by the operating system user 'postgres'. +When this is the case, the password is not required. Standard usage for testing: ./test_warden_server.py @@ -64,16 +69,22 @@ Standard usage for testing: Advanced usage: ./test_warden_server.py --help - usage: test_warden_server.py [-h] [-i] [-n] + usage: test_warden_server.py [-h] [-i] [-n] [-d {MySQL,PostgreSQL}] Warden3 Server Test Suite optional arguments: - -h, --help show this help message and exit - -i, --init Set up an user with rights to CREATE/DROP the - test database - -n, --nopurge Skip the database purge after running the tests - + -h, --help show this help message and exit + -d {MySQL,PostgreSQL}, --dbms {MySQL,PostgreSQL} + Database management system to use for + testing + -i, --init Set up an user with rights to + CREATE/DROP the test database + -n, --nopurge Skip the database purge after running + the tests + +Option -d (--dbms) sets the databse management system to use for testing. +If this option is not provided, MySQL is used as default. Option -n (--nopurge) is meant for debugging purposes and test development, it keeps the test database around for inspection after running the tests. diff --git a/warden_server/test_warden_server.py b/warden_server/test_warden_server.py index 09376e1e4fc44b0505c6edee1e56d7c0be75a753..915db505ed5649c4b058d4caab018d065b47182c 100755 --- a/warden_server/test_warden_server.py +++ b/warden_server/test_warden_server.py @@ -6,6 +6,7 @@ import argparse import getpass import sys import warnings +import json from os import path from warden_server import build_server import warden_server @@ -486,8 +487,178 @@ class MySQL: conn.close() +class PostgreSQL: + name = "PostgreSQL" + reg_mod_test_query = "SELECT requestor, hostname, name, secret, valid, clients.read, " \ + "debug, clients.write, test, note FROM clients WHERE id = %s" + + def __init__(self, user=USER, password=PASSWORD, dbname=DB): + import psycopg2 as ppg + from psycopg2 import sql as ppgsql + self.ppg = ppg + self.ppgsql = ppgsql + + self.user = user + self.password = password + self.dbname = dbname + + def init_user(self): + """DB user rights setup""" + running_as_postgres = getpass.getuser() == "postgres" + conn = None + try: + password = None if running_as_postgres else getpass.getpass("Enter PostgreSQL password for the user 'postgres':") + conn = self.ppg.connect(user="postgres", password=password) + with conn.cursor() as cur: + cur.execute( + self.ppgsql.SQL("CREATE ROLE {} PASSWORD {} CREATEDB LOGIN").format( + self.ppgsql.Identifier(self.user), + self.ppgsql.Placeholder() + ), + (self.password,) + ) + conn.commit() + print("DB User set up successfuly") + except self.ppg.OperationalError as ex: + if conn: + conn.rollback() + conn.close() + conn = None + if running_as_postgres: + print("Connection unsuccessful. Original exception: %s" % (str(ex))) + else: + print("Connection unsuccessful, bad password or meant to run as the user 'postgres'" + " (su postgres -c '%s --dbms PostgreSQL --init')? Original exception: %s" % + (path.join('.', path.normpath(sys.argv[0])), str(ex))) + exit() + except KeyboardInterrupt: + print("\nCancelled!") + exit() + finally: + if conn: + conn.close() + + def _load_tags(self, cur): + with open(path.join(path.dirname(__file__), "tagmap_db.json"), + encoding="utf8") as tagmapf: + tagmap = json.load(tagmapf) + for tag, num in tagmap.items(): + cur.execute( + "INSERT INTO tags(id, tag) VALUES (%s, %s)", (num, tag)) + + def _load_cats(self, cur): + with open(path.join(path.dirname(__file__), "catmap_db.json"), + encoding="utf8") as catmapf: + catmap = json.load(catmapf) + for cat_subcat, num in catmap.items(): + catsplit = cat_subcat.split(".", 1) + category = catsplit[0] + subcategory = catsplit[1] if len(catsplit) > 1 else None + cur.execute( + "INSERT INTO categories(id, category, subcategory, cat_subcat) VALUES (%s, %s, %s, %s)", + (num, category, subcategory, cat_subcat) + ) + + def set_up(self): + conn = None + try: + conn = self.ppg.connect(user=self.user, password=self.password, + host='localhost', dbname='postgres') + conn.autocommit = True + cur = conn.cursor() + cur.execute( + self.ppgsql.SQL("DROP DATABASE IF EXISTS {}").format( + self.ppgsql.Identifier(self.dbname) + ) + ) + cur.execute( + self.ppgsql.SQL("CREATE DATABASE {}").format( + self.ppgsql.Identifier(self.dbname) + ) + ) + except self.ppg.OperationalError as ex: + if conn: + conn.rollback() + conn.close() + conn = None + print( + 'Setup failed, have you tried --init ? Original exception: %s' % (str(ex),)) + exit() + finally: + if conn: + conn.close() + conn = None + try: + conn = self.ppg.connect(user=self.user, password=self.password, + dbname=self.dbname, host='localhost') + cur = conn.cursor() + with open(path.join(path.dirname(__file__), 'warden_3.0_postgres.sql'), + encoding="utf8") as script: + statements = script.read() + cur.execute(statements) + + self._load_tags(cur) + self._load_cats(cur) + + cur.execute( + "INSERT INTO clients " + "(registered, requestor, hostname, note, valid," + " name, secret, read, debug, write, test) " + "VALUES(NOW(), 'warden-info@cesnet.cz', 'test.server.warden.cesnet.cz', " + "NULL, 1, 'cz.cesnet.warden3test', 'abc', 1, 1, 1, 0)" + ) + conn.commit() + except self.ppg.OperationalError as ex: + if conn: + conn.rollback() + conn.close() + conn = None + print( + 'Something went wrong during database setup. Original exception: %s' % (str(ex),)) + exit() + finally: + if conn: + conn.close() + + def do_sql_select(self, query, params): + """Reads data from database""" + conn = self.ppg.connect(user=self.user, password=self.password, + dbname=self.dbname, host='localhost') + cur = conn.cursor() + cur.execute(query, params) + result = cur.fetchall() + cur.close() + conn.close() + return tuple(result) + + def tear_down(self): + """Clean up by purging the test database""" + conn = self.ppg.connect(user=self.user, password=self.password, + dbname='postgres', host='localhost') + conn.autocommit = True + cur = conn.cursor() + cur.execute( + self.ppgsql.SQL("DROP DATABASE IF EXISTS {} WITH(FORCE)").format( + self.ppgsql.Identifier(self.dbname) + ) + ) + conn.close() + + def clean_lastid(self): + """Cleans the lastid information for all clients""" + conn = self.ppg.connect( + user=self.user, password=self.password, dbname=self.dbname, host='localhost') + cur = conn.cursor() + cur.execute("DELETE FROM last_events") + cur.execute("DELETE FROM events") + cur.close() + conn.commit() + conn.close() + + database_types = { - 'MySQL': MySQL + 'MySQL': MySQL, + 'PostgreSQL': PostgreSQL }