[MERGED] osmo-gsm-tester[master]: Replicate resources based on times attr before combine time

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Neels Hofmeyr gerrit-no-reply at lists.osmocom.org
Sat Sep 16 19:51:35 UTC 2017


Neels Hofmeyr has submitted this change and it was merged.

Change subject: Replicate resources based on times attr before combine time
......................................................................


Replicate resources based on times attr before combine time

As suite.conf and scenarios need to match 1-to-1 in lists, it's important to
extend the dictionaries by replicating the objects with a 'times' values
higher than 1 in order to match the objects correctly.

Since dictionanries are expanded at combine time, there's no need to
expand them during reserve() time because they are already expanded.
As a result, this commit reworks the kind of schema applied in each
place (and takes the change to start validating scenario files, which
were neglected previously).

Two unit tests are added as a show case. Unfortunately output showing
scenario dictionaries needs to be ignored while verifying because it was
encountered that different versions of python print dictionary elements
in different order.

Change-Id: I25eb639c7e3cf3b4c67a205422808bffbdd791e6
---
M selftest/resource_test.py
M selftest/suite_test.ok
M selftest/suite_test.ok.ign
M selftest/suite_test.py
M selftest/suite_test/test_suite/suite.conf
M src/osmo_gsm_tester/config.py
M src/osmo_gsm_tester/resource.py
M src/osmo_gsm_tester/suite.py
8 files changed, 211 insertions(+), 37 deletions(-)

Approvals:
  Neels Hofmeyr: Looks good to me, approved
  Jenkins Builder: Verified



diff --git a/selftest/resource_test.py b/selftest/resource_test.py
index a0ec490..d72eb72 100755
--- a/selftest/resource_test.py
+++ b/selftest/resource_test.py
@@ -80,7 +80,7 @@
 
 origin = log.Origin(None, 'testowner')
 
-resources = pool.reserve(origin, want)
+resources = pool.reserve(origin, config.replicate_times(want))
 
 print('~~~ currently reserved:')
 with open(rrfile, 'r') as f:
diff --git a/selftest/suite_test.ok b/selftest/suite_test.ok
index 049f7b4..365fcaa 100644
--- a/selftest/suite_test.ok
+++ b/selftest/suite_test.ok
@@ -16,6 +16,7 @@
 resources:
   bts:
   - times: '1'
+  - times: '2'
   ip_address:
   - times: '1'
   modem:
@@ -30,14 +31,30 @@
 ---------------------------------------------------------------------
 tst test_suite: reserving resources in [PATH]/selftest/suite_test/test_work/state_dir ...
 tst test_suite: DBG: {combining='resources'}
-tst {combining_scenarios='resources'}: DBG: {definition_conf={bts=[{'times': '1'}], ip_address=[{'times': '1'}], modem=[{'times': '2'}]}}  [test_suite↪{combining_scenarios='resources'}]
-tst test_suite: Reserving 1 x bts (candidates: 6)
+tst {combining_scenarios='resources'}: DBG: {definition_conf={bts=[{}, {}, {}], ip_address=[{}], modem=[{}, {}]}}  [test_suite↪{combining_scenarios='resources'}]
+tst test_suite: Reserving 3 x bts (candidates: 6)
 tst test_suite: DBG: Picked - _hash: 07d9c8aaa940b674efcbbabdd69f58a6ce4e94f9
   addr: 10.42.42.114
   band: GSM-1800
   ipa_unit_id: '1'
   label: sysmoBTS 1002
   type: sysmo
+- _hash: 76c8d2f459113cd6c99ed62d1a94bbe9a291ba94
+  addr: 10.42.42.115
+  band: GSM-1800
+  ipa_unit_id: '5'
+  label: octBTS 3000
+  trx_list:
+  - hw_addr: 00:0c:90:32:b5:8a
+  type: oct
+- _hash: 0b7fabd512b36aec43d7d496abd00af4e193b0f8
+  addr: 10.42.42.190
+  band: GSM-1900
+  ipa_unit_id: '1902'
+  label: nanoBTS 1900
+  trx_list:
+  - hw_addr: 00:02:95:00:41:b3
+  type: nanobts
 tst test_suite: Reserving 1 x ip_address (candidates: 3)
 tst test_suite: DBG: Picked - _hash: cde1debf28f07f94f92c761b4b7c6bf35785ced4
   addr: 10.42.42.1
@@ -140,5 +157,151 @@
     skip: test_error.py (N.N sec)
     skip: test_fail.py (N.N sec)
     FAIL: test_fail_raise.py (N.N sec) ExpectedFail: This failure is expected
+- test with half empty scenario
+cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in ./suite_test which is [PATH]/selftest/suite_test  [config.py:[LINENR]]
+cnf ResourcesPool: DBG: Found path state_dir as [PATH]/selftest/suite_test/test_work/state_dir  [config.py:[LINENR]]
+
+---------------------------------------------------------------------
+trial test_suite
+---------------------------------------------------------------------
+tst test_suite: reserving resources in [PATH]/selftest/suite_test/test_work/state_dir ...  [suite.py:[LINENR]]
+tst test_suite: DBG: {combining='resources'}  [suite.py:[LINENR]]
+tst {combining_scenarios='resources'}: DBG: {definition_conf={bts=[{}, {}, {}], ip_address=[{}], modem=[{}, {}]}}  [test_suite↪{combining_scenarios='resources'}]  [suite.py:[LINENR]]
+tst {combining_scenarios='resources', scenario='foo'}: [RESOURCE_DICT]
+tst test_suite: Reserving 3 x bts (candidates: 6)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: 07d9c8aaa940b674efcbbabdd69f58a6ce4e94f9
+  addr: 10.42.42.114
+  band: GSM-1800
+  ipa_unit_id: '1'
+  label: sysmoBTS 1002
+  type: sysmo
+- _hash: 76c8d2f459113cd6c99ed62d1a94bbe9a291ba94
+  addr: 10.42.42.115
+  band: GSM-1800
+  ipa_unit_id: '5'
+  label: octBTS 3000
+  trx_list:
+  - hw_addr: 00:0c:90:32:b5:8a
+  type: oct
+- _hash: 0b7fabd512b36aec43d7d496abd00af4e193b0f8
+  addr: 10.42.42.190
+  band: GSM-1900
+  ipa_unit_id: '1902'
+  label: nanoBTS 1900
+  trx_list:
+  - hw_addr: 00:02:95:00:41:b3
+  type: nanobts
+  [resource.py:[LINENR]]
+tst test_suite: Reserving 1 x ip_address (candidates: 3)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: cde1debf28f07f94f92c761b4b7c6bf35785ced4
+  addr: 10.42.42.1
+  [resource.py:[LINENR]]
+tst test_suite: Reserving 2 x modem (candidates: 16)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: 19c69e45aa090fb511446bd00797690aa82ff52f
+  imsi: '901700000007801'
+  ki: D620F48487B1B782DA55DF6717F08FF9
+  label: m7801
+  path: /wavecom_0
+- _hash: e1a46516a1fb493b2617ab14fc1693a9a45ec254
+  imsi: '901700000007802'
+  ki: 47FDB2D55CE6A10A85ABDAD034A5B7B3
+  label: m7802
+  path: /wavecom_1
+  [resource.py:[LINENR]]
+
+----------------------------------------------
+trial test_suite hello_world.py
+----------------------------------------------
+tst hello_world.py:[LINENR]: hello world  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: I am 'test_suite' / 'hello_world.py:[LINENR]'  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: one  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: two  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: three  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR] Test passed (N.N sec)  [test_suite↪hello_world.py]  [suite.py:[LINENR]]
+---------------------------------------------------------------------
+trial test_suite PASS
+---------------------------------------------------------------------
+PASS: test_suite (pass: 1, skip: 5)
+    pass: hello_world.py (N.N sec)
+    skip: mo_mt_sms.py
+    skip: mo_sms.py
+    skip: test_error.py
+    skip: test_fail.py
+    skip: test_fail_raise.py
+- test with scenario
+cnf ResourcesPool: DBG: Found config file resources.conf as [PATH]/selftest/suite_test/resources.conf in ./suite_test which is [PATH]/selftest/suite_test  [config.py:[LINENR]]
+cnf ResourcesPool: DBG: Found path state_dir as [PATH]/selftest/suite_test/test_work/state_dir  [config.py:[LINENR]]
+
+---------------------------------------------------------------------
+trial test_suite
+---------------------------------------------------------------------
+tst test_suite: reserving resources in [PATH]/selftest/suite_test/test_work/state_dir ...  [suite.py:[LINENR]]
+tst test_suite: DBG: {combining='resources'}  [suite.py:[LINENR]]
+tst {combining_scenarios='resources'}: DBG: {definition_conf={bts=[{}, {}, {}], ip_address=[{}], modem=[{}, {}]}}  [test_suite↪{combining_scenarios='resources'}]  [suite.py:[LINENR]]
+tst {combining_scenarios='resources', scenario='foo'}: [RESOURCE_DICT]
+tst test_suite: Reserving 3 x bts (candidates: 6)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: f1cab48db5b9004986e2030cb71730a5c55e823e
+  addr: 10.42.42.52
+  band: GSM-1800
+  ipa_unit_id: '6'
+  label: Ettus B200
+  launch_trx: 'True'
+  trx_list:
+  - nominal_power: '10'
+  - nominal_power: '12'
+  type: osmo-bts-trx
+- _hash: 1d00bd0d6643db5590cdbefff3152e70500abefc
+  addr: 10.42.42.53
+  band: GSM-1800
+  ipa_unit_id: '7'
+  label: sysmoCell 5000
+  trx_list:
+  - nominal_power: '10'
+  - nominal_power: '12'
+  trx_remote_ip: 10.42.42.112
+  type: osmo-bts-trx
+- _hash: 07d9c8aaa940b674efcbbabdd69f58a6ce4e94f9
+  addr: 10.42.42.114
+  band: GSM-1800
+  ipa_unit_id: '1'
+  label: sysmoBTS 1002
+  type: sysmo
+  [resource.py:[LINENR]]
+tst test_suite: Reserving 1 x ip_address (candidates: 3)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: cde1debf28f07f94f92c761b4b7c6bf35785ced4
+  addr: 10.42.42.1
+  [resource.py:[LINENR]]
+tst test_suite: Reserving 2 x modem (candidates: 16)  [resource.py:[LINENR]]
+tst test_suite: DBG: Picked - _hash: 19c69e45aa090fb511446bd00797690aa82ff52f
+  imsi: '901700000007801'
+  ki: D620F48487B1B782DA55DF6717F08FF9
+  label: m7801
+  path: /wavecom_0
+- _hash: e1a46516a1fb493b2617ab14fc1693a9a45ec254
+  imsi: '901700000007802'
+  ki: 47FDB2D55CE6A10A85ABDAD034A5B7B3
+  label: m7802
+  path: /wavecom_1
+  [resource.py:[LINENR]]
+
+----------------------------------------------
+trial test_suite hello_world.py
+----------------------------------------------
+tst hello_world.py:[LINENR]: hello world  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: I am 'test_suite' / 'hello_world.py:[LINENR]'  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: one  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: two  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR]: three  [test_suite↪hello_world.py:[LINENR]]  [hello_world.py:[LINENR]]
+tst hello_world.py:[LINENR] Test passed (N.N sec)  [test_suite↪hello_world.py]  [suite.py:[LINENR]]
+---------------------------------------------------------------------
+trial test_suite PASS
+---------------------------------------------------------------------
+PASS: test_suite (pass: 1, skip: 5)
+    pass: hello_world.py (N.N sec)
+    skip: mo_mt_sms.py
+    skip: mo_sms.py
+    skip: test_error.py
+    skip: test_fail.py
+    skip: test_fail_raise.py
 
 - graceful exit.
diff --git a/selftest/suite_test.ok.ign b/selftest/suite_test.ok.ign
index dcda3b6..49bd9eb 100644
--- a/selftest/suite_test.ok.ign
+++ b/selftest/suite_test.ok.ign
@@ -1,3 +1,4 @@
 /[^ ]*/selftest/	[PATH]/selftest/
 \.py:[0-9]*	.py:[LINENR]
 \([0-9.]+ sec\)	(N.N sec)
+{combining_scenarios='resources', scenario='foo'}:.*	{combining_scenarios='resources', scenario='foo'}: [RESOURCE_DICT]
diff --git a/selftest/suite_test.py b/selftest/suite_test.py
index 86c4c25..12bd5e7 100755
--- a/selftest/suite_test.py
+++ b/selftest/suite_test.py
@@ -42,5 +42,21 @@
 output = report.suite_to_text(s)
 print(output)
 
+print('- test with half empty scenario')
+trial = log.Origin(log.C_TST, 'trial')
+scenario = config.Scenario('foo', 'bar')
+scenario['resources'] = { 'bts': [{'type': 'sysmo'}] }
+s = suite.SuiteRun(trial, 'test_suite', s_def, [scenario])
+results = s.run_tests('hello_world.py')
+print(report.suite_to_text(s))
+
+print('- test with scenario')
+trial = log.Origin(log.C_TST, 'trial')
+scenario = config.Scenario('foo', 'bar')
+scenario['resources'] = { 'bts': [{ 'times': '2', 'type': 'osmo-bts-trx', 'trx_list': [{'nominal_power': '10'}, {'nominal_power': '12'}]}, {'type': 'sysmo'}] }
+s = suite.SuiteRun(trial, 'test_suite', s_def, [scenario])
+results = s.run_tests('hello_world.py')
+print(report.suite_to_text(s))
+
 print('\n- graceful exit.')
 # vim: expandtab tabstop=4 shiftwidth=4
diff --git a/selftest/suite_test/test_suite/suite.conf b/selftest/suite_test/test_suite/suite.conf
index 376f6cd..890f66a 100644
--- a/selftest/suite_test/test_suite/suite.conf
+++ b/selftest/suite_test/test_suite/suite.conf
@@ -3,6 +3,7 @@
   - times: 1
   bts:
   - times: 1
+  - times: 2
   modem:
   - times: 2
 
diff --git a/src/osmo_gsm_tester/config.py b/src/osmo_gsm_tester/config.py
index 1d52073..27ce428 100644
--- a/src/osmo_gsm_tester/config.py
+++ b/src/osmo_gsm_tester/config.py
@@ -274,7 +274,13 @@
     return src
 
 def replicate_times(d):
-    'replicate items that have a "times" > 1'
+    '''
+    replicate items that have a "times" > 1
+
+    'd' is a dict matching WANT_SCHEMA, which is the same as
+    the RESOURCES_SCHEMA, except each entity that can be reserved has a 'times'
+    field added, to indicate how many of those should be reserved.
+    '''
     d = copy.deepcopy(d)
     for key, item_list in d.items():
         idx = 0
diff --git a/src/osmo_gsm_tester/resource.py b/src/osmo_gsm_tester/resource.py
index 7ba6e72..8206767 100644
--- a/src/osmo_gsm_tester/resource.py
+++ b/src/osmo_gsm_tester/resource.py
@@ -73,6 +73,10 @@
     dict([('%s[].times' % r, schema.TIMES) for r in R_ALL]),
     RESOURCES_SCHEMA)
 
+CONF_SCHEMA = util.dict_add(
+    { 'defaults.timeout': schema.STR },
+    dict([('resources.%s' % key, val) for key, val in WANT_SCHEMA.items()]))
+
 KNOWN_BTS_TYPES = {
         'osmo-bts-sysmo': bts_sysmo.SysmoBts,
         'osmo-bts-trx': bts_osmotrx.OsmoBtsTrx,
@@ -107,11 +111,9 @@
 
         'origin' should be an Origin() instance.
 
-        'want' is a dict matching WANT_SCHEMA, which is the same as
-        the RESOURCES_SCHEMA, except each entity that can be reserved has a 'times'
-        field added, to indicate how many of those should be reserved.
+        'want' is a dict matching RESOURCES_SCHEMA.
 
-        If an entry has only a 'times' set, any of the resources may be
+        If an entry has no attribute set, any of the resources may be
         reserved without further limitations.
 
         ResourcesPool may also be selected with narrowed down constraints.
@@ -119,24 +121,13 @@
         sysmo and one of type trx, plus 2 ARFCNs in the 1800 band:
 
          {
-           'ip_address': [ { 'times': 1 } ],
-           'bts': [ { 'type': 'sysmo', 'times': 1 }, { 'type': 'trx', 'times': 1 } ],
-           'arfcn': [ { 'band': 'GSM-1800', 'times': 2 } ],
-           'modem': [ { 'times': 2 } ],
-         }
-
-        A times=1 value is implicit, so the above is equivalent to:
-
-         {
            'ip_address': [ {} ],
            'bts': [ { 'type': 'sysmo' }, { 'type': 'trx' } ],
-           'arfcn': [ { 'band': 'GSM-1800', 'times': 2 } ],
-           'modem': [ { 'times': 2 } ],
+           'arfcn': [ { 'band': 'GSM-1800' }, { 'band': 'GSM-1800' } ],
+           'modem': [ {}, {} ],
          }
         '''
-        schema.validate(want, WANT_SCHEMA)
-
-        want = config.replicate_times(want)
+        schema.validate(want, RESOURCES_SCHEMA)
 
         origin_id = origin.origin_id()
 
@@ -271,10 +262,10 @@
         Pass a dict of resource requirements, e.g.:
           want = {
             'bts': [ {'type': 'osmo-bts-sysmo',}, {} ],
-            'modem': [ {'times': 3} ]
+            'modem': [ {}, {}, {} ]
           }
         This function tries to find a combination from the available resources that
-        matches these requiremens. The returnvalue is a dict (wrapped in a Resources class)
+        matches these requirements. The return value is a dict (wrapped in a Resources class)
         that contains the matching resources in the order of 'want' dict: in above
         example, the returned dict would have a 'bts' list with the first item being
         a sysmoBTS, the second item being any other available BTS.
@@ -290,6 +281,10 @@
 
         If raise_if_missing is False, this will return an empty item for any
         resource that had no match, instead of immediately raising an exception.
+
+        This function expects input dictionaries whose contents have already
+        been replicated based on its the 'times' attributes. See
+        config.replicate_times() for more details.
         '''
         matches = {}
         for key, want_list in sorted(want.items()): # sorted for deterministic test results
@@ -314,7 +309,7 @@
                     my_item = my_list[i]
                     if skip_if_marked and my_item.get(skip_if_marked):
                         continue
-                    if item_matches(my_item, want_item, ignore_keys=('times',)):
+                    if item_matches(my_item, want_item):
                         item_match_list.append(i)
                 if not item_match_list:
                     if raise_if_missing:
diff --git a/src/osmo_gsm_tester/suite.py b/src/osmo_gsm_tester/suite.py
index 9b975fd..28bdd69 100644
--- a/src/osmo_gsm_tester/suite.py
+++ b/src/osmo_gsm_tester/suite.py
@@ -40,14 +40,6 @@
 
     CONF_FILENAME = 'suite.conf'
 
-    CONF_SCHEMA = util.dict_add(
-        {
-            'defaults.timeout': schema.STR,
-        },
-        dict([('resources.%s' % k, t) for k,t in resource.WANT_SCHEMA.items()])
-        )
-
-
     def __init__(self, suite_dir):
         self.suite_dir = suite_dir
         super().__init__(log.C_CNF, os.path.basename(self.suite_dir))
@@ -59,7 +51,7 @@
             raise RuntimeError('No such directory: %r' % self.suite_dir)
         self.conf = config.read(os.path.join(self.suite_dir,
                                              SuiteDefinition.CONF_FILENAME),
-                                SuiteDefinition.CONF_SCHEMA)
+                                resource.CONF_SCHEMA)
         self.load_test_basenames()
 
     def load_test_basenames(self):
@@ -209,11 +201,11 @@
     def combined(self, conf_name):
         log.dbg(combining=conf_name)
         log.ctx(combining_scenarios=conf_name)
-        combination = copy.deepcopy(self.definition.conf.get(conf_name) or {})
+        combination = config.replicate_times(self.definition.conf.get(conf_name, {}))
         log.dbg(definition_conf=combination)
         for scenario in self.scenarios:
             log.ctx(combining_scenarios=conf_name, scenario=scenario.name())
-            c = scenario.get(conf_name)
+            c = config.replicate_times(scenario.get(conf_name, {}))
             log.dbg(scenario=scenario.name(), conf=c)
             if c is None:
                 continue
@@ -447,7 +439,7 @@
 def load_suite_scenario_str(suite_scenario_str):
     suite_name, scenario_names = parse_suite_scenario_str(suite_scenario_str)
     suite = load(suite_name)
-    scenarios = [config.get_scenario(scenario_name) for scenario_name in scenario_names]
+    scenarios = [config.get_scenario(scenario_name, resource.CONF_SCHEMA) for scenario_name in scenario_names]
     return (suite_scenario_str, suite, scenarios)
 
 def bts_obj(suite_run, conf):

-- 
To view, visit https://gerrit.osmocom.org/3907
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I25eb639c7e3cf3b4c67a205422808bffbdd791e6
Gerrit-PatchSet: 7
Gerrit-Project: osmo-gsm-tester
Gerrit-Branch: master
Gerrit-Owner: Pau Espin Pedrol <pespin at sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: Neels Hofmeyr <nhofmeyr at sysmocom.de>
Gerrit-Reviewer: Pau Espin Pedrol <pespin at sysmocom.de>



More information about the gerrit-log mailing list