diff --git a/pscheduler-archiver-bitbucket/testbed.json b/pscheduler-archiver-bitbucket/testbed.json new file mode 100644 index 000000000..3c55659ee --- /dev/null +++ b/pscheduler-archiver-bitbucket/testbed.json @@ -0,0 +1,28 @@ +{ + "tasks": { + "pscheduler-archiver-bitbucket-basic": { + "_meta": { + "display-name": "Bitbucket Archiver - Basic" + }, + "group": "list", + "schedule": "PT1H", + "test": "pscheduler-archiver-bitbucket-basic" + } + }, + "tests": { + "pscheduler-archiver-bitbucket-basic": { + "type": "noop", + "spec": { + "host": "{% address[0] %}" + } + } + }, + "archives": { + "pscheduler-archiver-bitbucket": { + "label": "Bitbucket Archiver", + "archiver": "bitbucket", + "data": { + } + } + } +} diff --git a/pscheduler-archiver-failer/testbed.json b/pscheduler-archiver-failer/testbed.json new file mode 100644 index 000000000..1869f24ba --- /dev/null +++ b/pscheduler-archiver-failer/testbed.json @@ -0,0 +1,33 @@ +{ + "tasks": { + "pscheduler-archiver-failer-basic": { + "_meta": { + "display-name": "Failer Archiver - Basic" + }, + "group": "list", + "schedule": "PT1H", + "test": "pscheduler-archiver-failer-basic" + } + }, + "tests": { + "pscheduler-archiver-failer-basic": { + "type": "noop", + "spec": { + "host": "{% address[0] %}" + } + } + }, + "archives": { + "pscheduler-archiver-failer": { + "label": "Failer Archiver - Expect some failures", + "archiver": "failer", + "data": { + "schema": 2, + "delay": "PT0S", + "fail": 0.5, + "retry": 0.5, + "badly": 0.5 + } + } + } +} diff --git a/pscheduler-archiver-syslog/testbed.json b/pscheduler-archiver-syslog/testbed.json new file mode 100644 index 000000000..79dcbe1a6 --- /dev/null +++ b/pscheduler-archiver-syslog/testbed.json @@ -0,0 +1,35 @@ +{ + "tasks": { + "pscheduler-archiver-syslog-basic": { + "_meta": { + "display-name": "Syslog Archiver - Basic" + }, + "group": "list", + "schedule": "PT1H", + "test": "pscheduler-archiver-syslog-basic" + } + }, + "tests": { + "pscheduler-archiver-syslog-basic": { + "type": "noop", + "spec": { + "host": "{% address[0] %}" + } + } + }, + "archives": { + "pscheduler-archiver-syslog": { + "label": "Syslog Archiver", + "archiver": "syslog", + "data": { + "ident": "pscheduler-archiver-syslog", + "facility": "local4", + "priority": "info" + }, + "transform": { + "script": "\"Syslog Archiver: \\(.test.type) with \\(.tool.name)\"", + "output-raw": true + } + } + } +} diff --git a/pscheduler-tool-iperf3/testbed.json b/pscheduler-tool-iperf3/testbed.json new file mode 100644 index 000000000..f33c8a97f --- /dev/null +++ b/pscheduler-tool-iperf3/testbed.json @@ -0,0 +1,42 @@ +{ + "tasks": { + "pscheduler-tool-iperf3-tcp": { + "_meta": { + "display-name": "Throughput - Iperf3 TCP" + }, + "group": "mesh", + "schedule": "PT4H", + "test": "pscheduler-tool-iperf3-tcp", + "tools": [ "iperf3" ] + }, + "pscheduler-tool-iperf3-udp": { + "_meta": { + "display-name": "Throughput - Iperf3 UDP" + }, + "group": "mesh", + "schedule": "PT4H", + "test": "pscheduler-tool-iperf3-udp", + "tools": [ "iperf3" ] + } + + }, + "tests": { + "pscheduler-tool-iperf3-tcp": { + "type": "throughput", + "spec": { + "source": "{% address[0] %}", + "dest": "{% address[1] %}", + "duration": "PT20S" + } + }, + "pscheduler-tool-iperf3-udp": { + "type": "throughput", + "spec": { + "udp": true, + "source": "{% address[0] %}", + "dest": "{% address[1] %}", + "duration": "PT20S" + } + } + } +} diff --git a/pscheduler-tool-ping/testbed.json b/pscheduler-tool-ping/testbed.json new file mode 100644 index 000000000..8c3dd56af --- /dev/null +++ b/pscheduler-tool-ping/testbed.json @@ -0,0 +1,22 @@ +{ + "tasks": { + "pscheduler-tool-ping-basic": { + "_meta": { + "display-name": "RTT - Ping" + }, + "group": "mesh", + "schedule": "PT5M", + "test": "pscheduler-tool-ping-basic", + "tools": [ "ping" ] + } + }, + "tests": { + "pscheduler-tool-ping-basic": { + "type": "rtt", + "spec": { + "source": "{% address[0] %}", + "dest": "{% address[1] %}" + } + } + } +} diff --git a/pscheduler-tool-traceroute/testbed.json b/pscheduler-tool-traceroute/testbed.json new file mode 100644 index 000000000..50e0c4506 --- /dev/null +++ b/pscheduler-tool-traceroute/testbed.json @@ -0,0 +1,22 @@ +{ + "tasks": { + "pscheduler-tool-traceroute-basic": { + "_meta": { + "display-name": "RTT - Traceroute" + }, + "group": "mesh", + "schedule": "PT5M", + "test": "pscheduler-tool-traceroute-basic", + "tools": [ "traceroute" ] + } + }, + "tests": { + "pscheduler-tool-traceroute-basic": { + "type": "trace", + "spec": { + "source": "{% address[0] %}", + "dest": "{% address[1] %}" + } + } + } +} diff --git a/testbed/Makefile b/testbed/Makefile index 5782e25e7..920cf1b66 100644 --- a/testbed/Makefile +++ b/testbed/Makefile @@ -7,20 +7,24 @@ PRODUCT := testbed.json # Each subdirectory's Makefile produces a file called "output.json" SUBDIRS := \ skeleton \ + members \ plugins \ schedules default: build -$(PRODUCT): Makefile - @for SUBDIR in $(SUBDIRS) ; \ + +$(PRODUCT):: + @set -e && for SUBDIR in $(SUBDIRS) ; \ do \ printf "\n#\n# Building $$SUBDIR\n#\n\n" ; \ $(MAKE) -C "$${SUBDIR}" ; \ done printf "\n#\n# Merging results\n#\n\n" - jq -s add $(SUBDIRS:%=%/output.json) > "$@" + ./bin/merge-json $(SUBDIRS:%=%/output.json) \ + | ./bin/finish \ + > "$@" TO_CLEAN += $(PRODUCT) @@ -28,8 +32,9 @@ build: $(PRODUCT) clean: - @for SUBDIR in $(SUBDIRS) ; \ + @set -e && for SUBDIR in $(SUBDIRS) ; \ do \ $(MAKE) -C "$${SUBDIR}" "$@" ; \ done - rm -rf $(TO_CLEAN) *~ + rm -rf $(TO_CLEAN) + find . -name '*~' | xargs rm -rf diff --git a/testbed/README.md b/testbed/README.md index 2d88b7a29..f88c3f0a0 100644 --- a/testbed/README.md +++ b/testbed/README.md @@ -1,53 +1,112 @@ # pScheduler Test Bed Configuration -Scattered throughout the sources for the pScheduler plugins are files named `psconfig-testbed`. +## Building the Configuration -# Tes + * Install the prerequisites: + * Red Hat: `dnf -y install make python3` + * Debian: `apt-get -y install make python3` + + * Run `make` + + * Find the ready-to-use mesh in `testbed.json`. + + +## Configuration Files + + +`skeleton/output.json` - Contains a skeletal version of a pSConfig +file and includes globally-significant data such as the administrator +information. All other configuration is built atop this one. + +`members/members` - Lists the hosts that will be part of the test bed. +Each entry consists of a FQDN and a label that will be translated into +an `address` entry in the finished pSConfig file. The address entries +will be merged into groups as described below. + +`schedules/intervals` - Lists the ISO 8601 durations that will be made +available as standard entries in the `schedules` section of the +pSConfig file. + + +## Plugin Configuration Files + +Each plugin may, optionally, contribute tests and tasks to the +configuration by placing a file named `testbed.json` in its top-level +directory (e.g., `pscheduler-tool-iperf2/testbed.json`). Note that +only directories for known plugin types (`archiver` , `context` , +`test` or `tool`) will be probed for these files. + +Inside the file + + +Identifiers for tests and tasks must be unique to avoid colliding with +those in other plugins. The recommended convention is to use the full +name of the plugin package as a prefix, (e.g., +`pscheduler-tool-iperf3-udp`). + + + + +Construction of the final `testbed.json` + + * Each measurement will be sent to all of the archivers listed in the + `archives` section. + +A suitable sample for each type may be found in these plugins: + + * Test: `pscheduler-test-idle/testbed.json` + * Tool: `pscheduler-tool-iperf3/testbed.json` + * Archiver: `pscheduler-archiver-syslog/testbed.json` + * Context: `TODO:` ``` { - "_meta": { - }, - "addresses": {}, - "archives": { - }, - "groups": { - }, - "schedules": { - "PT5M" { - "repeat": "PT5M", - "slip": "PT2M30S", - "sliprand": true - } - }, "tasks": { + "pscheduler-tool-iperf-tcp": { + "_meta": { + "display-name": "Throughput - Iperf3 TCP" + }, + "schedule": "PT4H", + "test": "pscheduler-tool-iperf3-tcp", + "tools": [ "iperf3" ] + }, + "pscheduler-tool-iperf3-udp": { + "_meta": { + "display-name": "Throughput - Iperf3 UDP" + }, + "schedule": "PT4H", + "test": "pscheduler-tool-iperf3-udp", + "tools": [ "iperf3" ] + } + }, "tests": { + "pscheduler-tool-iperf3-tcp": { + "type": "throughput", + "spec": { + "source": "{% address[0] %}", + "dest": "{% address[1] %}", + "duration": "PT20S" + } + }, + "pscheduler-tool-iperf3-udp": { + "type": "throughput", + "spec": { + "udp": true, + "source": "{% address[0] %}", + "dest": "{% address[1] %}", + "duration": "PT20S" + } + } } } +``` -## Naming - -Anything added by a plugin package should be prefixed with its name, -e.g. `pscheduler-tool-iperf3-udp`. - -## Schedules - -Standard schedules are available - - * `PT30S` - * `PT1M` - * `PT5M` - * `PT10M` - * `PT15M` - * `PT30M` - * `PT1H` - * `PT4H` - * `PT6H` - * `PT12H` - * `P1D` -A plugin may define its own schedules +## Development Notes +Everything in this directory is designed to work on a minimal system +with no perfSONAR components intstalled. Ergo, it is preferable to +write a Python script to process the data rather than using jq. diff --git a/testbed/bin/finish b/testbed/bin/finish new file mode 100755 index 000000000..1de81e29b --- /dev/null +++ b/testbed/bin/finish @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# Build missing things +# + +import json +import sys + +result = json.load(sys.stdin) + +# +# Groups +# + +if 'groups' not in result: + result['groups'] = '' + +# These are structurally-identical +for group_type in [ 'list', 'mesh' ]: + result['groups'][group_type] = { + 'type': group_type, + 'addresses': [ + { 'name': key } for key, _value in result['addresses'].items() + ] + } + + + +# +# Add all archives to all tasks +# + +archive_list = list(result['archives'].keys()) + +for _key, task in result['tasks'].items(): + task['archives'] = archive_list + + + + +print(json.dumps( + result, + sort_keys=True, + indent=4, + separators=(', ', ': ') + )) diff --git a/testbed/bin/merge-json b/testbed/bin/merge-json new file mode 100755 index 000000000..71825daf5 --- /dev/null +++ b/testbed/bin/merge-json @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# +# Merge all JSON dictionaries in files specified on the command line. +# + +import json +import sys + +def dict_merge(dct, merge_dct): + """ + Recursive dict merge. Inspired by dict.update(), instead of + updating only top-level keys, dict_merge recurses down into dicts + nested to an arbitrary depth, updating keys. The merge_dct is + merged into dct. + + Modified from https://gist.github.com/angstwad/bf22d1822c38a92ec0a9 + """ + for k, v in merge_dct.items(): + if (k in dct and isinstance(dct[k], dict) and isinstance(merge_dct[k], dict)): #noqa + dict_merge(dct[k], merge_dct[k]) + else: + dct[k] = merge_dct[k] + +result = {} + +for filename in sys.argv[1:]: + with open(filename, 'r') as infile: + dict_merge(result, json.load(infile)) + +print(json.dumps( + result, + sort_keys=True, + indent=4, + separators=(', ', ': ') + )) diff --git a/testbed/bin/validate-json b/testbed/bin/validate-json new file mode 100755 index 000000000..fdbc3eeaa --- /dev/null +++ b/testbed/bin/validate-json @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# +# Validate the JSON file(s) named on the command line +# + +import json +import sys + +def die(message): + print(message, file=sys.stderr) + sys.exit(1) + + +for test_file in sys.argv[1:]: + try: + with open(test_file, 'r') as input_file: + try: + json.load(input_file) + except json.decoder.JSONDecodeError as ex: + die(f'{test_file}: Invalid JSON: {ex}') + except IOError as ex: + die(f'{test_file}: {ex}') diff --git a/testbed/members/.gitignore b/testbed/members/.gitignore new file mode 100644 index 000000000..e102ff534 --- /dev/null +++ b/testbed/members/.gitignore @@ -0,0 +1 @@ +output.json diff --git a/testbed/members/Makefile b/testbed/members/Makefile new file mode 100644 index 000000000..f66e80071 --- /dev/null +++ b/testbed/members/Makefile @@ -0,0 +1,22 @@ +# +# Makefile for Testbed Members +# + +PRODUCT := output.json + + +default: build + +MEMBERS := members + +BUILDER := ./build-addresses +$(PRODUCT): Makefile $(BUILDER) $(MEMBERS) + $(BUILDER) < '$(MEMBERS)' > "$@" +TO_CLEAN += $(PRODUCT) + + +build: $(PRODUCT) + + +clean: + rm -rf $(TO_CLEAN) *~ diff --git a/testbed/members/build-addresses b/testbed/members/build-addresses new file mode 100755 index 000000000..d3a5e8f7d --- /dev/null +++ b/testbed/members/build-addresses @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# +# Build a pSConfig addresses section from a list of hosts on stdin +# + +import json +import re +import sys + +addresses = {} + +for line in sys.stdin: + # TODO: Remove excess whitespace and blanks + member = re.sub(r'\s*[#].*$', '', line[:-1]) + if not member: + continue + + address, label = re.split(r'\s+', member, maxsplit=1) + + addresses[address] = { + 'address': address, + '_meta': { + 'display-name': label + } + } + +print(json.dumps( + { 'addresses': addresses }, + sort_keys=True, + indent=4, + separators=(', ', ': '))) diff --git a/testbed/members/members b/testbed/members/members new file mode 100644 index 000000000..7b402c173 --- /dev/null +++ b/testbed/members/members @@ -0,0 +1,10 @@ +# +# Members of the test bed +# + +# Format: Hostname Label + +host1.perfsonar.net First Host +host2.personar.net Second Host +host3.perfsonar.net Third Host +host4.perfsonar.net Fourth Host diff --git a/testbed/plugins/Makefile b/testbed/plugins/Makefile index 363017d92..d04275f15 100644 --- a/testbed/plugins/Makefile +++ b/testbed/plugins/Makefile @@ -21,7 +21,7 @@ TO_CLEAN += $(WORK) BUILDER := ./copy-plugin-json $(PRODUCT): Makefile $(BUILDER) $(WORK) $(BUILDER) '$(TOP)' '$(WORK)' - jq -s add $(WORK)/* > $@ + ../bin/merge-json $(WORK)/* > $@ TO_CLEAN += $(PRODUCT) diff --git a/testbed/plugins/copy-plugin-json b/testbed/plugins/copy-plugin-json index 4df011e34..217988d24 100755 --- a/testbed/plugins/copy-plugin-json +++ b/testbed/plugins/copy-plugin-json @@ -7,6 +7,8 @@ TOP="$1" DEST="$2" +WHEREAMI=$(dirname "$0") + ls "${TOP}" \ | egrep -e '^pscheduler-(archiver|context|text|tool)-.*$' \ | ( @@ -15,6 +17,7 @@ ls "${TOP}" \ PLUGIN_FILE="${TOP}/${PLUGIN}/testbed.json" if [ -e "${PLUGIN_FILE}" ] then + "${WHEREAMI}/../bin/validate-json" "${PLUGIN_FILE}" cp "${PLUGIN_FILE}" "${DEST}/${PLUGIN}" fi done diff --git a/testbed/skeleton/output.json b/testbed/skeleton/output.json index ddfcefe67..b6b4f77a4 100644 --- a/testbed/skeleton/output.json +++ b/testbed/skeleton/output.json @@ -1,5 +1,11 @@ { "_meta": { + "NOTE": [ + "", + "This file was automatically-generated and should not", + "be edited manually.", + "" + ], "administrators": [ { "email": "perfsonar-developer@internet2.edu",