From 29578ece539c615b3cfd67e1c09fa4f63afae13b Mon Sep 17 00:00:00 2001 From: karec Date: Fri, 17 Jun 2016 16:14:33 +0200 Subject: [PATCH 01/34] replace standard database creation with db_url creation ref #30 --- oct/results/models.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/oct/results/models.py b/oct/results/models.py index eb4b1f5..0c50203 100644 --- a/oct/results/models.py +++ b/oct/results/models.py @@ -1,6 +1,9 @@ +import os import json import datetime -from peewee import Proxy, TextField, FloatField, CharField, IntegerField, Model, DateTimeField, SqliteDatabase + +from playhouse.db_url import connect +from peewee import Proxy, TextField, FloatField, CharField, IntegerField, Model, DateTimeField db = Proxy() @@ -59,15 +62,19 @@ class Meta: database = db -def set_database(db_path, proxy, config): +def set_database(db_url, proxy, config): """Initialize the peewee database with the given configuration - :param str db_path: the path of the sqlite database + If the given db_url is a regular file, it will be used as sqlite database + + :param str db_url: the connection string for database or path if sqlite file :param peewee.Proxy proxy: the peewee proxy to initialise :param dict config: the configuration dictionnary """ if 'testing' in config and config['testing'] is True: - database = SqliteDatabase('/tmp/results.sqlite', check_same_thread=False, threadlocals=True) + database = connect('sqlite:////tmp/results.sqlite', check_same_thread=False, threadlocals=True) else: - database = SqliteDatabase(db_path, check_same_thread=False, threadlocals=True) + if os.path.isfile(db_url): + db_url = "sqlite:///" + db_url + database = connect(db_url, check_same_thread=False, threadlocals=True) proxy.initialize(database) From d98afac572bda2e9bca91841b9d289b3e203b020 Mon Sep 17 00:00:00 2001 From: karec Date: Thu, 23 Jun 2016 23:54:21 +0200 Subject: [PATCH 02/34] update database management for multiples database backend --- oct/results/models.py | 7 +++++-- oct/results/stats_handler.py | 10 ++++++++-- oct/utilities/configuration.py | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/oct/results/models.py b/oct/results/models.py index 0c50203..949e09d 100644 --- a/oct/results/models.py +++ b/oct/results/models.py @@ -71,10 +71,13 @@ def set_database(db_url, proxy, config): :param peewee.Proxy proxy: the peewee proxy to initialise :param dict config: the configuration dictionnary """ + db_config = config.get('results_database', {}).get('params', {}) + if 'testing' in config and config['testing'] is True: database = connect('sqlite:////tmp/results.sqlite', check_same_thread=False, threadlocals=True) else: - if os.path.isfile(db_url): + if os.path.isfile(db_url) or os.path.isdir(os.path.dirname(db_url)): db_url = "sqlite:///" + db_url - database = connect(db_url, check_same_thread=False, threadlocals=True) + db_config.update(check_same_thread=False, threadlocals=True) + database = connect(db_url, **db_config) proxy.initialize(database) diff --git a/oct/results/stats_handler.py b/oct/results/stats_handler.py index 2449b36..c2fe4f5 100644 --- a/oct/results/stats_handler.py +++ b/oct/results/stats_handler.py @@ -1,6 +1,7 @@ import os import ujson +from oct.utilities.configuration import get_db_uri from oct.results.models import Result, Turret, set_database, db @@ -16,9 +17,14 @@ def init_stats(output_dir, config): print("ERROR: Can not create output directory\n") raise - set_database(output_dir + "results.sqlite", db, config) + db_uri = get_db_uri(config, output_dir) + + set_database(db_uri, db, config) + + tables_to_create = [t for t in [Result, Turret] if not t.table_exists()] + db.connect() - db.create_tables([Result, Turret]) + db.create_tables(tables_to_create) class StatsHandler(object): diff --git a/oct/utilities/configuration.py b/oct/utilities/configuration.py index 3744ad8..a8dc669 100644 --- a/oct/utilities/configuration.py +++ b/oct/utilities/configuration.py @@ -80,3 +80,17 @@ def cleanup_turret_config(config): del config[key] return config + + +def get_db_uri(config, output_dir): + """Process results_database parameters in config to format them for + set database function + + :param dict config: project configuration dict + :param str output_dir: output directory for results + :return: string for db uri + """ + db_config = config.get("results_database", {"db_uri": "default"}) + if db_config['db_uri'] == 'default': + return os.path.join(output_dir, "results.sqlite") + return db_config['db_uri'] From 9d55d7c1bc1d98cc3a827520a0bb35079b86194b Mon Sep 17 00:00:00 2001 From: karec Date: Thu, 23 Jun 2016 23:54:40 +0200 Subject: [PATCH 03/34] update config.json template for database configuration example --- oct/utilities/templates/configuration/config.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/oct/utilities/templates/configuration/config.json b/oct/utilities/templates/configuration/config.json index d770ed1..8d3df0b 100644 --- a/oct/utilities/templates/configuration/config.json +++ b/oct/utilities/templates/configuration/config.json @@ -24,5 +24,8 @@ "turrets_requirements": [], "extra_turret_config": { + }, + "results_database": { + "db_uri": "default" } } From be90b829908d1562fcbe1d16be47b18a107c1ddf Mon Sep 17 00:00:00 2001 From: karec Date: Thu, 23 Jun 2016 23:55:05 +0200 Subject: [PATCH 04/34] update rebuild results command behavior --- oct/tools/rebuild_results.py | 16 +++++++++++++--- tests/test_tools.py | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/oct/tools/rebuild_results.py b/oct/tools/rebuild_results.py index 79fb1df..f4888c7 100644 --- a/oct/tools/rebuild_results.py +++ b/oct/tools/rebuild_results.py @@ -1,13 +1,23 @@ import six +from argparse import ArgumentError from oct.results.output import output from oct.results.models import db, set_database -from oct.utilities.configuration import configure +from oct.utilities.configuration import configure, get_db_uri def rebuild(args): config = configure(None, None, args.config_file) - set_database(args.results_file, db, config) + + if args.results_file is None: + db_uri = get_db_uri(config, args.results_dir) + else: + db_uri = args.results_file + + if not db_uri: + raise ArgumentError("Bad database configured, if you use sqlite database use -f option") + + set_database(db_uri, db, config) output(args.results_dir, config) @@ -17,6 +27,6 @@ def rebuild_results(sp): else: parser = sp.add_parser('rebuild-results', help="Rebuild the html report from result dir", aliases=['rebuild']) parser.add_argument('results_dir', help="The directory containing the results") - parser.add_argument('results_file', help="The result file") parser.add_argument('config_file', help="The configuration file") + parser.add_argument('-f', '--results-file', help="The sqlite result file", default=None) parser.set_defaults(func=rebuild) diff --git a/tests/test_tools.py b/tests/test_tools.py index 131d298..7e2ac4b 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -46,12 +46,12 @@ def test_rebuild_results(self): """OCT should be able to rebuild results from sqlite file """ sys.argv = sys.argv[:1] - sys.argv += ["rebuild-results", self.rebuild_dir, self.results_file, self.config_file] + sys.argv += ["rebuild-results", self.rebuild_dir, self.config_file, "-f", self.results_file] main() # try same rebuild sys.argv = sys.argv[:1] - sys.argv += ["rebuild-results", self.rebuild_dir, self.results_file, self.config_file] + sys.argv += ["rebuild-results", self.rebuild_dir, self.config_file, "-f", self.results_file] main() def tearDown(self): From ccfecfd699689f9d89ea679cb55c929cd92ebfa0 Mon Sep 17 00:00:00 2001 From: karec Date: Thu, 23 Jun 2016 23:58:41 +0200 Subject: [PATCH 05/34] update changelog for #30 --- changelog.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/changelog.txt b/changelog.txt index 1e1d8e5..72c2c0e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,15 @@ +0.4.5 to 0.4.6 +============== + +* allow database configuration in project configuration, you can now use any peewee supported database backend +* update rebuild command to take database configuration in consideration + +Backward compatibility break +---------------------------- + +* `oct rebuild-results` commands arguments has changed, results_file is now optionnal and the command will by default look +at the configuration file. If no results_database key is present, it will use the patern `results_directory + results.sqlite` + 0.4.4 to 0.4.5 ============== From c445ffa185749f0ef5cb5589d9fbab89b6efa953 Mon Sep 17 00:00:00 2001 From: karec Date: Fri, 24 Jun 2016 00:02:12 +0200 Subject: [PATCH 06/34] update oct documentation --- doc/collecting_results.rst | 5 ++--- doc/commands_reference.rst | 16 ++++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/doc/collecting_results.rst b/doc/collecting_results.rst index 5988008..b68639b 100644 --- a/doc/collecting_results.rst +++ b/doc/collecting_results.rst @@ -28,9 +28,8 @@ You can simply use the ``oct rebuild-results`` like this for example: .. code-block:: bash - oct rebuild-results . results.sqlite config.json + oct rebuild-results . config.json .. note:: - The oct rebuild-results command will only work on an already created results folder that contains only the sqlite results - and optionnaly the configuration. + `rebuild-results` command will use database configuration in `config.json` file diff --git a/doc/commands_reference.rst b/doc/commands_reference.rst index 4772400..748324c 100644 --- a/doc/commands_reference.rst +++ b/doc/commands_reference.rst @@ -128,17 +128,17 @@ usage : .. code-block:: sh - oct rebuild-results + oct rebuild-results [-f] Arguments : -================ ==== ========== ============================================= -name type mandatory description -================ ==== ========== ============================================= -results_dir str yes results directory to rebuild -results_file str yes sqlite result file to use -config_file str yes json config file of the project -================ ==== ========== ============================================= +================== ==== ========== ============================================= +name type mandatory description +================== ==== ========== ============================================= +results_dir str yes results directory to rebuild +config_file str yes json config file of the project +-f, --results-file str no sqlite results file +================== ==== ========== ============================================= Results to csv --------------- From 96cfd181ec99ff4f40c892394052608c7145f6fe Mon Sep 17 00:00:00 2001 From: karec Date: Mon, 27 Jun 2016 13:14:27 +0200 Subject: [PATCH 07/34] update documentation for new database configuration --- doc/first_project.rst | 6 ++++++ oct/utilities/templates/configuration/config.json | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/first_project.rst b/doc/first_project.rst index b2fea75..0d6e32c 100644 --- a/doc/first_project.rst +++ b/doc/first_project.rst @@ -63,6 +63,10 @@ The main and more important file here is the `config.json` file, let's take a lo "turrets_requirements": [], "extra_turret_config": { // put global turrets config key / values here + }, + "results_database": { + "db_uri": "default", + "params": {} } } @@ -88,6 +92,8 @@ Every key here is useful, but some keys are not required to run a test. Let's ta * ``extra_turret_config``: A nested object containing all extra turrets parameters. Each value in it will be set in each turret configuration +* ``results_database``: Nested object that allows results database configuration, for exemple if you don't want to use the default sqlite results database + This configuration is simple but will allow you to run simple tests in a local environment. diff --git a/oct/utilities/templates/configuration/config.json b/oct/utilities/templates/configuration/config.json index 8d3df0b..1df68bd 100644 --- a/oct/utilities/templates/configuration/config.json +++ b/oct/utilities/templates/configuration/config.json @@ -26,6 +26,7 @@ }, "results_database": { - "db_uri": "default" + "db_uri": "default", + "params": {} } } From 6f0caa67e8499dca4c4f026861d9aba7a897deb4 Mon Sep 17 00:00:00 2001 From: karec Date: Mon, 27 Jun 2016 15:04:26 +0200 Subject: [PATCH 08/34] prepare project for streamer and forwarders --- oct/core/hq.py | 36 ++++++++++++++++++++++++------------ oct/utilities/run.py | 6 +++--- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/oct/core/hq.py b/oct/core/hq.py index 2bb06f6..e572177 100644 --- a/oct/core/hq.py +++ b/oct/core/hq.py @@ -13,28 +13,23 @@ class HightQuarter(object): """The main hight quarter that will receive informations from the turrets and send the start message - :param int publish_port: the port for publishing information to turrets - :param int rc_port: the result collector port for collecting results from the turrets - :param StatsHandler stats_handler: the stats handler writer + :param str output_dir: output directory for results :param dict config: the configuration of the test + :param str topic: topic for external publishing socket + :param bool with_forwarder: tell HQ if it should connects to forwarder, default False + :param bool with_streamer: tell HQ if ti should connects to streamer, default False """ - def __init__(self, publish_port, rc_port, output_dir, config, topic): + def __init__(self, output_dir, config, topic, *args, **kwargs): self.context = zmq.Context() self.poller = zmq.Poller() self.topic = topic self.result_collector = self.context.socket(zmq.PULL) - self.result_collector.set_hwm(0) - self.result_collector.bind("tcp://*:{}".format(rc_port)) - self.external_publisher = self.context.socket(zmq.PUB) - self.external_publisher.bind("tcp://*:{}".format(config.get('external_publisher', 5002))) - self.stats_handler = StatsHandler(output_dir, config) - self.poller.register(self.result_collector, zmq.POLLIN) - - self.turrets_manager = TurretsManager(publish_port) + self._configure_sockets(config) + self.turrets_manager = TurretsManager(config.get('publish_port', 5000)) self.config = config self.started = False self.messages = 0 @@ -43,6 +38,23 @@ def __init__(self, publish_port, rc_port, output_dir, config, topic): print("Warmup") time.sleep(1) + def _configure_sockets(self, config, with_streamer=False, with_forwarder=False): + """Configure sockets for HQ + + :param dict config: test configuration + :param bool with_streamer: tell if we need to connect to streamer or simply bind + :param bool with_forwarder: tell if we need to connect to forwarder or simply bind + """ + rc_port = config.get('rc_port', 5001) + external_publisher = config.get('external_publisher', 5002) + + self.result_collector.set_hwm(0) + self.result_collector.bind("tcp://*:{}".format(rc_port)) + + self.external_publisher.bind("tcp://*:{}".format(external_publisher)) + + self.poller.register(self.result_collector, zmq.POLLIN) + def _process_socks(self, socks): if self.result_collector in socks: data = self.result_collector.recv_string() diff --git a/oct/utilities/run.py b/oct/utilities/run.py index b15524f..53fe0bc 100644 --- a/oct/utilities/run.py +++ b/oct/utilities/run.py @@ -37,9 +37,7 @@ def run(cmd_args): topic = cmd_args.publisher_channel or uuid.uuid4().hex print("External publishing topic is %s" % topic) - hq = HightQuarter(config.get('publish_port', 5000), - config.get('rc_port', 5001), - output_dir, config, topic) + hq = HightQuarter(output_dir, config, topic) hq.wait_turrets(config.get("min_turrets", 1)) hq.run() @@ -65,4 +63,6 @@ def run_command(sp): parser.add_argument('-p', '--publisher-channel', dest='publisher_channel', help='the channel for the external publisher', default=None) + parser.add_argument('-s', '--with-streamer', action='store_true', help="tell if HQ should connect to streamer") + parser.add_argument('-f', '--with-forwarder', action='store_true', help="tell if HQ should connect to forwarder") parser.set_defaults(func=run) From e606873262017866145a6d535a94b116db219930 Mon Sep 17 00:00:00 2001 From: karec Date: Mon, 27 Jun 2016 17:21:36 +0200 Subject: [PATCH 09/34] start devices implementation --- oct/core/devices.py | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 oct/core/devices.py diff --git a/oct/core/devices.py b/oct/core/devices.py new file mode 100644 index 0000000..375ee1b --- /dev/null +++ b/oct/core/devices.py @@ -0,0 +1,50 @@ +"""Contain all zeromq devices needed by OCT +""" +import zmq + + +def forwarder(frontend, backend): + """Simple pub/sub forwarder + + :param int frontend: fontend zeromq port + :param int backend: backend zeromq port + """ + try: + context = zmq.Context() + + frontend = context.socket(zmq.SUB) + frontend.bind("tcp://*:%d" % frontend) + + backend = context.socket(zmq.PUB) + backend.bind("tcp://*:%d" % backend) + + zmq.device(zmq.FORWARDER, frontend, backend) + except Exception as e: + print(e) + finally: + frontend.close() + backend.close() + context.term() + + +def streamer(frontend, backend): + """Simple push/pull streamer + + :param int frontend: fontend zeromq port + :param int backend: backend zeromq port + """ + try: + context = zmq.Context() + + frontend = context.socket(zmq.PULL) + frontend.set_hwm(0) + frontend.bind("tcp://*:%d" % frontend) + + backend = context.socket(zmq.PUSH) + backend.bind("tcp://*:%d" % backend) + except Exception as e: + print(e) + finally: + frontend.close() + backend.close() + context.term() From 262632cfe7a5ce50a419513a7e92873859b661e3 Mon Sep 17 00:00:00 2001 From: evalette Date: Tue, 28 Jun 2016 17:48:09 +0200 Subject: [PATCH 10/34] add run device command to start streamer or forwarder --- oct/core/devices.py | 33 +++++++++++++++++++-------------- oct/utilities/commands.py | 4 +++- oct/utilities/run.py | 8 -------- oct/utilities/run_device.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 oct/utilities/run_device.py diff --git a/oct/core/devices.py b/oct/core/devices.py index 375ee1b..9bd895d 100644 --- a/oct/core/devices.py +++ b/oct/core/devices.py @@ -1,5 +1,6 @@ """Contain all zeromq devices needed by OCT """ +from __future__ import print_function, unicode_literals import zmq @@ -12,18 +13,19 @@ def forwarder(frontend, backend): try: context = zmq.Context() - frontend = context.socket(zmq.SUB) - frontend.bind("tcp://*:%d" % frontend) + front_sub = context.socket(zmq.SUB) + front_sub.bind("tcp://*:%d" % frontend) - backend = context.socket(zmq.PUB) - backend.bind("tcp://*:%d" % backend) + back_pub = context.socket(zmq.PUB) + back_pub.bind("tcp://*:%d" % backend) - zmq.device(zmq.FORWARDER, frontend, backend) + print("forwarder started, backend on port : %d\tfrontend on port: %d" % (backend, frontend)) + zmq.device(zmq.FORWARDER, front_sub, back_pub) except Exception as e: print(e) finally: - frontend.close() - backend.close() + front_sub.close() + back_pub.close() context.term() @@ -36,15 +38,18 @@ def streamer(frontend, backend): try: context = zmq.Context() - frontend = context.socket(zmq.PULL) - frontend.set_hwm(0) - frontend.bind("tcp://*:%d" % frontend) + front_pull = context.socket(zmq.PULL) + front_pull.set_hwm(0) + front_pull.bind("tcp://*:%d" % frontend) - backend = context.socket(zmq.PUSH) - backend.bind("tcp://*:%d" % backend) + back_push = context.socket(zmq.PUSH) + back_push.bind("tcp://*:%d" % backend) + + print("streamer started, backend on port : %d\tfrontend on port: %d" % (backend, frontend)) + zmq.device(zmq.STREAMER, front_pull, back_push) except Exception as e: print(e) finally: - frontend.close() - backend.close() + front_pull.close() + back_push.close() context.term() diff --git a/oct/utilities/commands.py b/oct/utilities/commands.py index 4c66e81..d13c4c1 100644 --- a/oct/utilities/commands.py +++ b/oct/utilities/commands.py @@ -4,6 +4,7 @@ from oct.utilities.newproject import new_project from oct.utilities.pack import pack_turrets from oct.utilities.run import run_command +from oct.utilities.run_device import run_device_command from oct.tools.rebuild_results import rebuild_results from oct.tools.results_to_csv import results_to_csv @@ -13,7 +14,8 @@ pack_turrets, run_command, rebuild_results, - results_to_csv + results_to_csv, + run_device_command ] diff --git a/oct/utilities/run.py b/oct/utilities/run.py index 53fe0bc..ac143ed 100644 --- a/oct/utilities/run.py +++ b/oct/utilities/run.py @@ -1,11 +1,3 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -*- -# -# Copyright (c) 2010-2012 Corey Goldberg (corey@goldb.org) -# License: GNU LGPLv3 -# -# This file is part of Multi-Mechanize | Performance Test Framework -# from __future__ import print_function import os import shutil diff --git a/oct/utilities/run_device.py b/oct/utilities/run_device.py new file mode 100644 index 0000000..5145598 --- /dev/null +++ b/oct/utilities/run_device.py @@ -0,0 +1,32 @@ +"""Start a streamer or a forwarder device for multi-hq tests +""" +from __future__ import print_function + +from oct.core import devices + + +def start_device(name, frontend, backend): + """Start specified device + + :param str name: name of the device, MUST match one of ['forwarder', 'streamer'] + :param int frontend: frontend bind port for device + :param int backend: backend bind port for device + """ + device = getattr(devices, name) + device(frontend, backend) + + +def run_device(args): + start_device(args.device, args.frontend, args.backend) + + +def run_device_command(sp): + """ + Main function to run oct tests. + """ + parser = sp.add_parser('run-device', help="run an oct device for multi-HQ tests") + parser.add_argument('device', help="The project directory", choices=['forwarder', 'streamer']) + parser.add_argument('-f', '--frontend', help="frontend port", type=int, required=True) + parser.add_argument('-b', '--backend', help="backend port", type=int, required=True) + + parser.set_defaults(func=run_device) From 13207177a5b8a5115d660fe0a008ec26b18a40f0 Mon Sep 17 00:00:00 2001 From: evalette Date: Tue, 28 Jun 2016 18:01:52 +0200 Subject: [PATCH 11/34] add --no-results option to oct run, allowing to run test without generating html output --- oct/utilities/run.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/oct/utilities/run.py b/oct/utilities/run.py index ac143ed..fb4cf9a 100644 --- a/oct/utilities/run.py +++ b/oct/utilities/run.py @@ -34,7 +34,7 @@ def run(cmd_args): hq.run() print('\nanalyzing results...\n') - if output_results(output_dir, config): + if cmd_args.no_results is False and output_results(output_dir, config): print('created: %sresults.html\n' % output_dir) project_config = os.path.join(cmd_args.project_dir, project_name, 'config.json') @@ -57,4 +57,5 @@ def run_command(sp): default=None) parser.add_argument('-s', '--with-streamer', action='store_true', help="tell if HQ should connect to streamer") parser.add_argument('-f', '--with-forwarder', action='store_true', help="tell if HQ should connect to forwarder") + parser.add_argument('-o', '--no-results', action='store_true', help="if set, no results will be output") parser.set_defaults(func=run) From 4ca04b23e3da92f3061d4c38d0b1f9c5092bff72 Mon Sep 17 00:00:00 2001 From: evalette Date: Tue, 28 Jun 2016 18:58:09 +0200 Subject: [PATCH 12/34] * Update `configure` function, now only taking project path * Remove `-d` option to run command, project_name and project_path where used the same way * Updated unit tests for new parameters * Updated all functions using `configure` --- oct/tools/rebuild_results.py | 2 +- oct/utilities/configuration.py | 6 +++--- oct/utilities/run.py | 37 ++++++++++++++++++---------------- tests/test_configuration.py | 6 +++--- tests/test_hq.py | 6 +++--- 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/oct/tools/rebuild_results.py b/oct/tools/rebuild_results.py index f4888c7..9ec273b 100644 --- a/oct/tools/rebuild_results.py +++ b/oct/tools/rebuild_results.py @@ -7,7 +7,7 @@ def rebuild(args): - config = configure(None, None, args.config_file) + config = configure(None, args.config_file) if args.results_file is None: db_uri = get_db_uri(config, args.results_dir) diff --git a/oct/utilities/configuration.py b/oct/utilities/configuration.py index a8dc669..4ef7aa8 100644 --- a/oct/utilities/configuration.py +++ b/oct/utilities/configuration.py @@ -23,14 +23,14 @@ ] -def configure(project_name, cmd_opts, config_file=None): +def configure(project_path, config_file=None): """Get the configuration of the test and return it as a config object :return: the configured config object :rtype: Object """ if config_file is None: - config_file = os.path.join(cmd_opts.project_dir, project_name, 'config.json') + config_file = os.path.join(project_path, 'config.json') try: with open(config_file, 'r') as f: config = json.load(f) @@ -50,7 +50,7 @@ def configure_for_turret(project_name, config_file): :return: the loaded configuration :rtype: dict """ - config = configure(project_name, None, config_file) + config = configure(project_name, config_file) for key in WARNING_CONFIG_KEYS: if key not in config: print("WARNING: %s configuration key not present, the value will be set to default value" % key) diff --git a/oct/utilities/run.py b/oct/utilities/run.py index fb4cf9a..47c5a60 100644 --- a/oct/utilities/run.py +++ b/oct/utilities/run.py @@ -11,22 +11,27 @@ from oct.core.hq import HightQuarter -def run(cmd_args): +def generate_output_path(args, project_path): + """Generate default output directory + """ + milisec = datetime.now().microsecond + dirname = 'results_{}_{}'.format(time.strftime('%Y.%m.%d_%H.%M.%S', time.localtime()), str(milisec)) + return os.path.join(project_path, 'results', dirname) + + +def run(args): """Start an oct project - :param Namespace cmd_args: the commande-line arguments + :param Namespace args: the commande-line arguments """ - project_name = cmd_args.project_name - config = configure(project_name, cmd_args) + project_path = args.project_path + config = configure(project_path) - run_localtime = time.localtime() - milisec = datetime.now().microsecond - output_dir = '%s/%s/results/results_%s' % (cmd_args.project_dir, project_name, - time.strftime('%Y.%m.%d_%H.%M.%S_' + str(milisec) + '/', run_localtime)) + output_dir = generate_output_path(args, project_path) stats_handler.init_stats(output_dir, config) - topic = cmd_args.publisher_channel or uuid.uuid4().hex + topic = args.publisher_channel or uuid.uuid4().hex print("External publishing topic is %s" % topic) hq = HightQuarter(output_dir, config, topic) @@ -34,10 +39,10 @@ def run(cmd_args): hq.run() print('\nanalyzing results...\n') - if cmd_args.no_results is False and output_results(output_dir, config): - print('created: %sresults.html\n' % output_dir) + if args.no_results is False and output_results(output_dir, config): + print('created: %s/results.html\n' % output_dir) - project_config = os.path.join(cmd_args.project_dir, project_name, 'config.json') + project_config = os.path.join(project_path, 'config.json') saved_config = os.path.join(output_dir, 'config.json') shutil.copy(project_config, saved_config) print('done.\n') @@ -48,14 +53,12 @@ def run_command(sp): Main function to run oct tests. """ parser = sp.add_parser('run', help="run an oct project") - parser.add_argument('project_name', help="The project directory") - parser.add_argument('-r', '--results', dest='results_dir', help='results directory to reprocess') - parser.add_argument('-d', '--directory', dest='project_dir', help='directory containing project folder', - default='.') + parser.add_argument('project_path', help="The project directory") parser.add_argument('-p', '--publisher-channel', dest='publisher_channel', help='the channel for the external publisher', default=None) parser.add_argument('-s', '--with-streamer', action='store_true', help="tell if HQ should connect to streamer") parser.add_argument('-f', '--with-forwarder', action='store_true', help="tell if HQ should connect to forwarder") - parser.add_argument('-o', '--no-results', action='store_true', help="if set, no results will be output") + parser.add_argument('--no-results', action='store_true', help="if set, html report and graphs will not be generated") + parser.add_argument('-o', '--output-dir', help="output directory for test results", default=None) parser.set_defaults(func=run) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 1cd449b..5a78035 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -17,19 +17,19 @@ def setUp(self): def test_good_config(self): """Configuration should be parsable """ - configure(None, None, config_file=self.good_config) + configure(None, config_file=self.good_config) def test_bad_config(self): """Bad configuration file should correctly raise exceptions """ with self.assertRaises(OctConfigurationError): - configure(None, None, config_file=self.bad_config) + configure(None, config_file=self.bad_config) def test_missing_keys(self): """Missing keys in configuration should correctly raise exceptions """ with self.assertRaises(OctConfigurationError): - configure(None, None, config_file=self.missing_keys) + configure(None, config_file=self.missing_keys) def test_config_turrets(self): """Turrets must be configurable from a good config file diff --git a/tests/test_hq.py b/tests/test_hq.py index bbc891a..1836427 100644 --- a/tests/test_hq.py +++ b/tests/test_hq.py @@ -30,9 +30,9 @@ def run_bad_turret(): class CmdOpts(object): - project_dir = '/tmp/oct-test' - project_name = '.' + project_path = '/tmp/oct-test' publisher_channel = None + no_results = False class HQTest(unittest.TestCase): @@ -63,7 +63,7 @@ def test_run_argparse(self): """ sys.argv = sys.argv[:1] opts = CmdOpts() - sys.argv += ["run", opts.project_name, "-d", opts.project_dir] + sys.argv += ["run", opts.project_path] main() def test_create_errors(self): From d449c74c84a9e51f9746cd442025b61b21ada01c Mon Sep 17 00:00:00 2001 From: karec Date: Tue, 28 Jun 2016 22:03:59 +0200 Subject: [PATCH 13/34] start implementing master and slave logic #32 --- oct/core/hq.py | 5 +++-- oct/core/turrets_manager.py | 18 ++++++++++++++---- oct/results/stats_handler.py | 4 ++-- oct/utilities/run.py | 11 ++++++----- tests/test_stats_handler.py | 2 +- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/oct/core/hq.py b/oct/core/hq.py index e572177..914094d 100644 --- a/oct/core/hq.py +++ b/oct/core/hq.py @@ -19,17 +19,18 @@ class HightQuarter(object): :param bool with_forwarder: tell HQ if it should connects to forwarder, default False :param bool with_streamer: tell HQ if ti should connects to streamer, default False """ - def __init__(self, output_dir, config, topic, *args, **kwargs): + def __init__(self, output_dir, config, topic, master=True, *args, **kwargs): self.context = zmq.Context() self.poller = zmq.Poller() self.topic = topic + self.master = master self.result_collector = self.context.socket(zmq.PULL) self.external_publisher = self.context.socket(zmq.PUB) self.stats_handler = StatsHandler(output_dir, config) self._configure_sockets(config) - self.turrets_manager = TurretsManager(config.get('publish_port', 5000)) + self.turrets_manager = TurretsManager(config.get('publish_port', 5000), master) self.config = config self.started = False self.messages = 0 diff --git a/oct/core/turrets_manager.py b/oct/core/turrets_manager.py index ec37cdf..7579c37 100644 --- a/oct/core/turrets_manager.py +++ b/oct/core/turrets_manager.py @@ -9,17 +9,22 @@ class TurretsManager(object): """Turrets management while runing test. This class is in charge to send message to turrets and to store informations about active turrets + + :param int publish_port: pub socket port + :param bool master: tell if current HQ is a master. Only master can send messages to turrets """ STATUS_REQUEST = {'command': 'status_request', 'msg': None} START = {'command': 'start', 'msg': 'open fire'} STOP = {'command': 'stop', 'msg': 'premature stop'} - def __init__(self, publish_port): + def __init__(self, publish_port, master=True): self.turrets = {} + self.master = master - context = zmq.Context() - self.publisher = context.socket(zmq.PUB) - self.publisher.bind("tcp://*:{}".format(publish_port)) + if master: + context = zmq.Context() + self.publisher = context.socket(zmq.PUB) + self.publisher.bind("tcp://*:{}".format(publish_port)) def clean(self): self.publisher.close() @@ -45,6 +50,9 @@ def process_message(self, message, is_started=False): :param dict message: incomming message :param bool is_started: test started indicator """ + if not self.master: + return False + if 'status' not in message: return False message['name'] = message['turret'] @@ -97,6 +105,8 @@ def publish(self, message, channel=None): :param dict message: message to send to turrets :pram str channel: channel to send message, default to empty string """ + if not self.master: + return channel = channel or '' data = json.dumps(message) self.publisher.send_string("%s %s" % (channel, data)) diff --git a/oct/results/stats_handler.py b/oct/results/stats_handler.py index c2fe4f5..ec9b63e 100644 --- a/oct/results/stats_handler.py +++ b/oct/results/stats_handler.py @@ -13,8 +13,8 @@ def init_stats(output_dir, config): try: os.makedirs(output_dir, 0o755) - except OSError: - print("ERROR: Can not create output directory\n") + except OSError as e: + print("ERROR: Can not create output directory: %s\n" % e) raise db_uri = get_db_uri(config, output_dir) diff --git a/oct/utilities/run.py b/oct/utilities/run.py index 47c5a60..d504157 100644 --- a/oct/utilities/run.py +++ b/oct/utilities/run.py @@ -38,8 +38,8 @@ def run(args): hq.wait_turrets(config.get("min_turrets", 1)) hq.run() - print('\nanalyzing results...\n') - if args.no_results is False and output_results(output_dir, config): + if not args.no_results and output_results(output_dir, config): + print('\nanalyzing results...\n') print('created: %s/results.html\n' % output_dir) project_config = os.path.join(project_path, 'config.json') @@ -57,8 +57,9 @@ def run_command(sp): parser.add_argument('-p', '--publisher-channel', dest='publisher_channel', help='the channel for the external publisher', default=None) - parser.add_argument('-s', '--with-streamer', action='store_true', help="tell if HQ should connect to streamer") - parser.add_argument('-f', '--with-forwarder', action='store_true', help="tell if HQ should connect to forwarder") - parser.add_argument('--no-results', action='store_true', help="if set, html report and graphs will not be generated") + parser.add_argument('-n', '--number', type=int, + help="tell how many HQ will be spawned. You must adapt your configuration for this option") + parser.add_argument('--no-results', action='store_true', + help="if set, html report and graphs will not be generated") parser.add_argument('-o', '--output-dir', help="output directory for test results", default=None) parser.set_defaults(func=run) diff --git a/tests/test_stats_handler.py b/tests/test_stats_handler.py index 199a2b9..6130aaa 100644 --- a/tests/test_stats_handler.py +++ b/tests/test_stats_handler.py @@ -9,4 +9,4 @@ def test_bad_directory(self): """Not existing directory should raise error """ with self.assertRaises(OSError): - init_stats('/', '/') + init_stats('/', {}) From f7f6730b29ba0b9d0d6fa7b049c0ab6e607ebd1d Mon Sep 17 00:00:00 2001 From: karec Date: Tue, 28 Jun 2016 22:59:29 +0200 Subject: [PATCH 14/34] add unit tests for devices --- oct/core/devices.py | 4 ++-- tests/test_devices.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/test_devices.py diff --git a/oct/core/devices.py b/oct/core/devices.py index 9bd895d..a8d312c 100644 --- a/oct/core/devices.py +++ b/oct/core/devices.py @@ -20,7 +20,7 @@ def forwarder(frontend, backend): back_pub.bind("tcp://*:%d" % backend) print("forwarder started, backend on port : %d\tfrontend on port: %d" % (backend, frontend)) - zmq.device(zmq.FORWARDER, front_sub, back_pub) + zmq.proxy(front_sub, back_pub) except Exception as e: print(e) finally: @@ -46,7 +46,7 @@ def streamer(frontend, backend): back_push.bind("tcp://*:%d" % backend) print("streamer started, backend on port : %d\tfrontend on port: %d" % (backend, frontend)) - zmq.device(zmq.STREAMER, front_pull, back_push) + zmq.proxy(front_pull, back_push) except Exception as e: print(e) finally: diff --git a/tests/test_devices.py b/tests/test_devices.py new file mode 100644 index 0000000..dd4ad31 --- /dev/null +++ b/tests/test_devices.py @@ -0,0 +1,23 @@ +import unittest +from multiprocessing import Process + +from oct.core.devices import forwarder, streamer + + +class DevicesTest(unittest.TestCase): + + def test_forwarder(self): + """Should be able to start forwarder correctly + """ + p = Process(target=forwarder, args=(0, 0)) + p.start() + p.join(timeout=2) + p.terminate() + + def test_streamer(self): + """Should be able to start streamer + """ + p = Process(target=streamer, args=(0, 0)) + p.start() + p.join(timeout=2) + p.terminate() From 04310fb40af60a18ba3a9f15e26fae34c96b0fda Mon Sep 17 00:00:00 2001 From: karec Date: Tue, 28 Jun 2016 23:08:30 +0200 Subject: [PATCH 15/34] improve devices tests --- tests/test_devices.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tests/test_devices.py b/tests/test_devices.py index dd4ad31..92bb8bd 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -2,6 +2,14 @@ from multiprocessing import Process from oct.core.devices import forwarder, streamer +from oct.utilities.run_device import start_device, run_device + + +class DummyArgs: + + device = 'forwarder' + frontend = 0 + backend = 0 class DevicesTest(unittest.TestCase): @@ -11,7 +19,7 @@ def test_forwarder(self): """ p = Process(target=forwarder, args=(0, 0)) p.start() - p.join(timeout=2) + p.join(timeout=1) p.terminate() def test_streamer(self): @@ -19,5 +27,21 @@ def test_streamer(self): """ p = Process(target=streamer, args=(0, 0)) p.start() - p.join(timeout=2) + p.join(timeout=1) + p.terminate() + + def test_start_device_function(self): + """Should be able to start device with start_device function + """ + p = Process(target=start_device, args=('streamer', 0, 0)) + p.start() + p.join(timeout=1) + p.terminate() + + def test_run_device_function(self): + """Should be able start device with run_device function + """ + p = Process(target=start_device, args=('forwarder', 0, 0)) + p.start() + p.join(timeout=1) p.terminate() From 11742b9aecab818c7dcc361bd3a72b539852b606 Mon Sep 17 00:00:00 2001 From: karec Date: Tue, 28 Jun 2016 23:12:36 +0200 Subject: [PATCH 16/34] improve devices tests --- tests/test_devices.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_devices.py b/tests/test_devices.py index 92bb8bd..bfd0550 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -41,7 +41,7 @@ def test_start_device_function(self): def test_run_device_function(self): """Should be able start device with run_device function """ - p = Process(target=start_device, args=('forwarder', 0, 0)) + p = Process(target=run_device, args=(DummyArgs,)) p.start() p.join(timeout=1) p.terminate() From 6a7a2556bd77f2478e301a8b400b1e4d0aed3193 Mon Sep 17 00:00:00 2001 From: karec Date: Tue, 28 Jun 2016 23:22:04 +0200 Subject: [PATCH 17/34] refactor run utility --- oct/utilities/run.py | 45 +++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/oct/utilities/run.py b/oct/utilities/run.py index d504157..5fb0b0b 100644 --- a/oct/utilities/run.py +++ b/oct/utilities/run.py @@ -11,6 +11,37 @@ from oct.core.hq import HightQuarter +def process_results(no_results, output_dir, config): + """Process results and output them + """ + if no_results: + return + + print('\nanalyzing results...\n') + res = output_results(output_dir, config) + if res: + print('created: %s/results.html\n' % output_dir) + else: + print('results cannot be processed') + + +def copy_config(project_path, output_dir): + """Copy current config file to output directory + """ + project_config = os.path.join(project_path, 'config.json') + saved_config = os.path.join(output_dir, 'config.json') + shutil.copy(project_config, saved_config) + + +def start_hq(output_dir, config, topic, is_master=True): + """Start a HQ + """ + hq = HightQuarter(output_dir, config, topic) + if is_master: + hq.wait_turrets(config.get("min_turrets", 1)) + hq.run() + + def generate_output_path(args, project_path): """Generate default output directory """ @@ -34,17 +65,9 @@ def run(args): topic = args.publisher_channel or uuid.uuid4().hex print("External publishing topic is %s" % topic) - hq = HightQuarter(output_dir, config, topic) - hq.wait_turrets(config.get("min_turrets", 1)) - hq.run() - - if not args.no_results and output_results(output_dir, config): - print('\nanalyzing results...\n') - print('created: %s/results.html\n' % output_dir) - - project_config = os.path.join(project_path, 'config.json') - saved_config = os.path.join(output_dir, 'config.json') - shutil.copy(project_config, saved_config) + start_hq(output_dir, config, topic) + process_results(args.no_results, output_dir, config) + copy_config(project_path, output_dir) print('done.\n') From 63cbfb7df86404baa07cb87204b0c33f5f4db1f6 Mon Sep 17 00:00:00 2001 From: karec Date: Tue, 28 Jun 2016 23:22:16 +0200 Subject: [PATCH 18/34] add multiprocessing option for coveralls --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4a1301a..eeed624 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,8 @@ script: - python setup.py install - nosetests -vdx tests/ --with-coverage --cover-package=oct after_success: - coveralls + coveralls --concurrency=multiprocessing + coveralls combine deploy: provider: pypi user: Emmanuel.Valette From 98d9f0c410588ce9240638f9f1c2e6c380ff721f Mon Sep 17 00:00:00 2001 From: karec Date: Tue, 28 Jun 2016 23:37:55 +0200 Subject: [PATCH 19/34] prepare hq for forwarder #28 --- oct/core/hq.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/oct/core/hq.py b/oct/core/hq.py index 914094d..06216b7 100644 --- a/oct/core/hq.py +++ b/oct/core/hq.py @@ -18,6 +18,7 @@ class HightQuarter(object): :param str topic: topic for external publishing socket :param bool with_forwarder: tell HQ if it should connects to forwarder, default False :param bool with_streamer: tell HQ if ti should connects to streamer, default False + :param str streamer_address: streamer address to connect with form : : """ def __init__(self, output_dir, config, topic, master=True, *args, **kwargs): self.context = zmq.Context() @@ -35,10 +36,25 @@ def __init__(self, output_dir, config, topic, master=True, *args, **kwargs): self.started = False self.messages = 0 + with_streamer = kwargs.get('with_streamer', False) + streamer_address = None + if with_streamer: + streamer_address = kwargs.get('streamer_address', "127.0.0.1:{}".format(config.get('external_publisher'))) + + self._configure_external_publisher(config, with_streamer, streamer_address) + # waiting for init sockets print("Warmup") time.sleep(1) + def _configure_external_publisher(self, config, with_streamer=False, streamer_address=None): + external_publisher = config.get('external_publisher', 5002) if not streamer_address else streamer_address + + if with_streamer: + self.external_publisher.connect("tcp://{}".format(external_publisher)) + else: + self.external_publisher.bind("tcp://*:{}".format(external_publisher)) + def _configure_sockets(self, config, with_streamer=False, with_forwarder=False): """Configure sockets for HQ @@ -47,13 +63,10 @@ def _configure_sockets(self, config, with_streamer=False, with_forwarder=False): :param bool with_forwarder: tell if we need to connect to forwarder or simply bind """ rc_port = config.get('rc_port', 5001) - external_publisher = config.get('external_publisher', 5002) self.result_collector.set_hwm(0) self.result_collector.bind("tcp://*:{}".format(rc_port)) - self.external_publisher.bind("tcp://*:{}".format(external_publisher)) - self.poller.register(self.result_collector, zmq.POLLIN) def _process_socks(self, socks): From 45b408d8a8747d72157a6bdc364c1d23c00e4369 Mon Sep 17 00:00:00 2001 From: karec Date: Tue, 28 Jun 2016 23:54:31 +0200 Subject: [PATCH 20/34] update coverage rc and travis file --- .coveragerc | 3 +++ .travis.yml | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.coveragerc b/.coveragerc index 2f746c5..ba884e2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,5 @@ +[run] +concurrency=multiprocessing + [report] omit = oct/utilities/templates/scripts/v_user.j2 diff --git a/.travis.yml b/.travis.yml index eeed624..9bf235f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,8 @@ script: - python setup.py install - nosetests -vdx tests/ --with-coverage --cover-package=oct after_success: - coveralls --concurrency=multiprocessing - coveralls combine + coverage combine + coveralls deploy: provider: pypi user: Emmanuel.Valette From 343b02660ae2e12e08ef8b14276ddd30d94598ab Mon Sep 17 00:00:00 2001 From: karec Date: Wed, 29 Jun 2016 00:03:10 +0200 Subject: [PATCH 21/34] update travis file --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9bf235f..4a1301a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ script: - python setup.py install - nosetests -vdx tests/ --with-coverage --cover-package=oct after_success: - coverage combine coveralls deploy: provider: pypi From e77d59662dbd961049dc8e14d5481365a2b1975b Mon Sep 17 00:00:00 2001 From: karec Date: Wed, 29 Jun 2016 00:10:38 +0200 Subject: [PATCH 22/34] update travis --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4a1301a..23d52b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,8 @@ script: - python setup.py install - nosetests -vdx tests/ --with-coverage --cover-package=oct after_success: - coveralls + - coverage combine + - coveralls deploy: provider: pypi user: Emmanuel.Valette From 8d3771214fea8cd36f1f90f66f22031b65b8a21b Mon Sep 17 00:00:00 2001 From: evalette Date: Wed, 29 Jun 2016 09:51:36 +0200 Subject: [PATCH 23/34] update coveragerc --- .coveragerc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index ba884e2..c346f5c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,4 +2,7 @@ concurrency=multiprocessing [report] -omit = oct/utilities/templates/scripts/v_user.j2 +omit = + oct/utilities/templates/scripts/v_user.j2 + oct/core/devices.py + oct/utilities/run_device.py From cb6dc61f4dc2e512e40aa3bb368a1357f27d2cec Mon Sep 17 00:00:00 2001 From: evalette Date: Wed, 29 Jun 2016 11:33:56 +0200 Subject: [PATCH 24/34] first working implementation of external forwarder #28 --- oct/core/devices.py | 2 ++ oct/core/hq.py | 21 +++++++++++++-------- oct/utilities/run.py | 31 ++++++++++++++++++++----------- tests/test_hq.py | 9 ++++++--- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/oct/core/devices.py b/oct/core/devices.py index a8d312c..2878f4e 100644 --- a/oct/core/devices.py +++ b/oct/core/devices.py @@ -16,6 +16,8 @@ def forwarder(frontend, backend): front_sub = context.socket(zmq.SUB) front_sub.bind("tcp://*:%d" % frontend) + front_sub.setsockopt_string(zmq.SUBSCRIBE, "") + back_pub = context.socket(zmq.PUB) back_pub.bind("tcp://*:%d" % backend) diff --git a/oct/core/hq.py b/oct/core/hq.py index 06216b7..516cffd 100644 --- a/oct/core/hq.py +++ b/oct/core/hq.py @@ -36,21 +36,26 @@ def __init__(self, output_dir, config, topic, master=True, *args, **kwargs): self.started = False self.messages = 0 - with_streamer = kwargs.get('with_streamer', False) - streamer_address = None - if with_streamer: - streamer_address = kwargs.get('streamer_address', "127.0.0.1:{}".format(config.get('external_publisher'))) + print(kwargs) - self._configure_external_publisher(config, with_streamer, streamer_address) + with_forwarder = kwargs.get('with_forwarder', False) + forwarder_address = None + if with_forwarder is True: + forwarder_address = kwargs.get('forwarder_address', None) + if forwarder_address is None: + forwarder_address = "127.0.0.1:{}".format(config.get('external_publisher', 5002)) + + self._configure_external_publisher(config, with_forwarder, forwarder_address) # waiting for init sockets print("Warmup") time.sleep(1) - def _configure_external_publisher(self, config, with_streamer=False, streamer_address=None): - external_publisher = config.get('external_publisher', 5002) if not streamer_address else streamer_address + def _configure_external_publisher(self, config, with_forwarder=False, forwarder_address=None): + external_publisher = config.get('external_publisher', 5002) if not forwarder_address else forwarder_address - if with_streamer: + print(external_publisher) + if with_forwarder: self.external_publisher.connect("tcp://{}".format(external_publisher)) else: self.external_publisher.bind("tcp://*:{}".format(external_publisher)) diff --git a/oct/utilities/run.py b/oct/utilities/run.py index 5fb0b0b..f90269c 100644 --- a/oct/utilities/run.py +++ b/oct/utilities/run.py @@ -11,12 +11,9 @@ from oct.core.hq import HightQuarter -def process_results(no_results, output_dir, config): +def process_results(output_dir, config): """Process results and output them """ - if no_results: - return - print('\nanalyzing results...\n') res = output_results(output_dir, config) if res: @@ -33,10 +30,10 @@ def copy_config(project_path, output_dir): shutil.copy(project_config, saved_config) -def start_hq(output_dir, config, topic, is_master=True): +def start_hq(output_dir, config, topic, is_master=True, **kwargs): """Start a HQ """ - hq = HightQuarter(output_dir, config, topic) + hq = HightQuarter(output_dir, config, topic, **kwargs) if is_master: hq.wait_turrets(config.get("min_turrets", 1)) hq.run() @@ -55,18 +52,26 @@ def run(args): :param Namespace args: the commande-line arguments """ - project_path = args.project_path + kwargs = vars(args) + + if 'func' in kwargs: + del kwargs['func'] + + project_path = kwargs.pop('project_path') config = configure(project_path) - output_dir = generate_output_path(args, project_path) + output_dir = kwargs.pop('output_dir', None) or generate_output_path(args, project_path) stats_handler.init_stats(output_dir, config) topic = args.publisher_channel or uuid.uuid4().hex print("External publishing topic is %s" % topic) - start_hq(output_dir, config, topic) - process_results(args.no_results, output_dir, config) + start_hq(output_dir, config, topic, **kwargs) + + if not args.no_results: + process_results(output_dir, config) + copy_config(project_path, output_dir) print('done.\n') @@ -84,5 +89,9 @@ def run_command(sp): help="tell how many HQ will be spawned. You must adapt your configuration for this option") parser.add_argument('--no-results', action='store_true', help="if set, html report and graphs will not be generated") - parser.add_argument('-o', '--output-dir', help="output directory for test results", default=None) + parser.add_argument('-o', '--output-dir', help="output directory for test results") + parser.add_argument('--with-forwarder', action='store_true', + help="Set if HQ should connect to external forwarder") + parser.add_argument('--forwarder-address', + help="with form ip:port. If not set and --with-forwarder flag present HQ will use default values") parser.set_defaults(func=run) diff --git a/tests/test_hq.py b/tests/test_hq.py index 1836427..4bfbead 100644 --- a/tests/test_hq.py +++ b/tests/test_hq.py @@ -30,9 +30,12 @@ def run_bad_turret(): class CmdOpts(object): - project_path = '/tmp/oct-test' - publisher_channel = None - no_results = False + + def __init__(self): + + self.project_path = '/tmp/oct-test' + self.publisher_channel = None + self.no_results = False class HQTest(unittest.TestCase): From 344d55da96a77e70e708dcbfedc1407b7ed2ffd2 Mon Sep 17 00:00:00 2001 From: evalette Date: Thu, 7 Jul 2016 17:33:11 +0200 Subject: [PATCH 25/34] remove unused option in run command (for the moment) and update changelog --- changelog.txt | 7 ++++++- oct/utilities/run.py | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 72c2c0e..5cded2f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,11 @@ * allow database configuration in project configuration, you can now use any peewee supported database backend * update rebuild command to take database configuration in consideration +* devices has been added to project, allowing you to easily start a zmq forwarder or streamer for more complex tests +* `oct run` command has been reworked. Some useless and undocumented arguments has been removed and new arguments has been added : + * ``-o`` allowing to choose output directory for tests results + * ``--with-forwarder`` start HQ with external publisher forwarder, allowing you to connect to forwarder device instead of simply binding a PUB socket + * ``--forwarder-address`` allow you to set ip and port for external forwarder with format :. If not set but ``--with-forwarder`` is present default will be used (localhost:5002). Default port is based on your configuration and use the ``external_publisher`` configuration key Backward compatibility break ---------------------------- @@ -177,7 +182,7 @@ Backward compatibility breaks as been removed. Transaction now inherit by default from the BaseTransaction class located in the oct-turrets package * A new command has been added `oct-pack-turrets` that will create a tar file from a test folder containing all files and configuration required for running a turret. This command will generate a tar archive per turret in your project `config.json` file -* More informations are stored in the results for the turrets and the report display the last known status of a turret and the associated last update time +## Last update Thu Jul 7 17:30:27 2016 VALETTE Emmanuel * You can hotplug a turret while a test is running. This turret will appear in the final report * The HQ will sleep for 1 second after the creation of the sockets to wait for them * Remove the GenericTransaction class in oct.core.generic diff --git a/oct/utilities/run.py b/oct/utilities/run.py index f90269c..33f5b5d 100644 --- a/oct/utilities/run.py +++ b/oct/utilities/run.py @@ -85,8 +85,6 @@ def run_command(sp): parser.add_argument('-p', '--publisher-channel', dest='publisher_channel', help='the channel for the external publisher', default=None) - parser.add_argument('-n', '--number', type=int, - help="tell how many HQ will be spawned. You must adapt your configuration for this option") parser.add_argument('--no-results', action='store_true', help="if set, html report and graphs will not be generated") parser.add_argument('-o', '--output-dir', help="output directory for test results") From 64e34e28a4004cfbf8477a6eb1c7e9dee81478c8 Mon Sep 17 00:00:00 2001 From: evalette Date: Thu, 7 Jul 2016 17:43:02 +0200 Subject: [PATCH 26/34] bump version to 0.4.6 --- oct/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oct/__init__.py b/oct/__init__.py index 68eb9b6..ab45471 100644 --- a/oct/__init__.py +++ b/oct/__init__.py @@ -1 +1 @@ -__version__ = '0.4.5' +__version__ = '0.4.6' From 35ab7b2639797917d21932656422d95abc2f836c Mon Sep 17 00:00:00 2001 From: evalette Date: Thu, 7 Jul 2016 17:50:02 +0200 Subject: [PATCH 27/34] remove print satement --- oct/core/hq.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/oct/core/hq.py b/oct/core/hq.py index 516cffd..eb0b897 100644 --- a/oct/core/hq.py +++ b/oct/core/hq.py @@ -36,8 +36,6 @@ def __init__(self, output_dir, config, topic, master=True, *args, **kwargs): self.started = False self.messages = 0 - print(kwargs) - with_forwarder = kwargs.get('with_forwarder', False) forwarder_address = None if with_forwarder is True: From d5cffb86957590235ef67c4f87d1bbe4ae3c02a7 Mon Sep 17 00:00:00 2001 From: evalette Date: Thu, 7 Jul 2016 18:21:05 +0200 Subject: [PATCH 28/34] improve hq tests --- tests/test_hq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hq.py b/tests/test_hq.py index 4bfbead..abcf0e4 100644 --- a/tests/test_hq.py +++ b/tests/test_hq.py @@ -66,7 +66,7 @@ def test_run_argparse(self): """ sys.argv = sys.argv[:1] opts = CmdOpts() - sys.argv += ["run", opts.project_path] + sys.argv += ["run", opts.project_path, "--with-forwarder"] main() def test_create_errors(self): From 659185a3d21ab13716e44678dafbb15ae4975c51 Mon Sep 17 00:00:00 2001 From: karec Date: Fri, 8 Jul 2016 00:14:54 +0200 Subject: [PATCH 29/34] add new line before end of docstring and function delclaration --- oct/core/hq.py | 1 + 1 file changed, 1 insertion(+) diff --git a/oct/core/hq.py b/oct/core/hq.py index 06216b7..2d443a6 100644 --- a/oct/core/hq.py +++ b/oct/core/hq.py @@ -20,6 +20,7 @@ class HightQuarter(object): :param bool with_streamer: tell HQ if ti should connects to streamer, default False :param str streamer_address: streamer address to connect with form : : """ + def __init__(self, output_dir, config, topic, master=True, *args, **kwargs): self.context = zmq.Context() self.poller = zmq.Poller() From 809e4837c2310028c8ca8e91b34a8c397187d5f1 Mon Sep 17 00:00:00 2001 From: karec Date: Fri, 8 Jul 2016 10:06:21 +0200 Subject: [PATCH 30/34] improve stats_hanler tests --- tests/test_stats_handler.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/test_stats_handler.py b/tests/test_stats_handler.py index 6130aaa..343207b 100644 --- a/tests/test_stats_handler.py +++ b/tests/test_stats_handler.py @@ -1,12 +1,39 @@ +import os +import shutil import unittest -from oct.results.stats_handler import init_stats +from oct.utilities.configuration import configure +from oct.results.stats_handler import init_stats, StatsHandler + +BASE_DIR = os.path.dirname(os.path.realpath(__file__)) class ReportTest(unittest.TestCase): + def setUp(self): + config_file = os.path.join(BASE_DIR, 'fixtures', 'config.json') + self.config = configure(None, config_file) + self.handler = StatsHandler(None, self.config) + def test_bad_directory(self): """Not existing directory should raise error """ with self.assertRaises(OSError): init_stats('/', {}) + + def test_write_results(self): + init_stats('/tmp/oct_stats_tests', self.config) + data = { + 'elapsed': 1.1, + 'epoch': 22222222222, + 'scriptrun_time': 20, + 'error': '', + 'custom_timers': {} + } + for i in range(490): + self.handler.write_result(data.copy()) + self.handler.write_remaining() + + def tearDown(self): + if os.path.exists('/tmp/oct_stats_tests'): + shutil.rmtree('/tmp/oct_stats_tests') From d28ce53f1391c5f8ac47a85087c4fcddc150853f Mon Sep 17 00:00:00 2001 From: karec Date: Fri, 8 Jul 2016 10:47:51 +0200 Subject: [PATCH 31/34] improve unit tests again --- oct/core/hq.py | 2 +- oct/results/stats_handler.py | 2 +- oct/tools/rebuild_results.py | 4 ++-- tests/fixtures/bad_rebuild_config.json | 14 ++++++++++++++ tests/test_stats_handler.py | 2 +- tests/test_tools.py | 9 +++++++++ 6 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/bad_rebuild_config.json diff --git a/oct/core/hq.py b/oct/core/hq.py index e2e32c5..49975b3 100644 --- a/oct/core/hq.py +++ b/oct/core/hq.py @@ -29,7 +29,7 @@ def __init__(self, output_dir, config, topic, master=True, *args, **kwargs): self.result_collector = self.context.socket(zmq.PULL) self.external_publisher = self.context.socket(zmq.PUB) - self.stats_handler = StatsHandler(output_dir, config) + self.stats_handler = StatsHandler(output_dir) self._configure_sockets(config) self.turrets_manager = TurretsManager(config.get('publish_port', 5000), master) diff --git a/oct/results/stats_handler.py b/oct/results/stats_handler.py index ec9b63e..bf5c5dc 100644 --- a/oct/results/stats_handler.py +++ b/oct/results/stats_handler.py @@ -31,7 +31,7 @@ class StatsHandler(object): """This class will handle results and stats comming from the turrets :param str output_dir: the output directory for the results """ - def __init__(self, output_dir, config, context=None): + def __init__(self, output_dir): self.output_dir = output_dir self.turret_name = 'Turret' self.results = [] diff --git a/oct/tools/rebuild_results.py b/oct/tools/rebuild_results.py index 9ec273b..5f63848 100644 --- a/oct/tools/rebuild_results.py +++ b/oct/tools/rebuild_results.py @@ -1,5 +1,5 @@ import six -from argparse import ArgumentError +from oct.core.exceptions import OctConfigurationError from oct.results.output import output from oct.results.models import db, set_database @@ -15,7 +15,7 @@ def rebuild(args): db_uri = args.results_file if not db_uri: - raise ArgumentError("Bad database configured, if you use sqlite database use -f option") + raise OctConfigurationError("Bad database configured, if you use sqlite database use -f option") set_database(db_uri, db, config) output(args.results_dir, config) diff --git a/tests/fixtures/bad_rebuild_config.json b/tests/fixtures/bad_rebuild_config.json new file mode 100644 index 0000000..b59598c --- /dev/null +++ b/tests/fixtures/bad_rebuild_config.json @@ -0,0 +1,14 @@ +{ + "run_time": 10, + "results_ts_interval": 1, + "progress_bar": true, + "console_logging": false, + "testing": false, + "min_turrets": 1, + "turrets": [ + {"name": "navigation", "cannons": 2, "rampup": 0, "script": "v_user.py"} + ], + "results_database": { + "db_uri": null + } +} diff --git a/tests/test_stats_handler.py b/tests/test_stats_handler.py index 343207b..0a3690f 100644 --- a/tests/test_stats_handler.py +++ b/tests/test_stats_handler.py @@ -13,7 +13,7 @@ class ReportTest(unittest.TestCase): def setUp(self): config_file = os.path.join(BASE_DIR, 'fixtures', 'config.json') self.config = configure(None, config_file) - self.handler = StatsHandler(None, self.config) + self.handler = StatsHandler(None) def test_bad_directory(self): """Not existing directory should raise error diff --git a/tests/test_tools.py b/tests/test_tools.py index 7e2ac4b..416c318 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -3,6 +3,7 @@ import shutil import unittest +from oct.core.exceptions import OctConfigurationError from oct.utilities.commands import main @@ -13,6 +14,7 @@ def setUp(self): self.output_file = '/tmp/oct-test-output.csv' self.results_file = os.path.join(self.base_dir, 'fixtures', 'results.sqlite') self.config_file = os.path.join(self.base_dir, 'fixtures', 'rebuild_config.json') + self.bad_config = os.path.join(self.base_dir, 'fixtures', 'bad_rebuild_config.json') self.rebuild_project_dir = '/tmp/rebuild_results' self.rebuild_dir = '/tmp/rebuild_results/results/test' @@ -54,6 +56,13 @@ def test_rebuild_results(self): sys.argv += ["rebuild-results", self.rebuild_dir, self.config_file, "-f", self.results_file] main() + def test_rebuild_results_error(self): + """rebuild results command should correctly raise errors when bad configured""" + sys.argv = sys.argv[:1] + sys.argv += ['rebuild-results', self.rebuild_dir, self.bad_config] + with self.assertRaises(OctConfigurationError): + main() + def tearDown(self): if os.path.exists(self.output_file): os.remove(self.output_file) From b1197076326b968802560ef4a5f6bd9c887aefe4 Mon Sep 17 00:00:00 2001 From: karec Date: Fri, 8 Jul 2016 10:50:27 +0200 Subject: [PATCH 32/34] refactor stats handler and remove useless properties and parameters --- oct/core/hq.py | 2 +- oct/results/stats_handler.py | 8 ++------ tests/test_stats_handler.py | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/oct/core/hq.py b/oct/core/hq.py index 49975b3..4175a84 100644 --- a/oct/core/hq.py +++ b/oct/core/hq.py @@ -29,7 +29,7 @@ def __init__(self, output_dir, config, topic, master=True, *args, **kwargs): self.result_collector = self.context.socket(zmq.PULL) self.external_publisher = self.context.socket(zmq.PUB) - self.stats_handler = StatsHandler(output_dir) + self.stats_handler = StatsHandler() self._configure_sockets(config) self.turrets_manager = TurretsManager(config.get('publish_port', 5000), master) diff --git a/oct/results/stats_handler.py b/oct/results/stats_handler.py index bf5c5dc..749c7f3 100644 --- a/oct/results/stats_handler.py +++ b/oct/results/stats_handler.py @@ -28,12 +28,8 @@ def init_stats(output_dir, config): class StatsHandler(object): - """This class will handle results and stats comming from the turrets - :param str output_dir: the output directory for the results - """ - def __init__(self, output_dir): - self.output_dir = output_dir - self.turret_name = 'Turret' + """This class will handle results and stats comming from the turrets""" + def __init__(self): self.results = [] def write_result(self, data): diff --git a/tests/test_stats_handler.py b/tests/test_stats_handler.py index 0a3690f..36eb1b1 100644 --- a/tests/test_stats_handler.py +++ b/tests/test_stats_handler.py @@ -13,7 +13,7 @@ class ReportTest(unittest.TestCase): def setUp(self): config_file = os.path.join(BASE_DIR, 'fixtures', 'config.json') self.config = configure(None, config_file) - self.handler = StatsHandler(None) + self.handler = StatsHandler() def test_bad_directory(self): """Not existing directory should raise error From 0a85580640138689424b15c75de7ceaa0c341a3c Mon Sep 17 00:00:00 2001 From: karec Date: Fri, 8 Jul 2016 10:55:08 +0200 Subject: [PATCH 33/34] improve test turrets manager --- tests/test_turrets_manager.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_turrets_manager.py b/tests/test_turrets_manager.py index c7efc4c..2cb5d5a 100644 --- a/tests/test_turrets_manager.py +++ b/tests/test_turrets_manager.py @@ -39,6 +39,13 @@ def test_update(self): res = self.manager.update({'not': 'present'}) self.assertFalse(res) + def test_master_false(self): + """Turrets manager should act correctly in case of master set to false""" + self.manager.master = False + self.manager.process_message("bad") + self.manager.publish("bad") + self.manager.master = True + def tearDown(self): for turret in Turret.select(): turret.delete_instance() From c3ae6ba954c56a093026c94ad3df81ba3ec6adad Mon Sep 17 00:00:00 2001 From: karec Date: Fri, 8 Jul 2016 11:14:27 +0200 Subject: [PATCH 34/34] improve tests utilities --- tests/test_utilities.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_utilities.py b/tests/test_utilities.py index bb7f49f..2e3706e 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -80,6 +80,15 @@ def test_pack_errors(self): with self.assertRaises(IOError): main() + sys.argv = sys.argv[:1] + sys.argv += ["pack-turrets", self.test_dir] + os.chmod(self.test_dir, 0o444) + + with self.assertRaises(IOError): + main() + + os.chmod(self.test_dir, 0o777) + def tearDown(self): if os.path.exists(self.valid_dir): shutil.rmtree(self.valid_dir)