<p>pespin <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/18023">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Jenkins Builder: Verified
  pespin: Looks good to me, approved

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Generate schemas dynamically from pieces provided by each object class<br><br>This way we benefit from:<br>* knowing which attributes are used/required by each object class and<br>  subclass<br>* Having validation function definitions near the class going to use them<br><br>Change-Id: I8fd6773c51d19405a585977af4ed72cad2b21db1<br>---<br>M selftest/config_test.py<br>M selftest/resource_test.py<br>M selftest/suite_test.py<br>M src/osmo-gsm-tester.py<br>M src/osmo_gsm_tester/core/config.py<br>M src/osmo_gsm_tester/core/schema.py<br>M src/osmo_gsm_tester/core/util.py<br>M src/osmo_gsm_tester/obj/bsc_osmo.py<br>M src/osmo_gsm_tester/obj/bts.py<br>M src/osmo_gsm_tester/obj/bts_osmo.py<br>M src/osmo_gsm_tester/obj/bts_osmotrx.py<br>M src/osmo_gsm_tester/obj/enb.py<br>M src/osmo_gsm_tester/obj/enb_amarisoft.py<br>M src/osmo_gsm_tester/obj/enb_srs.py<br>M src/osmo_gsm_tester/obj/epc.py<br>M src/osmo_gsm_tester/obj/epc_amarisoft.py<br>M src/osmo_gsm_tester/obj/epc_srs.py<br>M src/osmo_gsm_tester/obj/iperf3.py<br>M src/osmo_gsm_tester/obj/ms.py<br>M src/osmo_gsm_tester/obj/ms_amarisoft.py<br>M src/osmo_gsm_tester/obj/ms_srs.py<br>M src/osmo_gsm_tester/obj/msc_osmo.py<br>M src/osmo_gsm_tester/obj/osmocon.py<br>M src/osmo_gsm_tester/obj/run_node.py<br>M src/osmo_gsm_tester/resource.py<br>M src/osmo_gsm_tester/suite.py<br>26 files changed, 418 insertions(+), 208 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/selftest/config_test.py b/selftest/config_test.py</span><br><span>index 83a8d06..c26ebd1 100755</span><br><span>--- a/selftest/config_test.py</span><br><span>+++ b/selftest/config_test.py</span><br><span>@@ -116,35 +116,35 @@</span><br><span> a = {'times': '2'}</span><br><span> b = {'type': 'osmo-bts-trx'}</span><br><span> res = {'times': '2', 'type': 'osmo-bts-trx'}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine dicts 2:')</span><br><span> a = {'times': '1', 'label': 'foo', 'type': 'osmo-bts-trx'}</span><br><span> b = {'type': 'osmo-bts-trx'}</span><br><span> res = {'times': '1', 'label': 'foo', 'type': 'osmo-bts-trx'}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine lists:')</span><br><span> a = { 'a_list': ['x', 'y', 'z'] }</span><br><span> b = { 'a_list': ['y'] }</span><br><span> res = {'a_list': ['x', 'y', 'z']}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine lists 2:')</span><br><span> a = { 'a_list': ['x'] }</span><br><span> b = { 'a_list': ['w', 'u', 'x', 'y', 'z'] }</span><br><span> res = {'a_list': ['x', 'w', 'u', 'y', 'z']}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine lists 3:')</span><br><span> a = { 'a_list': ['x', 3] }</span><br><span> b = { 'a_list': ['y', 'z'] }</span><br><span> try:</span><br><span style="color: hsl(0, 100%, 40%);">-    config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.combine(a, b)</span><br><span> except ValueError:</span><br><span>     print("ValueError expected")</span><br><span> </span><br><span>@@ -152,7 +152,7 @@</span><br><span> a = { 'a_list': [2, 3] }</span><br><span> b = { 'a_list': ['y', 'z'] }</span><br><span> try:</span><br><span style="color: hsl(0, 100%, 40%);">-    config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.combine(a, b)</span><br><span> except ValueError:</span><br><span>     print("ValueError expected")</span><br><span> </span><br><span>@@ -160,7 +160,7 @@</span><br><span> a = { 'a_list': [{}, {}] }</span><br><span> b = { 'a_list': ['y', 'z'] }</span><br><span> try:</span><br><span style="color: hsl(0, 100%, 40%);">-    config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.combine(a, b)</span><br><span> except ValueError:</span><br><span>     print("ValueError expected")</span><br><span> </span><br><span>@@ -168,49 +168,49 @@</span><br><span> a = { 'a_list': [{}, {}] }</span><br><span> b = { 'a_list': [{}] }</span><br><span> res = {'a_list': [{}, {}]}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine lists 7:')</span><br><span> a = { 'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }</span><br><span> b = { 'type': 'osmo-bts-trx', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }</span><br><span> res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine lists 8:')</span><br><span> a = { 'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}] }</span><br><span> b = { 'type': 'osmo-bts-trx', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }</span><br><span> res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine lists 9:')</span><br><span> a = { 'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }</span><br><span> b = { 'type': 'osmo-bts-trx', 'trx': [{'nominal power': '10'}] }</span><br><span> res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine lists 10:')</span><br><span> a = { 'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }</span><br><span> b = { 'type': 'osmo-bts-trx', 'trx': [{}, {'nominal power': '12'}] }</span><br><span> res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine lists 13:')</span><br><span> a = { 'times': '1', 'label': 'foo', 'trx': [{}, {'nominal power': '12'}] }</span><br><span> b = { 'type': 'osmo-bts-trx', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}] }</span><br><span> res = {'times': '1', 'label': 'foo', 'trx': [{'nominal power': '10'}, {'nominal power': '12'}], 'type': 'osmo-bts-trx'}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> print('- Combine lists 14:')</span><br><span> a = { 'times': '1', 'label': 'foo', 'trx': [] }</span><br><span> b = { 'type': 'osmo-bts-trx', 'trx': [] }</span><br><span> res = {'times': '1', 'label': 'foo', 'trx': [], 'type': 'osmo-bts-trx'}</span><br><span style="color: hsl(0, 100%, 40%);">-config.combine(a, b)</span><br><span style="color: hsl(120, 100%, 40%);">+schema.combine(a, b)</span><br><span> assert a == res</span><br><span> </span><br><span> # vim: expandtab tabstop=4 shiftwidth=4</span><br><span>diff --git a/selftest/resource_test.py b/selftest/resource_test.py</span><br><span>index f399e20..ecbeb24 100755</span><br><span>--- a/selftest/resource_test.py</span><br><span>+++ b/selftest/resource_test.py</span><br><span>@@ -7,6 +7,7 @@</span><br><span> import atexit</span><br><span> import _prep</span><br><span> from osmo_gsm_tester.core import config, log, util</span><br><span style="color: hsl(120, 100%, 40%);">+from osmo_gsm_tester.core.schema import generate_schemas</span><br><span> from osmo_gsm_tester import resource</span><br><span> </span><br><span> workdir = util.get_tempdir()</span><br><span>@@ -16,6 +17,9 @@</span><br><span> </span><br><span> log.get_process_id = lambda: '123-1490837279'</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+# Generate supported schemas dynamically from objects:</span><br><span style="color: hsl(120, 100%, 40%);">+generate_schemas()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> print('- expect solutions:')</span><br><span> pprint.pprint(</span><br><span>     resource.solve([ [0, 1, 2],</span><br><span>diff --git a/selftest/suite_test.py b/selftest/suite_test.py</span><br><span>index c4dd5bf..1fb95ec 100755</span><br><span>--- a/selftest/suite_test.py</span><br><span>+++ b/selftest/suite_test.py</span><br><span>@@ -3,6 +3,7 @@</span><br><span> import _prep</span><br><span> import shutil</span><br><span> from osmo_gsm_tester.core import log, config, util</span><br><span style="color: hsl(120, 100%, 40%);">+from osmo_gsm_tester.core.schema import generate_schemas</span><br><span> from osmo_gsm_tester import suite, report</span><br><span> </span><br><span> config.ENV_CONF = './suite_test'</span><br><span>@@ -24,6 +25,9 @@</span><br><span> </span><br><span> #log.style_change(trace=True)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+# Generate supported schemas dynamically from objects:</span><br><span style="color: hsl(120, 100%, 40%);">+generate_schemas()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> print('- non-existing suite dir')</span><br><span> assert(log.run_logging_exceptions(suite.load, 'does_not_exist') == None)</span><br><span> </span><br><span>diff --git a/src/osmo-gsm-tester.py b/src/osmo-gsm-tester.py</span><br><span>index df87957..af66b32 100755</span><br><span>--- a/src/osmo-gsm-tester.py</span><br><span>+++ b/src/osmo-gsm-tester.py</span><br><span>@@ -70,7 +70,9 @@</span><br><span> import argparse</span><br><span> from signal import *</span><br><span> from osmo_gsm_tester import __version__</span><br><span style="color: hsl(0, 100%, 40%);">-from osmo_gsm_tester.core import log, config</span><br><span style="color: hsl(120, 100%, 40%);">+from osmo_gsm_tester.core import log</span><br><span style="color: hsl(120, 100%, 40%);">+from osmo_gsm_tester.core.config import read_config_file, DEFAULT_SUITES_CONF</span><br><span style="color: hsl(120, 100%, 40%);">+from osmo_gsm_tester.core.schema import generate_schemas</span><br><span> from osmo_gsm_tester import trial, suite</span><br><span> </span><br><span> def sig_handler_cleanup(signum, frame):</span><br><span>@@ -162,6 +164,9 @@</span><br><span>     if not combination_strs:</span><br><span>         raise RuntimeError('Need at least one suite:scenario or series to run')</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    # Generate supported schemas dynamically from objects:</span><br><span style="color: hsl(120, 100%, 40%);">+    generate_schemas()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>     # make sure all suite:scenarios exist</span><br><span>     suite_scenarios = []</span><br><span>     for combination_str in combination_strs:</span><br><span>diff --git a/src/osmo_gsm_tester/core/config.py b/src/osmo_gsm_tester/core/config.py</span><br><span>index 9333601..6730807 100644</span><br><span>--- a/src/osmo_gsm_tester/core/config.py</span><br><span>+++ b/src/osmo_gsm_tester/core/config.py</span><br><span>@@ -54,7 +54,8 @@</span><br><span> import os</span><br><span> import copy</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-from . import log, schema, util, template</span><br><span style="color: hsl(120, 100%, 40%);">+from . import log, util, template</span><br><span style="color: hsl(120, 100%, 40%);">+from . import schema</span><br><span> from .util import is_dict, is_list, Dir, get_tempdir</span><br><span> </span><br><span> ENV_PREFIX = 'OSMO_GSM_TESTER_'</span><br><span>@@ -288,68 +289,6 @@</span><br><span>     sc.read_from_file(validation_schema)</span><br><span>     return sc</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-def add(dest, src):</span><br><span style="color: hsl(0, 100%, 40%);">-    if is_dict(dest):</span><br><span style="color: hsl(0, 100%, 40%);">-        if not is_dict(src):</span><br><span style="color: hsl(0, 100%, 40%);">-            raise ValueError('cannot add to dict a value of type: %r' % type(src))</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-        for key, val in src.items():</span><br><span style="color: hsl(0, 100%, 40%);">-            dest_val = dest.get(key)</span><br><span style="color: hsl(0, 100%, 40%);">-            if dest_val is None:</span><br><span style="color: hsl(0, 100%, 40%);">-                dest[key] = val</span><br><span style="color: hsl(0, 100%, 40%);">-            else:</span><br><span style="color: hsl(0, 100%, 40%);">-                log.ctx(key=key)</span><br><span style="color: hsl(0, 100%, 40%);">-                add(dest_val, val)</span><br><span style="color: hsl(0, 100%, 40%);">-        return</span><br><span style="color: hsl(0, 100%, 40%);">-    if is_list(dest):</span><br><span style="color: hsl(0, 100%, 40%);">-        if not is_list(src):</span><br><span style="color: hsl(0, 100%, 40%);">-            raise ValueError('cannot add to list a value of type: %r' % type(src))</span><br><span style="color: hsl(0, 100%, 40%);">-        dest.extend(src)</span><br><span style="color: hsl(0, 100%, 40%);">-        return</span><br><span style="color: hsl(0, 100%, 40%);">-    if dest == src:</span><br><span style="color: hsl(0, 100%, 40%);">-        return</span><br><span style="color: hsl(0, 100%, 40%);">-    raise ValueError('cannot add dicts, conflicting items (values %r and %r)'</span><br><span style="color: hsl(0, 100%, 40%);">-                     % (dest, src))</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-def combine(dest, src):</span><br><span style="color: hsl(0, 100%, 40%);">-    if is_dict(dest):</span><br><span style="color: hsl(0, 100%, 40%);">-        if not is_dict(src):</span><br><span style="color: hsl(0, 100%, 40%);">-            raise ValueError('cannot combine dict with a value of type: %r' % type(src))</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-        for key, val in src.items():</span><br><span style="color: hsl(0, 100%, 40%);">-            log.ctx(key=key)</span><br><span style="color: hsl(0, 100%, 40%);">-            dest_val = dest.get(key)</span><br><span style="color: hsl(0, 100%, 40%);">-            if dest_val is None:</span><br><span style="color: hsl(0, 100%, 40%);">-                dest[key] = val</span><br><span style="color: hsl(0, 100%, 40%);">-            else:</span><br><span style="color: hsl(0, 100%, 40%);">-                combine(dest_val, val)</span><br><span style="color: hsl(0, 100%, 40%);">-        return</span><br><span style="color: hsl(0, 100%, 40%);">-    if is_list(dest):</span><br><span style="color: hsl(0, 100%, 40%);">-        if not is_list(src):</span><br><span style="color: hsl(0, 100%, 40%);">-            raise ValueError('cannot combine list with a value of type: %r' % type(src))</span><br><span style="color: hsl(0, 100%, 40%);">-        # Validate that all elements in both lists are of the same type:</span><br><span style="color: hsl(0, 100%, 40%);">-        t = util.list_validate_same_elem_type(src + dest)</span><br><span style="color: hsl(0, 100%, 40%);">-        if t is None:</span><br><span style="color: hsl(0, 100%, 40%);">-            return # both lists are empty, return</span><br><span style="color: hsl(0, 100%, 40%);">-        # For lists of complex objects, we expect them to be sorted lists:</span><br><span style="color: hsl(0, 100%, 40%);">-        if t in (dict, list, tuple):</span><br><span style="color: hsl(0, 100%, 40%);">-            for i in range(len(dest)):</span><br><span style="color: hsl(0, 100%, 40%);">-                log.ctx(idx=i)</span><br><span style="color: hsl(0, 100%, 40%);">-                src_it = src[i] if i < len(src) else util.empty_instance_type(t)</span><br><span style="color: hsl(0, 100%, 40%);">-                combine(dest[i], src_it)</span><br><span style="color: hsl(0, 100%, 40%);">-            for i in range(len(dest), len(src)):</span><br><span style="color: hsl(0, 100%, 40%);">-                log.ctx(idx=i)</span><br><span style="color: hsl(0, 100%, 40%);">-                dest.append(src[i])</span><br><span style="color: hsl(0, 100%, 40%);">-        else: # for lists of basic elements, we handle them as unsorted sets:</span><br><span style="color: hsl(0, 100%, 40%);">-            for elem in src:</span><br><span style="color: hsl(0, 100%, 40%);">-                if elem not in dest:</span><br><span style="color: hsl(0, 100%, 40%);">-                    dest.append(elem)</span><br><span style="color: hsl(0, 100%, 40%);">-        return</span><br><span style="color: hsl(0, 100%, 40%);">-    if dest == src:</span><br><span style="color: hsl(0, 100%, 40%);">-        return</span><br><span style="color: hsl(0, 100%, 40%);">-    raise ValueError('cannot combine dicts, conflicting items (values %r and %r)'</span><br><span style="color: hsl(0, 100%, 40%);">-                     % (dest, src))</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span> def overlay(dest, src):</span><br><span>     if is_dict(dest):</span><br><span>         if not is_dict(src):</span><br><span>diff --git a/src/osmo_gsm_tester/core/schema.py b/src/osmo_gsm_tester/core/schema.py</span><br><span>index d343bef..588c432 100644</span><br><span>--- a/src/osmo_gsm_tester/core/schema.py</span><br><span>+++ b/src/osmo_gsm_tester/core/schema.py</span><br><span>@@ -18,9 +18,10 @@</span><br><span> # along with this program.  If not, see <http://www.gnu.org/licenses/>.</span><br><span> </span><br><span> import re</span><br><span style="color: hsl(120, 100%, 40%);">+import os</span><br><span> </span><br><span> from . import log</span><br><span style="color: hsl(0, 100%, 40%);">-from .util import is_dict, is_list, str2bool, ENUM_OSMO_AUTH_ALGO</span><br><span style="color: hsl(120, 100%, 40%);">+from . import util</span><br><span> </span><br><span> KEY_RE = re.compile('[a-zA-Z][a-zA-Z0-9_]*')</span><br><span> IPV4_RE = re.compile('([0-9]{1,3}.){3}[0-9]{1,3}')</span><br><span>@@ -62,7 +63,7 @@</span><br><span>     match_re('MSISDN', MSISDN_RE, val)</span><br><span> </span><br><span> def auth_algo(val):</span><br><span style="color: hsl(0, 100%, 40%);">-    if val not in ENUM_OSMO_AUTH_ALGO:</span><br><span style="color: hsl(120, 100%, 40%);">+    if val not in util.ENUM_OSMO_AUTH_ALGO:</span><br><span>         raise ValueError('Unknown Authentication Algorithm: %r' % val)</span><br><span> </span><br><span> def uint(val):</span><br><span>@@ -162,7 +163,7 @@</span><br><span>         INT: int,</span><br><span>         STR: str,</span><br><span>         UINT: uint,</span><br><span style="color: hsl(0, 100%, 40%);">-        BOOL_STR: str2bool,</span><br><span style="color: hsl(120, 100%, 40%);">+        BOOL_STR: util.str2bool,</span><br><span>         BAND: band,</span><br><span>         IPV4: ipv4,</span><br><span>         HWADDR: hwaddr,</span><br><span>@@ -182,6 +183,87 @@</span><br><span>         DURATION: duration,</span><br><span>     }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def add(dest, src):</span><br><span style="color: hsl(120, 100%, 40%);">+    if util.is_dict(dest):</span><br><span style="color: hsl(120, 100%, 40%);">+        if not util.is_dict(src):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise ValueError('cannot add to dict a value of type: %r' % type(src))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        for key, val in src.items():</span><br><span style="color: hsl(120, 100%, 40%);">+            dest_val = dest.get(key)</span><br><span style="color: hsl(120, 100%, 40%);">+            if dest_val is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                dest[key] = val</span><br><span style="color: hsl(120, 100%, 40%);">+            else:</span><br><span style="color: hsl(120, 100%, 40%);">+                log.ctx(key=key)</span><br><span style="color: hsl(120, 100%, 40%);">+                add(dest_val, val)</span><br><span style="color: hsl(120, 100%, 40%);">+        return</span><br><span style="color: hsl(120, 100%, 40%);">+    if util.is_list(dest):</span><br><span style="color: hsl(120, 100%, 40%);">+        if not util.is_list(src):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise ValueError('cannot add to list a value of type: %r' % type(src))</span><br><span style="color: hsl(120, 100%, 40%);">+        dest.extend(src)</span><br><span style="color: hsl(120, 100%, 40%);">+        return</span><br><span style="color: hsl(120, 100%, 40%);">+    if dest == src:</span><br><span style="color: hsl(120, 100%, 40%);">+        return</span><br><span style="color: hsl(120, 100%, 40%);">+    raise ValueError('cannot add dicts, conflicting items (values %r and %r)'</span><br><span style="color: hsl(120, 100%, 40%);">+                     % (dest, src))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def combine(dest, src):</span><br><span style="color: hsl(120, 100%, 40%);">+    if util.is_dict(dest):</span><br><span style="color: hsl(120, 100%, 40%);">+        if not util.is_dict(src):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise ValueError('cannot combine dict with a value of type: %r' % type(src))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        for key, val in src.items():</span><br><span style="color: hsl(120, 100%, 40%);">+            log.ctx(key=key)</span><br><span style="color: hsl(120, 100%, 40%);">+            dest_val = dest.get(key)</span><br><span style="color: hsl(120, 100%, 40%);">+            if dest_val is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                dest[key] = val</span><br><span style="color: hsl(120, 100%, 40%);">+            else:</span><br><span style="color: hsl(120, 100%, 40%);">+                combine(dest_val, val)</span><br><span style="color: hsl(120, 100%, 40%);">+        return</span><br><span style="color: hsl(120, 100%, 40%);">+    if util.is_list(dest):</span><br><span style="color: hsl(120, 100%, 40%);">+        if not util.is_list(src):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise ValueError('cannot combine list with a value of type: %r' % type(src))</span><br><span style="color: hsl(120, 100%, 40%);">+        # Validate that all elements in both lists are of the same type:</span><br><span style="color: hsl(120, 100%, 40%);">+        t = util.list_validate_same_elem_type(src + dest)</span><br><span style="color: hsl(120, 100%, 40%);">+        if t is None:</span><br><span style="color: hsl(120, 100%, 40%);">+            return # both lists are empty, return</span><br><span style="color: hsl(120, 100%, 40%);">+        # For lists of complex objects, we expect them to be sorted lists:</span><br><span style="color: hsl(120, 100%, 40%);">+        if t in (dict, list, tuple):</span><br><span style="color: hsl(120, 100%, 40%);">+            for i in range(len(dest)):</span><br><span style="color: hsl(120, 100%, 40%);">+                log.ctx(idx=i)</span><br><span style="color: hsl(120, 100%, 40%);">+                src_it = src[i] if i < len(src) else util.empty_instance_type(t)</span><br><span style="color: hsl(120, 100%, 40%);">+                combine(dest[i], src_it)</span><br><span style="color: hsl(120, 100%, 40%);">+            for i in range(len(dest), len(src)):</span><br><span style="color: hsl(120, 100%, 40%);">+                log.ctx(idx=i)</span><br><span style="color: hsl(120, 100%, 40%);">+                dest.append(src[i])</span><br><span style="color: hsl(120, 100%, 40%);">+        else: # for lists of basic elements, we handle them as unsorted sets:</span><br><span style="color: hsl(120, 100%, 40%);">+            for elem in src:</span><br><span style="color: hsl(120, 100%, 40%);">+                if elem not in dest:</span><br><span style="color: hsl(120, 100%, 40%);">+                    dest.append(elem)</span><br><span style="color: hsl(120, 100%, 40%);">+        return</span><br><span style="color: hsl(120, 100%, 40%);">+    if dest == src:</span><br><span style="color: hsl(120, 100%, 40%);">+        return</span><br><span style="color: hsl(120, 100%, 40%);">+    raise ValueError('cannot combine dicts, conflicting items (values %r and %r)'</span><br><span style="color: hsl(120, 100%, 40%);">+                     % (dest, src))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def replicate_times(d):</span><br><span style="color: hsl(120, 100%, 40%);">+    '''</span><br><span style="color: hsl(120, 100%, 40%);">+    replicate items that have a "times" > 1</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    'd' is a dict matching WANT_SCHEMA, which is the same as</span><br><span style="color: hsl(120, 100%, 40%);">+    the RESOURCES_SCHEMA, except each entity that can be reserved has a 'times'</span><br><span style="color: hsl(120, 100%, 40%);">+    field added, to indicate how many of those should be reserved.</span><br><span style="color: hsl(120, 100%, 40%);">+    '''</span><br><span style="color: hsl(120, 100%, 40%);">+    d = copy.deepcopy(d)</span><br><span style="color: hsl(120, 100%, 40%);">+    for key, item_list in d.items():</span><br><span style="color: hsl(120, 100%, 40%);">+        idx = 0</span><br><span style="color: hsl(120, 100%, 40%);">+        while idx < len(item_list):</span><br><span style="color: hsl(120, 100%, 40%);">+            item = item_list[idx]</span><br><span style="color: hsl(120, 100%, 40%);">+            times = int(item.pop('times', 1))</span><br><span style="color: hsl(120, 100%, 40%);">+            for j in range(1, times):</span><br><span style="color: hsl(120, 100%, 40%);">+                item_list.insert(idx + j, copy.deepcopy(item))</span><br><span style="color: hsl(120, 100%, 40%);">+            idx += times</span><br><span style="color: hsl(120, 100%, 40%);">+    return d</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> def validate(config, schema):</span><br><span>     '''Make sure the given config dict adheres to the schema.</span><br><span>        The schema is a dict of 'dict paths' in dot-notation with permitted</span><br><span>@@ -198,17 +280,17 @@</span><br><span>     def validate_item(path, value, schema):</span><br><span>         want_type = schema.get(path)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        if is_list(value):</span><br><span style="color: hsl(120, 100%, 40%);">+        if util.is_list(value):</span><br><span>             if want_type:</span><br><span>                 raise ValueError('config item is a list, should be %r: %r' % (want_type, path))</span><br><span>             path = path + '[]'</span><br><span>             want_type = schema.get(path)</span><br><span> </span><br><span>         if not want_type:</span><br><span style="color: hsl(0, 100%, 40%);">-            if is_dict(value):</span><br><span style="color: hsl(120, 100%, 40%);">+            if util.is_dict(value):</span><br><span>                 nest(path, value, schema)</span><br><span>                 return</span><br><span style="color: hsl(0, 100%, 40%);">-            if is_list(value) and value:</span><br><span style="color: hsl(120, 100%, 40%);">+            if util.is_list(value) and value:</span><br><span>                 for list_v in value:</span><br><span>                     validate_item(path, list_v, schema)</span><br><span>                 return</span><br><span>@@ -217,11 +299,11 @@</span><br><span>         if want_type not in SCHEMA_TYPES:</span><br><span>             raise ValueError('unknown type %r at %r' % (want_type, path))</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        if is_dict(value):</span><br><span style="color: hsl(120, 100%, 40%);">+        if util.is_dict(value):</span><br><span>             raise ValueError('config item is dict but should be a leaf node of type %r: %r'</span><br><span>                              % (want_type, path))</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        if is_list(value):</span><br><span style="color: hsl(120, 100%, 40%);">+        if util.is_list(value):</span><br><span>             for list_v in value:</span><br><span>                 validate_item(path, list_v, schema)</span><br><span>             return</span><br><span>@@ -243,4 +325,73 @@</span><br><span> </span><br><span>     nest(None, config, schema)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def generate_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    "Generate supported schemas dynamically from objects"</span><br><span style="color: hsl(120, 100%, 40%);">+    obj_dir = '%s/../obj/' % os.path.dirname(os.path.abspath(__file__))</span><br><span style="color: hsl(120, 100%, 40%);">+    for filename in os.listdir(obj_dir):</span><br><span style="color: hsl(120, 100%, 40%);">+        if not filename.endswith(".py"):</span><br><span style="color: hsl(120, 100%, 40%);">+            continue</span><br><span style="color: hsl(120, 100%, 40%);">+        module_name = 'osmo_gsm_tester.obj.%s' % filename[:-3]</span><br><span style="color: hsl(120, 100%, 40%);">+        util.run_python_file_method(module_name, 'on_register_schemas', False)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+_RESOURCE_TYPES = ['ip_address', 'arfcn']</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+_RESOURCES_SCHEMA = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'ip_address[].addr': IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'arfcn[].arfcn': INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'arfcn[].band': BAND,</span><br><span style="color: hsl(120, 100%, 40%);">+    }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+_CONFIG_SCHEMA = {}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+_WANT_SCHEMA = None</span><br><span style="color: hsl(120, 100%, 40%);">+_ALL_SCHEMA = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def register_resource_schema(obj_class_str, obj_attr_dict):</span><br><span style="color: hsl(120, 100%, 40%);">+    """Register schema attributes for a resource type.</span><br><span style="color: hsl(120, 100%, 40%);">+       For instance: register_resource_schema_attributes('modem', {'type': schema.STR, 'ki': schema.KI})</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+    global _RESOURCES_SCHEMA</span><br><span style="color: hsl(120, 100%, 40%);">+    global _RESOURCE_TYPES</span><br><span style="color: hsl(120, 100%, 40%);">+    tmpdict = {}</span><br><span style="color: hsl(120, 100%, 40%);">+    for key, val in obj_attr_dict.items():</span><br><span style="color: hsl(120, 100%, 40%);">+        new_key = '%s[].%s' % (obj_class_str, key)</span><br><span style="color: hsl(120, 100%, 40%);">+        tmpdict[new_key] = val</span><br><span style="color: hsl(120, 100%, 40%);">+    combine(_RESOURCES_SCHEMA, tmpdict)</span><br><span style="color: hsl(120, 100%, 40%);">+    if obj_class_str not in _RESOURCE_TYPES:</span><br><span style="color: hsl(120, 100%, 40%);">+        _RESOURCE_TYPES.append(obj_class_str)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def register_config_schema(obj_class_str, obj_attr_dict):</span><br><span style="color: hsl(120, 100%, 40%);">+    """Register schema attributes to configure all instances of an object class.</span><br><span style="color: hsl(120, 100%, 40%);">+       For instance: register_resource_schema_attributes('bsc', {'net.codec_list[]': schema.CODEC})</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+    global _CONFIG_SCHEMA</span><br><span style="color: hsl(120, 100%, 40%);">+    tmpdict = {}</span><br><span style="color: hsl(120, 100%, 40%);">+    for key, val in obj_attr_dict.items():</span><br><span style="color: hsl(120, 100%, 40%);">+        new_key = '%s.%s' % (obj_class_str, key)</span><br><span style="color: hsl(120, 100%, 40%);">+        tmpdict[new_key] = val</span><br><span style="color: hsl(120, 100%, 40%);">+    combine(_CONFIG_SCHEMA, tmpdict)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def get_resources_schema():</span><br><span style="color: hsl(120, 100%, 40%);">+    return _RESOURCES_SCHEMA;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def get_want_schema():</span><br><span style="color: hsl(120, 100%, 40%);">+    global _WANT_SCHEMA</span><br><span style="color: hsl(120, 100%, 40%);">+    if _WANT_SCHEMA is None:</span><br><span style="color: hsl(120, 100%, 40%);">+        _WANT_SCHEMA = util.dict_add(</span><br><span style="color: hsl(120, 100%, 40%);">+            dict([('%s[].times' % r, TIMES) for r in _RESOURCE_TYPES]),</span><br><span style="color: hsl(120, 100%, 40%);">+            get_resources_schema())</span><br><span style="color: hsl(120, 100%, 40%);">+    return _WANT_SCHEMA</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def get_all_schema():</span><br><span style="color: hsl(120, 100%, 40%);">+    global _ALL_SCHEMA</span><br><span style="color: hsl(120, 100%, 40%);">+    if _ALL_SCHEMA is None:</span><br><span style="color: hsl(120, 100%, 40%);">+        want_schema = get_want_schema()</span><br><span style="color: hsl(120, 100%, 40%);">+        _ALL_SCHEMA = util.dict_add({ 'defaults.timeout': STR },</span><br><span style="color: hsl(120, 100%, 40%);">+                        dict([('config.%s' % key, val) for key, val in _CONFIG_SCHEMA.items()]),</span><br><span style="color: hsl(120, 100%, 40%);">+                        dict([('resources.%s' % key, val) for key, val in want_schema.items()]),</span><br><span style="color: hsl(120, 100%, 40%);">+                        dict([('modifiers.%s' % key, val) for key, val in want_schema.items()]))</span><br><span style="color: hsl(120, 100%, 40%);">+    return _ALL_SCHEMA</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> # vim: expandtab tabstop=4 shiftwidth=4</span><br><span>diff --git a/src/osmo_gsm_tester/core/util.py b/src/osmo_gsm_tester/core/util.py</span><br><span>index a5b2bbf..4c7b1dd 100644</span><br><span>--- a/src/osmo_gsm_tester/core/util.py</span><br><span>+++ b/src/osmo_gsm_tester/core/util.py</span><br><span>@@ -370,6 +370,17 @@</span><br><span>     def run_python_file(module_name, path):</span><br><span>         SourceFileLoader(module_name, path).load_module()</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def run_python_file_method(module_name, func_name, fail_if_missing=True):</span><br><span style="color: hsl(120, 100%, 40%);">+    module_obj = __import__(module_name, globals(), locals(), [func_name])</span><br><span style="color: hsl(120, 100%, 40%);">+    try:</span><br><span style="color: hsl(120, 100%, 40%);">+        func = getattr(module_obj, func_name)</span><br><span style="color: hsl(120, 100%, 40%);">+    except AttributeError as e:</span><br><span style="color: hsl(120, 100%, 40%);">+        if fail_if_missing:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise RuntimeError('function %s not found in %s (%s)' % (func_name, module_name))</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            return None</span><br><span style="color: hsl(120, 100%, 40%);">+    return func()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> def msisdn_inc(msisdn_str):</span><br><span>     'add 1 and preserve leading zeros'</span><br><span>     return ('%%0%dd' % len(msisdn_str)) % (int(msisdn_str) + 1)</span><br><span>diff --git a/src/osmo_gsm_tester/obj/bsc_osmo.py b/src/osmo_gsm_tester/obj/bsc_osmo.py</span><br><span>index 25cc780..046ef94 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/bsc_osmo.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/bsc_osmo.py</span><br><span>@@ -22,8 +22,16 @@</span><br><span> import pprint</span><br><span> </span><br><span> from ..core import log, util, config, template, process</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from . import osmo_ctrl, pcap_recorder</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'net.codec_list[]': schema.CODEC,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('bsc', config_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class OsmoBsc(log.Origin):</span><br><span> </span><br><span>     def __init__(self, suite_run, msc, mgw, stp, ip_address):</span><br><span>diff --git a/src/osmo_gsm_tester/obj/bts.py b/src/osmo_gsm_tester/obj/bts.py</span><br><span>index 515b42b..8b05ea0 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/bts.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/bts.py</span><br><span>@@ -21,6 +21,30 @@</span><br><span> from abc import ABCMeta, abstractmethod</span><br><span> from ..core import log, config, schema</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'label': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'band': schema.BAND,</span><br><span style="color: hsl(120, 100%, 40%);">+        'direct_pcu': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'ciphers[]': schema.CIPHER,</span><br><span style="color: hsl(120, 100%, 40%);">+        'channel_allocator': schema.CHAN_ALLOCATOR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'gprs_mode': schema.GPRS_MODE,</span><br><span style="color: hsl(120, 100%, 40%);">+        'num_trx': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'max_trx': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'trx_list[].addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'trx_list[].hw_addr': schema.HWADDR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'trx_list[].net_device': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'trx_list[].nominal_power': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'trx_list[].max_power_red': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'trx_list[].timeslot_list[].phys_chan_config': schema.PHY_CHAN,</span><br><span style="color: hsl(120, 100%, 40%);">+        'trx_list[].power_supply.type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'trx_list[].power_supply.device': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'trx_list[].power_supply.port': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('bts', resource_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class Bts(log.Origin, metaclass=ABCMeta):</span><br><span> </span><br><span> ##############</span><br><span>diff --git a/src/osmo_gsm_tester/obj/bts_osmo.py b/src/osmo_gsm_tester/obj/bts_osmo.py</span><br><span>index 74f3ec7..a182c47 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/bts_osmo.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/bts_osmo.py</span><br><span>@@ -21,8 +21,18 @@</span><br><span> import tempfile</span><br><span> from abc import ABCMeta, abstractmethod</span><br><span> from ..core import log</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from . import bts, pcu_osmo</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'ipa_unit_id': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'direct_pcu': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'channel_allocator': schema.CHAN_ALLOCATOR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'gprs_mode': schema.GPRS_MODE,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('bts', resource_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class OsmoBts(bts.Bts, metaclass=ABCMeta):</span><br><span> </span><br><span> ##############</span><br><span>diff --git a/src/osmo_gsm_tester/obj/bts_osmotrx.py b/src/osmo_gsm_tester/obj/bts_osmotrx.py</span><br><span>index 5339946..9234fc8 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/bts_osmotrx.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/bts_osmotrx.py</span><br><span>@@ -21,9 +21,25 @@</span><br><span> import pprint</span><br><span> from abc import ABCMeta, abstractmethod</span><br><span> from ..core import log, config, util, template, process, remote</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from ..core.event_loop import MainLoop</span><br><span> from . import powersupply, bts_osmo</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.launch_trx': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.clock_reference': schema.OSMO_TRX_CLOCK_REF,</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.trx_ip': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.remote_user': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.dev_args': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.multi_arfcn': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.max_trxd_version': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.channels[].rx_path': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'osmo_trx.channels[].tx_path': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('bts', resource_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class OsmoBtsTrx(bts_osmo.OsmoBtsMainUnit):</span><br><span> ##############</span><br><span> # PROTECTED</span><br><span>diff --git a/src/osmo_gsm_tester/obj/enb.py b/src/osmo_gsm_tester/obj/enb.py</span><br><span>index 3c1f771..c652761 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/enb.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/enb.py</span><br><span>@@ -19,7 +19,47 @@</span><br><span> </span><br><span> from abc import ABCMeta, abstractmethod</span><br><span> from ..core import log, config</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'label': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'remote_user': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'gtp_bind_addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'id': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'num_prb': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'transmission_mode': schema.LTE_TRANSMISSION_MODE,</span><br><span style="color: hsl(120, 100%, 40%);">+        'tx_gain': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'rx_gain': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'rf_dev_type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'rf_dev_args': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'additional_args': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'enable_measurements': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a1_report_type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a1_report_value': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a1_hysteresis': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a1_time_to_trigger': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a2_report_type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a2_report_value': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a2_hysteresis': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a2_time_to_trigger': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a3_report_type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a3_report_value': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a3_hysteresis': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'a3_time_to_trigger': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'num_cells': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'cell_list[].cell_id': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'cell_list[].pci': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'cell_list[].ncell_list[]': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'cell_list[].scell_list[]': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'cell_list[].dl_earfcn': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'cell_list[].dl_rfemu.type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'cell_list[].dl_rfemu.addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'cell_list[].dl_rfemu.ports[]': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('enb', resource_schema)</span><br><span> </span><br><span> class eNodeB(log.Origin, metaclass=ABCMeta):</span><br><span> </span><br><span>diff --git a/src/osmo_gsm_tester/obj/enb_amarisoft.py b/src/osmo_gsm_tester/obj/enb_amarisoft.py</span><br><span>index ec7063c..6f7080a 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/enb_amarisoft.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/enb_amarisoft.py</span><br><span>@@ -21,9 +21,16 @@</span><br><span> import pprint</span><br><span> </span><br><span> from ..core import log, util, config, template, process, remote</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from . import enb</span><br><span> from . import rfemu</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'license_server_addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('amarisoft', config_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> def rf_type_valid(rf_type_str):</span><br><span>     return rf_type_str in ('uhd', 'zmq')</span><br><span> </span><br><span>diff --git a/src/osmo_gsm_tester/obj/enb_srs.py b/src/osmo_gsm_tester/obj/enb_srs.py</span><br><span>index 243ffaa..0816ca6 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/enb_srs.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/enb_srs.py</span><br><span>@@ -24,6 +24,14 @@</span><br><span> from . import enb</span><br><span> from . import rfemu</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'enable_pcap': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('enb', config_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> def rf_type_valid(rf_type_str):</span><br><span>     return rf_type_str in ('zmq', 'uhd', 'soapy', 'bladerf')</span><br><span> </span><br><span>diff --git a/src/osmo_gsm_tester/obj/epc.py b/src/osmo_gsm_tester/obj/epc.py</span><br><span>index f6bddea..c725f76 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/epc.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/epc.py</span><br><span>@@ -19,7 +19,14 @@</span><br><span> </span><br><span> from abc import ABCMeta, abstractmethod</span><br><span> from ..core import log, config</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'qci': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('epc', config_schema)</span><br><span> </span><br><span> class EPC(log.Origin, metaclass=ABCMeta):</span><br><span> </span><br><span>diff --git a/src/osmo_gsm_tester/obj/epc_amarisoft.py b/src/osmo_gsm_tester/obj/epc_amarisoft.py</span><br><span>index afd8aa4..8606e57 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/epc_amarisoft.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/epc_amarisoft.py</span><br><span>@@ -21,8 +21,15 @@</span><br><span> import pprint</span><br><span> </span><br><span> from ..core import log, util, config, template, process, remote</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from . import epc</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'license_server_addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('amarisoft', config_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class AmarisoftEPC(epc.EPC):</span><br><span> </span><br><span>     REMOTE_DIR = '/osmo-gsm-tester-amarisoftepc'</span><br><span>diff --git a/src/osmo_gsm_tester/obj/epc_srs.py b/src/osmo_gsm_tester/obj/epc_srs.py</span><br><span>index ec9dc44..f859df0 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/epc_srs.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/epc_srs.py</span><br><span>@@ -21,8 +21,15 @@</span><br><span> import pprint</span><br><span> </span><br><span> from ..core import log, util, config, template, process, remote</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from . import epc</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'enable_pcap': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('epc', config_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class srsEPC(epc.EPC):</span><br><span> </span><br><span>     REMOTE_DIR = '/osmo-gsm-tester-srsepc'</span><br><span>diff --git a/src/osmo_gsm_tester/obj/iperf3.py b/src/osmo_gsm_tester/obj/iperf3.py</span><br><span>index 9427770..b9fdfe8 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/iperf3.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/iperf3.py</span><br><span>@@ -21,8 +21,15 @@</span><br><span> import json</span><br><span> </span><br><span> from ..core import log, util, config, process, remote</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from . import pcap_recorder, run_node</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'time': schema.DURATION,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('iperf3cli', config_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> def iperf3_result_to_json(log_obj, data):</span><br><span>     try:</span><br><span>         # Drop non-interesting self-generated output before json:</span><br><span>diff --git a/src/osmo_gsm_tester/obj/ms.py b/src/osmo_gsm_tester/obj/ms.py</span><br><span>index 3dcea7b..b72333a 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/ms.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/ms.py</span><br><span>@@ -19,6 +19,21 @@</span><br><span> </span><br><span> from abc import ABCMeta, abstractmethod</span><br><span> from ..core import log</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'label': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'path': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'imsi': schema.IMSI,</span><br><span style="color: hsl(120, 100%, 40%);">+        'ki': schema.KI,</span><br><span style="color: hsl(120, 100%, 40%);">+        'auth_algo': schema.AUTH_ALGO,</span><br><span style="color: hsl(120, 100%, 40%);">+        'apn_ipaddr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'ciphers[]': schema.CIPHER,</span><br><span style="color: hsl(120, 100%, 40%);">+        'features[]': schema.MODEM_FEATURE</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('modem', resource_schema)</span><br><span> </span><br><span> class MS(log.Origin, metaclass=ABCMeta):</span><br><span>     """Base for everything about mobile/modem and SIMs."""</span><br><span>diff --git a/src/osmo_gsm_tester/obj/ms_amarisoft.py b/src/osmo_gsm_tester/obj/ms_amarisoft.py</span><br><span>index 6fd80ee..46a92dc 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/ms_amarisoft.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/ms_amarisoft.py</span><br><span>@@ -21,10 +21,17 @@</span><br><span> import pprint</span><br><span> </span><br><span> from ..core import log, util, config, template, process, remote</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from ..core.event_loop import MainLoop</span><br><span> from .run_node import RunNode</span><br><span> from .ms import MS</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'license_server_addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('amarisoft', config_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> def rf_type_valid(rf_type_str):</span><br><span>     return rf_type_str in ('uhd', 'zmq')</span><br><span> </span><br><span>diff --git a/src/osmo_gsm_tester/obj/ms_srs.py b/src/osmo_gsm_tester/obj/ms_srs.py</span><br><span>index cdc8d18..7d08b66 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/ms_srs.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/ms_srs.py</span><br><span>@@ -21,6 +21,7 @@</span><br><span> import pprint</span><br><span> </span><br><span> from ..core import log, util, config, template, process, remote</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from .run_node import RunNode</span><br><span> from ..core.event_loop import MainLoop</span><br><span> from .ms import MS</span><br><span>@@ -28,6 +29,26 @@</span><br><span> def rf_type_valid(rf_type_str):</span><br><span>     return rf_type_str in ('zmq', 'uhd', 'soapy', 'bladerf')</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'remote_user': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'rf_dev_type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'rf_dev_args': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'num_carriers': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'additional_args': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'airplane_t_on_ms': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'airplane_t_off_ms': schema.INT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'tx_gain': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        'rx_gain': schema.UINT,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('modem', resource_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'enable_pcap': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('modem', config_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> #reference: srsLTE.git srslte_symbol_sz()</span><br><span> def num_prb2symbol_sz(num_prb):</span><br><span>     if num_prb <= 6:</span><br><span>diff --git a/src/osmo_gsm_tester/obj/msc_osmo.py b/src/osmo_gsm_tester/obj/msc_osmo.py</span><br><span>index cb8894f..048934e 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/msc_osmo.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/msc_osmo.py</span><br><span>@@ -21,8 +21,15 @@</span><br><span> import pprint</span><br><span> </span><br><span> from ..core import log, util, config, template, process</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from . import osmo_ctrl, pcap_recorder, smsc</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'path': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('modem', resource_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class OsmoMsc(log.Origin):</span><br><span> </span><br><span>     def __init__(self, suite_run, hlr, mgw, stp, ip_address):</span><br><span>diff --git a/src/osmo_gsm_tester/obj/osmocon.py b/src/osmo_gsm_tester/obj/osmocon.py</span><br><span>index 1fad239..6f6ac2a 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/osmocon.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/osmocon.py</span><br><span>@@ -21,8 +21,16 @@</span><br><span> import tempfile</span><br><span> </span><br><span> from ..core import log, util, process</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span> from ..core.event_loop import MainLoop</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'serial_device': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('osmocon_phone', resource_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class Osmocon(log.Origin):</span><br><span> </span><br><span>     FIRMWARE_FILE="opt/osmocom-bb/target/firmware/board/compal_e88/layer1.compalram.bin"</span><br><span>diff --git a/src/osmo_gsm_tester/obj/run_node.py b/src/osmo_gsm_tester/obj/run_node.py</span><br><span>index e9f43a1..26c85df 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/run_node.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/run_node.py</span><br><span>@@ -18,6 +18,17 @@</span><br><span> # along with this program.  If not, see <http://www.gnu.org/licenses/>.</span><br><span> </span><br><span> from ..core import log</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'run_type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'run_addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        'ssh_user': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'ssh_addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('run_node', resource_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> </span><br><span> class RunNode(log.Origin):</span><br><span> </span><br><span>diff --git a/src/osmo_gsm_tester/resource.py b/src/osmo_gsm_tester/resource.py</span><br><span>index 20302d3..b216c50 100644</span><br><span>--- a/src/osmo_gsm_tester/resource.py</span><br><span>+++ b/src/osmo_gsm_tester/resource.py</span><br><span>@@ -46,120 +46,6 @@</span><br><span> R_MODEM = 'modem'</span><br><span> R_OSMOCON = 'osmocon_phone'</span><br><span> R_ENB = 'enb'</span><br><span style="color: hsl(0, 100%, 40%);">-R_ALL = (R_IP_ADDRESS, R_RUN_NODE, R_BTS, R_ARFCN, R_MODEM, R_OSMOCON, R_ENB)</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-RESOURCES_SCHEMA = {</span><br><span style="color: hsl(0, 100%, 40%);">-        'ip_address[].addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'run_node[].run_type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'run_node[].run_addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'run_node[].ssh_user': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'run_node[].ssh_addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].label': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].ipa_unit_id': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].band': schema.BAND,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].direct_pcu': schema.BOOL_STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].ciphers[]': schema.CIPHER,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].channel_allocator': schema.CHAN_ALLOCATOR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].gprs_mode': schema.GPRS_MODE,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].num_trx': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].max_trx': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].trx_list[].addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].trx_list[].hw_addr': schema.HWADDR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].trx_list[].net_device': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].trx_list[].nominal_power': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].trx_list[].max_power_red': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].trx_list[].timeslot_list[].phys_chan_config': schema.PHY_CHAN,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].trx_list[].power_supply.type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].trx_list[].power_supply.device': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].trx_list[].power_supply.port': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.launch_trx': schema.BOOL_STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.clock_reference': schema.OSMO_TRX_CLOCK_REF,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.trx_ip': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.remote_user': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.dev_args': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.multi_arfcn': schema.BOOL_STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.max_trxd_version': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.channels[].rx_path': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'bts[].osmo_trx.channels[].tx_path': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].label': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].remote_user': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].gtp_bind_addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].id': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].num_prb': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].transmission_mode': schema.LTE_TRANSMISSION_MODE,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].tx_gain': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].rx_gain': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].rf_dev_type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].rf_dev_args': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].additional_args': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].enable_measurements': schema.BOOL_STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a1_report_type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a1_report_value': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a1_hysteresis': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a1_time_to_trigger': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a2_report_type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a2_report_value': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a2_hysteresis': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a2_time_to_trigger': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a3_report_type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a3_report_value': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a3_hysteresis': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].a3_time_to_trigger': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].num_cells': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].cell_list[].cell_id': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].cell_list[].pci': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].cell_list[].ncell_list[]': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].cell_list[].scell_list[]': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].cell_list[].dl_earfcn': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].cell_list[].dl_rfemu.type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].cell_list[].dl_rfemu.addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'enb[].cell_list[].dl_rfemu.ports[]': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'arfcn[].arfcn': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'arfcn[].band': schema.BAND,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].label': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].path': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].imsi': schema.IMSI,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].ki': schema.KI,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].auth_algo': schema.AUTH_ALGO,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].apn_ipaddr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].remote_user': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].ciphers[]': schema.CIPHER,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].features[]': schema.MODEM_FEATURE,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].rf_dev_type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].rf_dev_args': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].num_carriers': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].additional_args': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].airplane_t_on_ms': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].airplane_t_off_ms': schema.INT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].tx_gain': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'modem[].rx_gain': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-        'osmocon_phone[].serial_device': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-    }</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-WANT_SCHEMA = util.dict_add(</span><br><span style="color: hsl(0, 100%, 40%);">-    dict([('%s[].times' % r, schema.TIMES) for r in R_ALL]),</span><br><span style="color: hsl(0, 100%, 40%);">-    RESOURCES_SCHEMA)</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-CONF_SCHEMA = util.dict_add(</span><br><span style="color: hsl(0, 100%, 40%);">-    { 'defaults.timeout': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-      'config.bsc.net.codec_list[]': schema.CODEC,</span><br><span style="color: hsl(0, 100%, 40%);">-      'config.enb.enable_pcap': schema.BOOL_STR,</span><br><span style="color: hsl(0, 100%, 40%);">-      'config.epc.type': schema.STR,</span><br><span style="color: hsl(0, 100%, 40%);">-      'config.epc.qci': schema.UINT,</span><br><span style="color: hsl(0, 100%, 40%);">-      'config.epc.enable_pcap': schema.BOOL_STR,</span><br><span style="color: hsl(0, 100%, 40%);">-      'config.modem.enable_pcap': schema.BOOL_STR,</span><br><span style="color: hsl(0, 100%, 40%);">-      'config.amarisoft.license_server_addr': schema.IPV4,</span><br><span style="color: hsl(0, 100%, 40%);">-      'config.iperf3cli.time': schema.DURATION,</span><br><span style="color: hsl(0, 100%, 40%);">-    },</span><br><span style="color: hsl(0, 100%, 40%);">-    dict([('resources.%s' % key, val) for key, val in WANT_SCHEMA.items()]),</span><br><span style="color: hsl(0, 100%, 40%);">-    dict([('modifiers.%s' % key, val) for key, val in WANT_SCHEMA.items()]))</span><br><span> </span><br><span> KNOWN_BTS_TYPES = {</span><br><span>         'osmo-bts-sysmo': bts_sysmo.SysmoBts,</span><br><span>@@ -204,7 +90,7 @@</span><br><span>         self.read_conf()</span><br><span> </span><br><span>     def read_conf(self):</span><br><span style="color: hsl(0, 100%, 40%);">-        self.all_resources = Resources(config.read(self.config_path, RESOURCES_SCHEMA))</span><br><span style="color: hsl(120, 100%, 40%);">+        self.all_resources = Resources(config.read(self.config_path, schema.get_resources_schema()))</span><br><span>         self.all_resources.set_hashes()</span><br><span> </span><br><span>     def reserve(self, origin, want, modifiers):</span><br><span>@@ -239,8 +125,8 @@</span><br><span>            'modem': [ {}, {} ],</span><br><span>          }</span><br><span>         '''</span><br><span style="color: hsl(0, 100%, 40%);">-        schema.validate(want, RESOURCES_SCHEMA)</span><br><span style="color: hsl(0, 100%, 40%);">-        schema.validate(modifiers, RESOURCES_SCHEMA)</span><br><span style="color: hsl(120, 100%, 40%);">+        schema.validate(want, schema.get_resources_schema())</span><br><span style="color: hsl(120, 100%, 40%);">+        schema.validate(modifiers, schema.get_resources_schema())</span><br><span> </span><br><span>         origin_id = origin.origin_id()</span><br><span> </span><br><span>@@ -480,7 +366,7 @@</span><br><span>     def add(self, more):</span><br><span>         if more is self:</span><br><span>             raise RuntimeError('adding a list of resources to itself?')</span><br><span style="color: hsl(0, 100%, 40%);">-        config.add(self, copy.deepcopy(more))</span><br><span style="color: hsl(120, 100%, 40%);">+        schema.add(self, copy.deepcopy(more))</span><br><span> </span><br><span>     def mark_reserved_by(self, origin_id):</span><br><span>         for key, item_list in self.items():</span><br><span>diff --git a/src/osmo_gsm_tester/suite.py b/src/osmo_gsm_tester/suite.py</span><br><span>index fecb7a6..6d2916c 100644</span><br><span>--- a/src/osmo_gsm_tester/suite.py</span><br><span>+++ b/src/osmo_gsm_tester/suite.py</span><br><span>@@ -21,7 +21,7 @@</span><br><span> import sys</span><br><span> import time</span><br><span> import pprint</span><br><span style="color: hsl(0, 100%, 40%);">-from .core import config, log, util, process</span><br><span style="color: hsl(120, 100%, 40%);">+from .core import config, log, util, process, schema</span><br><span> from .core.event_loop import MainLoop</span><br><span> from .obj import nitb_osmo, hlr_osmo, mgcpgw_osmo, mgw_osmo, msc_osmo, bsc_osmo, stp_osmo, ggsn_osmo, sgsn_osmo, esme, osmocon, ms_driver, iperf3</span><br><span> from .obj import run_node</span><br><span>@@ -50,7 +50,7 @@</span><br><span>             raise RuntimeError('No such directory: %r' % self.suite_dir)</span><br><span>         self.conf = config.read(os.path.join(self.suite_dir,</span><br><span>                                              SuiteDefinition.CONF_FILENAME),</span><br><span style="color: hsl(0, 100%, 40%);">-                                resource.CONF_SCHEMA)</span><br><span style="color: hsl(120, 100%, 40%);">+                                schema.get_all_schema())</span><br><span>         self.load_test_basenames()</span><br><span> </span><br><span>     def load_test_basenames(self):</span><br><span>@@ -143,7 +143,7 @@</span><br><span>             log.dbg(scenario=scenario.name(), conf=c)</span><br><span>             if c is None:</span><br><span>                 continue</span><br><span style="color: hsl(0, 100%, 40%);">-            config.combine(combination, c)</span><br><span style="color: hsl(120, 100%, 40%);">+            schema.combine(combination, c)</span><br><span>         return combination</span><br><span> </span><br><span>     def get_run_dir(self):</span><br><span>@@ -486,7 +486,7 @@</span><br><span> def load_suite_scenario_str(suite_scenario_str):</span><br><span>     suite_name, scenario_names = parse_suite_scenario_str(suite_scenario_str)</span><br><span>     suite = load(suite_name)</span><br><span style="color: hsl(0, 100%, 40%);">-    scenarios = [config.get_scenario(scenario_name, resource.CONF_SCHEMA) for scenario_name in scenario_names]</span><br><span style="color: hsl(120, 100%, 40%);">+    scenarios = [config.get_scenario(scenario_name, schema.get_all_schema()) for scenario_name in scenario_names]</span><br><span>     return (suite_scenario_str, suite, scenarios)</span><br><span> </span><br><span> def bts_obj(suite_run, conf):</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/18023">change 18023</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/18023"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: osmo-gsm-tester </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I8fd6773c51d19405a585977af4ed72cad2b21db1 </div>
<div style="display:none"> Gerrit-Change-Number: 18023 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>