| module Osmocom_CTRL_Functions { |
| |
| /* Definition of helper functions for the Osmocom CTRL interface. |
| * |
| * As opposed to many other parts of the Osmocom TTCN-3 code base, this module |
| * implements blocking functions, instead of asynchronous functions. The |
| * rationale for this is simple: One normally wants to inquire a value or set |
| * a value and not continue the main program until that operation is complete. |
| * |
| * CTRL is a machine-type protocol on how external programs can interact with |
| * an Osmocom program in a structured way. It is intended for programmatic |
| * access (by other software), as opposed to the VTY interface intended for |
| * human consumption. |
| * |
| * (C) 2017 by Harald Welte <laforge@gnumonks.org> |
| * All rights reserved. |
| * |
| * Released under the terms of GNU General Public License, Version 2 or |
| * (at your option) any later version. |
| */ |
| |
| |
| import from Osmocom_CTRL_Types all; |
| import from IPA_Emulation all; |
| |
| type record of charstring charstring_list; |
| |
| private function f_gen_rand_id() return CtrlId { |
| return int2str(float2int(rnd()*999999999.0)); |
| } |
| |
| /* perform a given GET Operation */ |
| function f_ctrl_get(IPA_CTRL_PT pt, CtrlVariable variable) return CtrlValue { |
| timer T := 2.0; |
| var CtrlMessage rx; |
| var CtrlId id := f_gen_rand_id(); |
| pt.send(ts_CtrlMsgGet(id, variable)); |
| T.start; |
| alt { |
| [] pt.receive(tr_CtrlMsgGetRepl(id, variable)) -> value rx { |
| } |
| [] pt.receive(tr_CtrlMsgTrap) { repeat; } |
| [] pt.receive(tr_CtrlMsgError) -> value rx { |
| setverdict(fail, "Error in CTRL GET ", variable, ": ", rx.err.reason); |
| mtc.stop; |
| } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for CTRL GET REPLY ", variable); |
| mtc.stop; |
| } |
| } |
| return rx.resp.val; |
| } |
| |
| /* perform a given SET Operation */ |
| function f_ctrl_set(IPA_CTRL_PT pt, CtrlVariable variable, CtrlValue val) { |
| timer T := 2.0; |
| var CtrlMessage rx; |
| var CtrlId id := f_gen_rand_id(); |
| pt.send(ts_CtrlMsgSet(id, variable, val)); |
| T.start; |
| alt { |
| [] pt.receive(tr_CtrlMsgSetRepl(id, variable, val)) { } |
| [] pt.receive(tr_CtrlMsgTrap) { repeat; } |
| [] pt.receive(tr_CtrlMsgError) -> value rx { |
| setverdict(fail, "Error in CTRL SET ", variable, ": ", rx.err.reason); |
| mtc.stop; |
| } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for CTRL SET REPLY ", variable); |
| mtc.stop; |
| } |
| } |
| } |
| |
| /* send a TRAP */ |
| function f_ctrl_trap(IPA_CTRL_PT pt, CtrlVariable variable, CtrlValue val) { |
| pt.send(ts_CtrlMsgTrap(variable, val)); |
| } |
| |
| /* Expect a matching TRAP */ |
| function f_ctrl_exp_trap(IPA_CTRL_PT pt, template CtrlVariable variable, |
| template CtrlValue val := ?, float timeout_val := 2.0) |
| return CtrlValue { |
| timer T := timeout_val; |
| var CtrlMessage rx; |
| T.start; |
| alt { |
| [] pt.receive(tr_CtrlMsgTrap(variable, val)) -> value rx { |
| } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for TRAP ", variable); |
| mtc.stop; |
| } |
| } |
| return rx.trap.val; |
| } |
| |
| /* Expect a matching SET, optionally answer */ |
| function f_ctrl_exp_set(IPA_CTRL_PT pt, template CtrlVariable variable, |
| template CtrlValue val := ?, |
| template (omit) CtrlValue rsp := omit, |
| float timeout_val := 2.0) |
| return CtrlValue { |
| timer T := timeout_val; |
| var CtrlMessage rx; |
| T.start; |
| alt { |
| [] pt.receive(tr_CtrlMsgSet(?, variable, val)) -> value rx { |
| if (ispresent(rsp)) { |
| pt.send(ts_CtrlMsgSetRepl(rx.cmd.id, valueof(variable), valueof(rsp))); |
| } |
| } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for SET ", variable); |
| mtc.stop; |
| } |
| } |
| return rx.cmd.val; |
| } |
| |
| /* Expect a matching SET, optionally answer */ |
| function f_ctrl_exp_get(IPA_CTRL_PT pt, template CtrlVariable variable, |
| template (omit) CtrlValue rsp := omit, |
| float timeout_val := 2.0) { |
| timer T := timeout_val; |
| var CtrlMessage rx; |
| T.start; |
| alt { |
| [] pt.receive(tr_CtrlMsgGet(?, variable)) -> value rx { |
| if (ispresent(rsp)) { |
| pt.send(ts_CtrlMsgGetRepl(rx.cmd.id, valueof(variable), valueof(rsp))); |
| } |
| } |
| [] T.timeout { |
| setverdict(fail, "Timeout waiting for GET ", variable); |
| mtc.stop; |
| } |
| } |
| } |
| |
| /* Expect a matching GET result */ |
| function f_ctrl_get_exp(IPA_CTRL_PT pt, CtrlVariable variable, template CtrlValue exp) { |
| var charstring ctrl_resp; |
| ctrl_resp := f_ctrl_get(pt, variable); |
| if (not match(ctrl_resp, exp)) { |
| setverdict(fail, "Unexpected " & variable & ":" & ctrl_resp); |
| mtc.stop; |
| } |
| } |
| |
| template charstring ts_ctrl_ratectr(CtrlVariable grp, integer instance, CtrlVariable name, |
| CtrlVariable kind := "abs") := |
| "rate_ctr." & kind & "." & grp & "." & int2str(instance) & "." & name; |
| |
| function f_ctrl_get_ratectr_abs(IPA_CTRL_PT pt, CtrlVariable grp, integer instance, |
| CtrlVariable name) return integer { |
| return str2int(f_ctrl_get(pt, valueof(ts_ctrl_ratectr(grp, instance, name)))); |
| } |
| |
| function f_ctrl_get_exp_ratectr_abs(IPA_CTRL_PT pt, CtrlVariable grp, integer instance, |
| CtrlVariable name, template integer exp) { |
| var charstring ctrl_resp; |
| var CtrlVariable variable := valueof(ts_ctrl_ratectr(grp, instance, name)); |
| ctrl_resp := f_ctrl_get(pt, variable); |
| if (not match(str2int(ctrl_resp), exp)) { |
| setverdict(fail, variable & " value " & ctrl_resp & " didn't match ", exp); |
| mtc.stop; |
| } |
| } |
| |
| |
| /* --- Retrieve and verify rate counter values in bulk --- |
| * |
| * BSC_Tests.ttcn shows a nice way to conveniently shorten the code needed to use these functions, see |
| * f_ctrs_msc_init() and f_ctrs_msc_expect(). |
| * |
| * Here also a full usage example: |
| * |
| * const CounterNameVals my_counternames := { |
| * { "mscpool:subscr:new", 0 }, |
| * { "mscpool:subscr:known", 0 }, |
| * { "mscpool:subscr:attach_lost", 0 }, |
| * }; |
| * |
| * var CounterNameValsList my_counters := f_counter_name_vals_get_n(instance_name := "msc", instance_count := 3, |
| * counternames := my_counternames); |
| * |
| * // run some tests that increment rate counters in the program, |
| * // and increment expected counters accordingly: |
| * f_counter_name_vals_list_add(my_counters, instance_nr := 1, "mscpool:subscr:new", 7); |
| * f_counter_name_vals_list_add(my_counters, instance_nr := 2, "mscpool:subscr:attach_lost", 3); |
| * |
| * // verify that the program reflects the expected counters: |
| * f_counter_name_vals_expect_n(instance_name := "msc", my_counters); |
| * |
| * // run some more tests... |
| * f_counter_name_vals_list_add(my_counters, instance_nr := 0, "mscpool:subscr:known"); |
| * // and verify again |
| * f_counter_name_vals_expect_n(instance_name := "msc", my_counters); |
| */ |
| |
| /* One counter value, e.g. { "name", 23 } */ |
| type record CounterNameVal { |
| charstring name, |
| integer val |
| } |
| |
| /* List of one instance's counters, |
| * e.g. { {"foo",23}, {"bar",42} } |
| */ |
| type record of CounterNameVal CounterNameVals; |
| |
| /* List of numerous instances' counters, |
| * e.g. { { {"foo",23}, {"bar",42} }, |
| * { {"foo",23}, {"bar",42} } } |
| */ |
| type record of CounterNameVals CounterNameValsList; |
| |
| /* Retrieve one instance's rate counter values of the given names. */ |
| function f_counter_name_vals_get(IPA_CTRL_PT pt, charstring instance_name, integer instance_nr, |
| CounterNameVals counternames) |
| return CounterNameVals { |
| var CounterNameVals vals; |
| for (var integer i := 0; i < lengthof(counternames); i := i + 1) { |
| vals[i] := { |
| name := counternames[i].name, |
| val := f_ctrl_get_ratectr_abs(pt, instance_name, instance_nr, counternames[i].name) |
| }; |
| } |
| return vals; |
| } |
| |
| /* Retrieve the first N instances' rate counter values of the given names */ |
| function f_counter_name_vals_get_n(IPA_CTRL_PT pt, charstring instance_name := "msc", |
| integer instance_count, CounterNameVals counternames) |
| return CounterNameValsList { |
| var CounterNameValsList valslist; |
| for (var integer instance_nr := 0; instance_nr < instance_count; instance_nr := instance_nr + 1) { |
| valslist[instance_nr] := f_counter_name_vals_get(pt, instance_name, instance_nr, counternames); |
| } |
| log("retrieved rate counters: ", instance_name, ": ", valslist); |
| return valslist; |
| } |
| |
| /* In a list of one instance's counters, increment a specifically named counter. */ |
| function f_counter_name_vals_add(inout CounterNameVals vals, charstring countername, integer val := 1) |
| { |
| for (var integer i := 0; i < lengthof(vals); i := i + 1) { |
| if (vals[i].name == countername) { |
| vals[i].val := vals[i].val + val; |
| return; |
| } |
| } |
| /* name not found, append */ |
| vals[lengthof(vals)] := { |
| name := countername, |
| val := val |
| } |
| } |
| |
| /* In a list of several instances' counters, increment a specific instance's specifically named counter. */ |
| function f_counter_name_vals_list_add(inout CounterNameValsList vals, integer instance_nr, |
| charstring countername, integer val := 1) |
| { |
| f_counter_name_vals_add(vals[instance_nr], countername, val); |
| } |
| |
| /* For a specific instance, call f_counter_name_vals_get() and compare with expected counter values. |
| * Set the test verdict accordingly. */ |
| function f_counter_name_vals_expect(IPA_CTRL_PT pt, charstring instance_name, integer instance_nr, |
| CounterNameVals vals) { |
| var CounterNameVals last := f_counter_name_vals_get(pt, instance_name, instance_nr, vals); |
| for (var integer i := 0; i < lengthof(vals); i := i + 1) { |
| if (last[i].name != vals[i].name) { |
| setverdict(fail, "Internal error"); |
| } |
| if (last[i].val != vals[i].val) { |
| setverdict(fail, "Rate counter mismatch: ", instance_name, " ", instance_nr, |
| " ", vals[i].name, " is at ", last[i].val, " but expected ", vals[i].val); |
| } |
| } |
| setverdict(pass); |
| } |
| |
| /* For N instances, call f_counter_name_vals_get() and compare with expected counter values. |
| * Set the test verdict accordingly. The number of instances is given by lengthof(valslist). */ |
| function f_counter_name_vals_expect_n(IPA_CTRL_PT pt, charstring instance_name, CounterNameValsList valslist) { |
| for (var integer instance_nr := 0; instance_nr < lengthof(valslist); instance_nr := instance_nr + 1) { |
| f_counter_name_vals_expect(pt, instance_name, instance_nr, valslist[instance_nr]); |
| } |
| } |
| |
| /* For a specific instance, call f_counter_name_vals_get() and indentify counters that have changed with respect |
| * to 'vals'. Return list of the changed counter names in the order they appear in 'vals'. */ |
| function f_counter_name_vals_get_changed(IPA_CTRL_PT pt, charstring instance_name, integer instance_nr, |
| CounterNameVals vals) |
| return charstring_list { |
| var charstring_list changed := {}; |
| var CounterNameVals last := f_counter_name_vals_get(pt, instance_name, instance_nr, vals); |
| for (var integer i := 0; i < lengthof(vals); i := i + 1) { |
| if (last[i].name != vals[i].name) { |
| setverdict(fail, "Internal error"); |
| } |
| if (last[i].val != vals[i].val) { |
| changed := changed & { instance_name & "." & int2str(instance_nr) & "." & vals[i].name }; |
| } |
| } |
| return changed; |
| } |
| |
| /* For N instances, call f_counter_name_vals_get() and indentify counters that have changed with respect |
| * to 'vals'. Return list of the changed counter names in the order they appear in 'vals'. */ |
| function f_counter_name_vals_get_changed_n(IPA_CTRL_PT pt, charstring instance_name, CounterNameValsList valslist) |
| return charstring_list { |
| var charstring_list changed := {}; |
| for (var integer instance_nr := 0; instance_nr < lengthof(valslist); instance_nr := instance_nr + 1) { |
| changed := changed & f_counter_name_vals_get_changed(pt, instance_name, instance_nr, valslist[instance_nr]); |
| } |
| return changed; |
| } |
| |
| function f_counter_name_vals_expect_changed(IPA_CTRL_PT pt, charstring instance_name, CounterNameValsList valslist, |
| charstring_list expect_changed) { |
| var charstring_list changed := f_counter_name_vals_get_changed_n(pt, instance_name, valslist); |
| f_counter_name_vals_expect_changed_list(changed, expect_changed); |
| } |
| |
| function f_counter_name_vals_expect_changed_list(charstring_list got_list, charstring_list expect_list) { |
| var charstring unexpected_change := ""; |
| for (var integer i := 0; i < lengthof(got_list); i := i + 1) { |
| var boolean found := false; |
| for (var integer j := 0; j < lengthof(expect_list); j := j + 1) { |
| if (got_list[i] == expect_list[j]) { |
| found := true; |
| break; |
| } |
| } |
| if (not found) { |
| unexpected_change := unexpected_change & " " & got_list[i]; |
| } |
| } |
| var charstring missing_change := ""; |
| for (var integer i := 0; i < lengthof(expect_list); i := i + 1) { |
| var boolean found := false; |
| for (var integer j := 0; j < lengthof(got_list); j := j + 1) { |
| if (expect_list[i] == got_list[j]) { |
| found := true; |
| break; |
| } |
| } |
| if (not found) { |
| missing_change := missing_change & " " & expect_list[i]; |
| } |
| } |
| var charstring diff := ""; |
| if (lengthof(unexpected_change) > 0) { |
| diff := diff & " Unexpected changes in" & unexpected_change & ";"; |
| } |
| if (lengthof(missing_change) > 0) { |
| diff := diff & " Missing changes in" & missing_change & ";"; |
| } |
| if (lengthof(diff) > 0) { |
| log("ERROR\nExpected: ", expect_list, "\nGot: ", got_list); |
| setverdict(fail, "Rate counters did not change as expected:" & diff); |
| } else { |
| setverdict(pass); |
| } |
| } |
| |
| } |