Skip to content

Commit

Permalink
Support csv-like fields passed to cli args as JSON (#1437)
Browse files Browse the repository at this point in the history
Extend cli arguments accepting csv-like fields to also support JSON
(via a filename, ending in .json, or as a JSON inline
string).

Also fully support inline JSON in cli arguments accepting kv-dicts
(currently we only treat strings starting with `{` as inline JSON, thus missing
arrays).
  • Loading branch information
dliappis authored Feb 21, 2022
1 parent 6355a09 commit 214e027
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 3 deletions.
21 changes: 21 additions & 0 deletions docs/command_line_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -975,3 +975,24 @@ Examples, assuming that two clusters have been specified with ``--target-hosts``

.. WARNING::
If you use ``client-options`` you must specify options for **every** cluster name defined with ``target-hosts``. Rally will raise an error if there is a mismatch.

Command line parameters accepting comma-separated values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Parameters that can accept comma-separated values such as ``--car``, ``--telemetry``, ``--include-tasks`` etc. can also accept a JSON array.
This can either be defined in a file ending in ``.json`` or passed as an inline JSON string.

Examples:

* comma-separated values::

--car="4gheap,trial-license"

* json file: ``--car="car.json"``::

$ cat car.json
["4gheap", "trial-license"]

* json inline string::

esrally race ... --telemetry='["node-stats", "recovery-stats"]'
5 changes: 5 additions & 0 deletions docs/migrate.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Migration Guide
===============

Migrating to Rally 2.4.0
------------------------

Rally now accepts JSON files (ending in .json) or inline JSON strings for cli arguments that accept comma separated values like `--car`, `--telemetry` etc. The only allowed JSON is a plain array e.g. ``esrally race ... --car='["4g", "trial-license"]'``.

Migrating to Rally 2.3.0
------------------------

Expand Down
16 changes: 14 additions & 2 deletions esrally/utils/opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,25 @@

import difflib
import json
import re

from esrally.utils import io

# detect (very simplistically) json that starts with an empty array or array of strings
RE_JSON_ARRAY_START = re.compile(r'^(\s*\[\s*\])|(\s*\[\s*".*)')


def csv_to_list(csv):
if csv is None:
return None
if io.has_extension(csv, ".json"):
with open(io.normalize_path(csv), mode="rt", encoding="utf-8") as f:
content = f.read()
if not RE_JSON_ARRAY_START.match(content):
raise ValueError(f"csv args only support arrays in json but you supplied [{csv}]")
return json.loads(content)
elif RE_JSON_ARRAY_START.match(csv):
return json.loads(csv)
elif len(csv.strip()) == 0:
return []
else:
Expand Down Expand Up @@ -79,9 +91,9 @@ def to_dict(arg, default_parser=kv_to_map):
if io.has_extension(arg, ".json"):
with open(io.normalize_path(arg), mode="rt", encoding="utf-8") as f:
return json.load(f)
elif arg.startswith("{"):
try:
return json.loads(arg)
else:
except json.decoder.JSONDecodeError:
return default_parser(csv_to_list(arg))


Expand Down
38 changes: 37 additions & 1 deletion tests/utils/opts_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,40 @@
# under the License.

import os
from unittest import mock

import pytest

from esrally.utils import opts


class TestConfigHelperFunction:
def test_csv_to_list(self):
def test_csv_to_list_inline(self):
assert opts.csv_to_list("") == []
assert opts.csv_to_list(" a,b,c , d") == ["a", "b", "c", "d"]
assert opts.csv_to_list(" a-;d ,b,c , d") == ["a-;d", "b", "c", "d"]
assert opts.csv_to_list("node-stats,recovery-stats") == ["node-stats", "recovery-stats"]
# stringified inline json tests
assert opts.csv_to_list('["node-stats", "recovery-stats"]') == ["node-stats", "recovery-stats"]
assert opts.csv_to_list(' [ "node-stats", "recovery-stats"]') == ["node-stats", "recovery-stats"]
assert opts.csv_to_list(' [ "node-stats", "recovery-stats"]') == ["node-stats", "recovery-stats"]
assert opts.csv_to_list("[]") == []
assert opts.csv_to_list(" [ ] ") == []

def test_csv_to_line_from_json_file(self):
# used to mock contents of "./telemetry_params.json"
config_data = mock.mock_open(read_data='["v1", "v2"]')

with mock.patch("esrally.utils.opts.open", config_data):
assert opts.csv_to_list("telemetry_params.json") == ["v1", "v2"]

def test_csv_to_line_raises_with_non_acceptable_json_file(self):
# used to mock contents of "./telemetry_params.json"
config_data = mock.mock_open(read_data="{}")

with mock.patch("esrally.utils.opts.open", config_data):
with pytest.raises(ValueError, match=r"csv args only support arrays in json but you supplied"):
opts.csv_to_list("telemetry_params.json")

def test_kv_to_map(self):
assert opts.kv_to_map([]) == {}
Expand All @@ -40,6 +65,17 @@ def test_kv_to_map(self):
"temperature": 0.5,
}

def test_to_dict_with_inline_json(self):
assert opts.to_dict('{"default": ["127.0.0.1:9200","10.17.0.5:19200"]}') == {"default": ["127.0.0.1:9200", "10.17.0.5:19200"]}

# valid json may also start with an array
assert opts.to_dict('["node-stats", "recovery-stats"]') == ["node-stats", "recovery-stats"]

assert opts.to_dict("[]") == []

def test_to_dict_with_inline_kv(self):
assert opts.to_dict("k1:v1,k2:v2") == {"k1": "v1", "k2": "v2"}


class TestGenericHelperFunction:
def test_list_as_bulleted_list(self):
Expand Down

0 comments on commit 214e027

Please sign in to comment.