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.orgNeels 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>