From 0fd615fd7b701fc0b3fd7f96429691e3eaa5a877 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Sat, 26 Jan 2019 20:36:12 +0100 Subject: add osmo_tdef API, originally adopted from osmo-bsc T_def Move T_def from osmo-bsc to libosmocore as osmo_tdef. Adjust naming to be more consistent. Upgrade to first class API: - add timer grouping - add generic vty support - add mising API doc - add C test - add VTY transcript tests, also as examples for using the API From osmo_fsm_inst_state_chg() API doc, cross reference to osmo_tdef API. The root reason for moving to libosmocore is that I want to use the mgw_endpoint_fsm in osmo-msc for inter-MSC handover, and hence want to move the FSM to libosmo-mgcp-client. This FSM uses the T_def from osmo-bsc. Though the mgw_endpoint_fsm's use of T_def is minimal, I intend to use the osmo_tdef API in osmo-msc (and probably elsewhere) as well. libosmocore is the most sensible place for this. osmo_tdef provides: - a list of Tnnnn (GSM) timers with description, unit and default value. - vty UI to allow users to configure non-default timeouts. - API to tie T timers to osmo_fsm states and set them on state transitions. - a few standard units (minute, second, millisecond) as well as a custom unit (which relies on the timer's human readable description to indicate the meaning of the value). - conversion for standard units: for example, some GSM timers are defined in minutes, while our FSM definitions need timeouts in seconds. Conversion is for convenience only and can be easily avoided via the custom unit. By keeping separate osmo_tdef arrays, several groups of timers can be kept separately. The VTY tests in tests/tdef/ showcase different schemes: - tests/vty/tdef_vty_test_config_root.c: Keep several timer definitions in separately named groups: showcase the osmo_tdef_vty_groups*() API. Each timer group exists exactly once. - tests/vty/tdef_vty_test_config_subnode.c: Keep a single list of timers without separate grouping. Put this list on a specific subnode below the CONFIG_NODE. There could be several separate subnodes with timers like this, i.e. continuing from this example, sets timers could be separated by placing timers in specific config subnodes instead of using the global group name. - tests/vty/tdef_vty_test_dynamic.c: Dynamically allocate timer definitions per each new created object. Thus there can be an arbitrary number of independent timer definitions, one per allocated object. T_def was introduced during the recent osmo-bsc refactoring for inter-BSC handover, and has proven useful: - without osmo_tdef, each invocation of osmo_fsm_inst_state_chg() needs to be programmed with the right timeout value, for all code paths that invoke this state change. It is a likely source of errors to get one of them wrong. By defining a T timer exactly for an FSM state, the caller can merely invoke the state change and trust on the original state definition to apply the correct timeout. - it is helpful to have a standardized config file UI to provide user configurable timeouts, instead of inventing new VTY commands for each separate application of T timer numbers. Change-Id: Ibd6b1ed7f1bd6e1f2e0fde53352055a4468f23e5 --- tests/Makefile.am | 36 +++ tests/tdef/tdef_test.c | 445 ++++++++++++++++++++++++++++ tests/tdef/tdef_test.ok | 184 ++++++++++++ tests/tdef/tdef_vty_test_config_root.c | 292 ++++++++++++++++++ tests/tdef/tdef_vty_test_config_root.vty | 292 ++++++++++++++++++ tests/tdef/tdef_vty_test_config_subnode.c | 288 ++++++++++++++++++ tests/tdef/tdef_vty_test_config_subnode.vty | 107 +++++++ tests/tdef/tdef_vty_test_dynamic.c | 362 ++++++++++++++++++++++ tests/tdef/tdef_vty_test_dynamic.vty | 83 ++++++ tests/testsuite.at | 6 + 10 files changed, 2095 insertions(+) create mode 100644 tests/tdef/tdef_test.c create mode 100644 tests/tdef/tdef_test.ok create mode 100644 tests/tdef/tdef_vty_test_config_root.c create mode 100644 tests/tdef/tdef_vty_test_config_root.vty create mode 100644 tests/tdef/tdef_vty_test_config_subnode.c create mode 100644 tests/tdef/tdef_vty_test_config_subnode.vty create mode 100644 tests/tdef/tdef_vty_test_dynamic.c create mode 100644 tests/tdef/tdef_vty_test_dynamic.vty (limited to 'tests') diff --git a/tests/Makefile.am b/tests/Makefile.am index 91f042e8..54fb11f0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -27,6 +27,9 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \ oap/oap_client_test gsm29205/gsm29205_test \ logging/logging_vty_test \ vty/vty_transcript_test \ + tdef/tdef_test tdef/tdef_vty_test_config_root \ + tdef/tdef_vty_test_config_subnode \ + tdef/tdef_vty_test_dynamic \ $(NULL) if ENABLE_MSGFILE @@ -221,6 +224,18 @@ prbs_prbs_test_SOURCES = prbs/prbs_test.c gsm23003_gsm23003_test_SOURCES = gsm23003/gsm23003_test.c gsm23003_gsm23003_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la +tdef_tdef_test_SOURCES = tdef/tdef_test.c +tdef_tdef_test_LDADD = $(LDADD) + +tdef_tdef_vty_test_config_root_SOURCES = tdef/tdef_vty_test_config_root.c +tdef_tdef_vty_test_config_root_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la + +tdef_tdef_vty_test_config_subnode_SOURCES = tdef/tdef_vty_test_config_subnode.c +tdef_tdef_vty_test_config_subnode_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la + +tdef_tdef_vty_test_dynamic_SOURCES = tdef/tdef_vty_test_dynamic.c +tdef_tdef_vty_test_dynamic_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la + # The `:;' works around a Bash 3.2 bug when the output is not writeable. $(srcdir)/package.m4: $(top_srcdir)/configure.ac :;{ \ @@ -284,6 +299,10 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \ timer/clk_override_test.ok \ oap/oap_client_test.ok oap/oap_client_test.err \ vty/vty_transcript_test.vty \ + tdef/tdef_test.ok \ + tdef/tdef_vty_test_config_root.vty \ + tdef/tdef_vty_test_config_subnode.vty \ + tdef/tdef_vty_test_dynamic.vty \ $(NULL) DISTCLEANFILES = atconfig atlocal conv/gsm0503_test_vectors.c @@ -328,6 +347,7 @@ endif # To update the VTY script from current application behavior, # pass -u to osmo_verify_transcript_vty.py by doing: # make vty-test U=-u + vty-test-logging: osmo_verify_transcript_vty.py -v \ -p 42042 \ @@ -340,9 +360,25 @@ vty-test-vty: -r "$(top_builddir)/tests/vty/vty_transcript_test" \ $(U) $(srcdir)/vty/*.vty +vty-test-tdef: + osmo_verify_transcript_vty.py -v \ + -p 42042 \ + -r "$(top_builddir)/tests/tdef/tdef_vty_test_config_root" \ + $(U) $(srcdir)/tdef/tdef_vty_test_config_root.vty + osmo_verify_transcript_vty.py -v \ + -p 42042 \ + -r "$(top_builddir)/tests/tdef/tdef_vty_test_config_subnode" \ + $(U) $(srcdir)/tdef/tdef_vty_test_config_subnode.vty + osmo_verify_transcript_vty.py -v \ + -p 42042 \ + -r "$(top_builddir)/tests/tdef/tdef_vty_test_dynamic" \ + $(U) $(srcdir)/tdef/tdef_vty_test_dynamic.vty + +# don't run vty tests concurrently so that the ports don't conflict vty-test: $(MAKE) vty-test-logging $(MAKE) vty-test-vty + $(MAKE) vty-test-tdef ctrl-test: echo "No CTRL tests exist currently" diff --git a/tests/tdef/tdef_test.c b/tests/tdef/tdef_test.c new file mode 100644 index 00000000..682c7ac7 --- /dev/null +++ b/tests/tdef/tdef_test.c @@ -0,0 +1,445 @@ +/* Test implementation for osmo_tdef API. */ +/* + * (C) 2019 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include + +#include +#include +#include + +#include + +static void *ctx = NULL; + +static struct osmo_tdef tdefs[] = { + { .T=1, .default_val=100, .desc="100s" }, + { .T=2, .default_val=100, .unit=OSMO_TDEF_MS, .desc="100ms" }, + { .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="100m" }, + { .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="100 potatoes" }, + + { .T=7, .default_val=50, .desc="Water Boiling Timeout" }, // default is .unit=OSMO_TDEF_S == 0 + { .T=8, .default_val=300, .desc="Tea brewing" }, + { .T=9, .default_val=5, .unit=OSMO_TDEF_M, .desc="Let tea cool down before drinking" }, + { .T=10, .default_val=20, .unit=OSMO_TDEF_M, .desc="Forgot to drink tea while it's warm" }, + + /* test conversions */ + { .T=1000, .default_val=2*1000, .unit=OSMO_TDEF_MS, .desc="two seconds from ms" }, + { .T=1001, .default_val=60*1000, .unit=OSMO_TDEF_MS, .desc="one minute from ms" }, + { .T=1002, .default_val=(ULONG_MAX/60), .unit=OSMO_TDEF_M, .desc="almost too many seconds" }, + { .T=1003, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="too many seconds" }, + { .T=1004, .default_val=1, .unit=OSMO_TDEF_MS, .desc="one ms" }, + { .T=1005, .default_val=0, .unit=OSMO_TDEF_MS, .desc="zero ms" }, + { .T=1006, .default_val=0, .unit=OSMO_TDEF_S, .desc="zero s" }, + { .T=1007, .default_val=0, .unit=OSMO_TDEF_M, .desc="zero m" }, + { .T=1008, .default_val=0, .unit=OSMO_TDEF_CUSTOM, .desc="zero" }, + + /* test range */ + { .T=INT_MAX, .default_val=ULONG_MAX, .unit=OSMO_TDEF_S, .desc="very large" }, + { .T=INT_MAX-1, .default_val=ULONG_MAX-1, .unit=OSMO_TDEF_S, .desc="very large" }, + { .T=INT_MAX-2, .default_val=LONG_MAX, .unit=OSMO_TDEF_S, .desc="very large" }, + { .T=INT_MAX-3, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="very large in minutes" }, + { .T=INT_MIN, .default_val=ULONG_MAX, .unit=OSMO_TDEF_S, .desc="negative" }, + + { .T=0, .default_val=1, .unit=OSMO_TDEF_CUSTOM, .desc="zero" }, + + /* no desc */ + { .T=123, .default_val=1 }, + + {} // <-- important! last entry shall be zero +}; + +#define print_tdef_get(T, AS_UNIT) do { \ + unsigned long val = osmo_tdef_get(tdefs, T, AS_UNIT, 999); \ + printf("osmo_tdef_get(tdefs, %d, %s, 999)\t= %lu\n", T, osmo_tdef_unit_name(AS_UNIT), val); \ + } while (0) + +#define print_tdef_get_short(T, AS_UNIT) do { \ + unsigned long val = osmo_tdef_get(tdefs, T, AS_UNIT, 999); \ + printf("osmo_tdef_get(%d, %s)\t= %lu\n", T, osmo_tdef_unit_name(AS_UNIT), val); \ + } while (0) + +void print_tdef_info(unsigned int T) +{ + const struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, T); + if (!t) { + printf("T%d=NULL", T); + return; + } + printf("T%d=%lu%s", T, t->val, osmo_tdef_unit_name(t->unit)); + if (t->val != t->default_val) + printf("(def=%lu)", t->default_val); + printf("\n"); +} + +static void test_tdef_get() +{ + int i; + enum osmo_tdef_unit as_unit; + + printf("\n%s()\n", __func__); + + osmo_tdefs_reset(tdefs); // make all values the default + + for (i = 0; i < ARRAY_SIZE(tdefs)-1; i++) { + unsigned int T = tdefs[i].T; + print_tdef_info(T); + for (as_unit = OSMO_TDEF_S; as_unit <= OSMO_TDEF_CUSTOM; as_unit++) { + print_tdef_get_short(T, as_unit); + } + } +} + +static void test_tdef_get_nonexisting() +{ + printf("\n%s()\n", __func__); + + print_tdef_get(5, OSMO_TDEF_S); + print_tdef_get(5, OSMO_TDEF_MS); + print_tdef_get(5, OSMO_TDEF_M); + print_tdef_get(5, OSMO_TDEF_CUSTOM); +} + +static void test_tdef_set_and_get() +{ + struct osmo_tdef *t; + printf("\n%s()\n", __func__); + + t = osmo_tdef_get_entry(tdefs, 7); + printf("setting 7 = 42\n"); + t->val = 42; + print_tdef_info(7); + print_tdef_get_short(7, OSMO_TDEF_MS); + print_tdef_get_short(7, OSMO_TDEF_S); + print_tdef_get_short(7, OSMO_TDEF_M); + print_tdef_get_short(7, OSMO_TDEF_CUSTOM); + + printf("setting 7 = 420\n"); + t->val = 420; + print_tdef_info(7); + print_tdef_get_short(7, OSMO_TDEF_MS); + print_tdef_get_short(7, OSMO_TDEF_S); + print_tdef_get_short(7, OSMO_TDEF_M); + print_tdef_get_short(7, OSMO_TDEF_CUSTOM); + + printf("resetting\n"); + osmo_tdefs_reset(tdefs); + print_tdef_info(7); + print_tdef_get_short(7, OSMO_TDEF_S); +} + +enum test_tdef_fsm_states { + S_A = 0, + S_B, + S_C, + S_D, + S_G, + S_H, + S_I, + S_J, + S_K, + S_L, + S_M, + S_N, + S_O, + S_X, + S_Y, + S_Z, +}; + +static const struct osmo_tdef_state_timeout test_tdef_state_timeouts[32] = { + [S_A] = { .T = 1 }, + [S_B] = { .T = 2 }, + [S_C] = { .T = 3 }, + [S_D] = { .T = 4 }, + + [S_G] = { .T = 7 }, + [S_H] = { .T = 8 }, + [S_I] = { .T = 9 }, + [S_J] = { .T = 10 }, + + /* keep_timer: adopt whichever T was running before and continue the timeout. */ + [S_K] = { .keep_timer = true }, + /* S_F defines an undefined T, but should continue previous state's timeout. */ + [S_L] = { .T = 123, .keep_timer = true }, + + /* range */ + [S_M] = { .T = INT_MAX }, + [S_N] = { .T = INT_MIN }, + + /* T0 is not addressable from osmo_tdef_state_timeout, since it is indistinguishable from an unset entry. Even + * though a timeout value is set for T=0, the transition to state S_O will show "no timer configured". */ + [S_O] = { .T = 0 }, + + /* S_X undefined on purpose */ + /* S_Y defines a T that does not exist */ + [S_Y] = { .T = 666 }, + /* S_Z undefined on purpose */ +}; + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state test_tdef_fsm_states[] = { +#define DEF_STATE(NAME) \ + [S_##NAME] = { \ + .name = #NAME, \ + .out_state_mask = 0 \ + | S(S_A) \ + | S(S_B) \ + | S(S_C) \ + | S(S_D) \ + | S(S_G) \ + | S(S_H) \ + | S(S_I) \ + | S(S_J) \ + | S(S_K) \ + | S(S_L) \ + | S(S_M) \ + | S(S_N) \ + | S(S_O) \ + | S(S_X) \ + | S(S_Y) \ + | S(S_Z) \ + , \ + } + + DEF_STATE(A), + DEF_STATE(B), + DEF_STATE(C), + DEF_STATE(D), + + DEF_STATE(G), + DEF_STATE(H), + DEF_STATE(I), + DEF_STATE(J), + + DEF_STATE(K), + DEF_STATE(L), + + DEF_STATE(M), + DEF_STATE(N), + DEF_STATE(O), + + DEF_STATE(X), + DEF_STATE(Y), + /* Z: test not being allowed to transition to other states. */ + [S_Z] = { + .name = "Z", + .out_state_mask = 0 + | S(S_A) + , + }, +}; + +static const struct value_string test_tdef_fsm_event_names[] = { {} }; + +static struct osmo_fsm test_tdef_fsm = { + .name = "tdef_test", + .states = test_tdef_fsm_states, + .event_names = test_tdef_fsm_event_names, + .num_states = ARRAY_SIZE(test_tdef_fsm_states), + .log_subsys = DLGLOBAL, +}; + +const struct timeval fake_time_start_time = { 123, 456 }; + +#define fake_time_passes(secs, usecs) do \ +{ \ + struct timeval diff; \ + osmo_gettimeofday_override_add(secs, usecs); \ + osmo_clock_override_add(CLOCK_MONOTONIC, secs, usecs * 1000); \ + timersub(&osmo_gettimeofday_override_time, &fake_time_start_time, &diff); \ + printf("Total time passed: %ld.%06ld s\n", diff.tv_sec, diff.tv_usec); \ + osmo_timers_prepare(); \ + osmo_timers_update(); \ +} while (0) + +void fake_time_start() +{ + struct timespec *clock_override; + + osmo_gettimeofday_override_time = fake_time_start_time; + osmo_gettimeofday_override = true; + clock_override = osmo_clock_override_gettimespec(CLOCK_MONOTONIC); + OSMO_ASSERT(clock_override); + clock_override->tv_sec = fake_time_start_time.tv_sec; + clock_override->tv_nsec = fake_time_start_time.tv_usec * 1000; + osmo_clock_override_enable(CLOCK_MONOTONIC, true); + fake_time_passes(0, 0); +} + +static void print_fsm_state(struct osmo_fsm_inst *fi) +{ + struct timeval remaining; + printf("state=%s T=%d", osmo_fsm_inst_state_name(fi), fi->T); + + if (!osmo_timer_pending(&fi->timer)) { + printf(", no timeout\n"); + return; + } + + osmo_timer_remaining(&fi->timer, &osmo_gettimeofday_override_time, &remaining); + printf(", %lu.%06lu s remaining\n", remaining.tv_sec, remaining.tv_usec); +} + + +#define test_tdef_fsm_state_chg(NEXT_STATE) do { \ + const struct osmo_tdef_state_timeout *st = osmo_tdef_get_state_timeout(NEXT_STATE, \ + test_tdef_state_timeouts); \ + if (!st) { \ + printf(" --> %s (no timer configured for this state)\n", \ + osmo_fsm_state_name(&test_tdef_fsm, NEXT_STATE)); \ + } else { \ + struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, st->T); \ + int rc = osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, test_tdef_state_timeouts, tdefs, 999); \ + printf(" --> %s (configured as T%d%s %lu %s) rc=%d;\t", osmo_fsm_state_name(&test_tdef_fsm, \ + NEXT_STATE), \ + st->T, st->keep_timer ? "(keep_timer)" : "", \ + t? t->val : -1, t? osmo_tdef_unit_name(t->unit) : "-", \ + rc); \ + print_fsm_state(fi); \ + } \ + } while(0) + + + +static void test_tdef_state_timeout(bool test_range) +{ + struct osmo_fsm_inst *fi; + struct osmo_tdef *m = osmo_tdef_get_entry(tdefs, INT_MAX); + unsigned long m_secs; + printf("\n%s()\n", __func__); + + osmo_tdefs_reset(tdefs); + + fake_time_start(); + + fi = osmo_fsm_inst_alloc(&test_tdef_fsm, ctx, NULL, LOGL_DEBUG, __func__); + OSMO_ASSERT(fi); + print_fsm_state(fi); + + test_tdef_fsm_state_chg(S_A); + test_tdef_fsm_state_chg(S_B); + test_tdef_fsm_state_chg(S_C); + test_tdef_fsm_state_chg(S_D); + + test_tdef_fsm_state_chg(S_G); + test_tdef_fsm_state_chg(S_H); + test_tdef_fsm_state_chg(S_I); + test_tdef_fsm_state_chg(S_J); + + printf("- test keep_timer:\n"); + fake_time_passes(123, 45678); + print_fsm_state(fi); + test_tdef_fsm_state_chg(S_K); + test_tdef_fsm_state_chg(S_A); + fake_time_passes(23, 45678); + print_fsm_state(fi); + test_tdef_fsm_state_chg(S_K); + + test_tdef_fsm_state_chg(S_A); + fake_time_passes(23, 45678); + print_fsm_state(fi); + test_tdef_fsm_state_chg(S_L); + + printf("- test large T:\n"); + test_tdef_fsm_state_chg(S_M); + + printf("- test T<0:\n"); + test_tdef_fsm_state_chg(S_N); + + printf("- test T=0:\n"); + test_tdef_fsm_state_chg(S_O); + + printf("- test no timer:\n"); + test_tdef_fsm_state_chg(S_X); + + printf("- test undefined timer, using default_val arg of osmo_tdef_fsm_inst_state_chg(), here passed as 999:\n"); + test_tdef_fsm_state_chg(S_Y); + + /* the range of unsigned long is architecture dependent. This test can be invoked manually to see whether + * clamping the timeout values works, but the output will be of varying lengths depending on the system's + * unsigned long range, and would cause differences in expected output. */ + if (test_range) { + printf("- test range:\n"); + test_tdef_fsm_state_chg(S_M); + /* sweep through all the bits, shifting in 0xfffff.. from the right. */ + m_secs = 0; + do { + m_secs = (m_secs << 1) + 1; + switch (m_secs) { + case 0x7fff: + printf("--- int32_t max ---\n"); + break; + case 0xffff: + printf("--- uint32_t max ---\n"); + break; + case 0x7fffffff: + printf("--- int64_t max ---\n"); + break; + case 0xffffffff: + printf("--- uint64_t max ---\n"); + break; + default: + break; + } + + m->val = m_secs - 1; + test_tdef_fsm_state_chg(S_M); + m->val = m_secs; + test_tdef_fsm_state_chg(S_M); + m->val = m_secs + 1; + test_tdef_fsm_state_chg(S_M); + } while (m_secs < ULONG_MAX); + } + + printf("- test disallowed transition:\n"); + test_tdef_fsm_state_chg(S_Z); + test_tdef_fsm_state_chg(S_B); + test_tdef_fsm_state_chg(S_C); + test_tdef_fsm_state_chg(S_D); +} + +int main(int argc, char **argv) +{ + ctx = talloc_named_const(NULL, 0, "tdef_test.c"); + osmo_init_logging2(ctx, NULL); + + log_set_print_filename(osmo_stderr_target, 0); + log_set_print_category(osmo_stderr_target, 1); + log_set_use_color(osmo_stderr_target, 0); + + osmo_fsm_register(&test_tdef_fsm); + + test_tdef_get(); + test_tdef_get_nonexisting(); + test_tdef_set_and_get(); + /* Run range test iff any argument is passed on the cmdline. For the rationale, see the comment in + * test_tdef_state_timeout(). */ + test_tdef_state_timeout(argc > 1); + + return EXIT_SUCCESS; +} diff --git a/tests/tdef/tdef_test.ok b/tests/tdef/tdef_test.ok new file mode 100644 index 00000000..cf4b77f5 --- /dev/null +++ b/tests/tdef/tdef_test.ok @@ -0,0 +1,184 @@ + +test_tdef_get() +T1=100s +osmo_tdef_get(1, s) = 100 +osmo_tdef_get(1, ms) = 100000 +osmo_tdef_get(1, m) = 2 +osmo_tdef_get(1, custom-unit) = 100 +T2=100ms +osmo_tdef_get(2, s) = 1 +osmo_tdef_get(2, ms) = 100 +osmo_tdef_get(2, m) = 1 +osmo_tdef_get(2, custom-unit) = 100 +T3=100m +osmo_tdef_get(3, s) = 6000 +osmo_tdef_get(3, ms) = 6000000 +osmo_tdef_get(3, m) = 100 +osmo_tdef_get(3, custom-unit) = 100 +T4=100custom-unit +osmo_tdef_get(4, s) = 100 +osmo_tdef_get(4, ms) = 100 +osmo_tdef_get(4, m) = 100 +osmo_tdef_get(4, custom-unit) = 100 +T7=50s +osmo_tdef_get(7, s) = 50 +osmo_tdef_get(7, ms) = 50000 +osmo_tdef_get(7, m) = 1 +osmo_tdef_get(7, custom-unit) = 50 +T8=300s +osmo_tdef_get(8, s) = 300 +osmo_tdef_get(8, ms) = 300000 +osmo_tdef_get(8, m) = 5 +osmo_tdef_get(8, custom-unit) = 300 +T9=5m +osmo_tdef_get(9, s) = 300 +osmo_tdef_get(9, ms) = 300000 +osmo_tdef_get(9, m) = 5 +osmo_tdef_get(9, custom-unit) = 5 +T10=20m +osmo_tdef_get(10, s) = 1200 +osmo_tdef_get(10, ms) = 1200000 +osmo_tdef_get(10, m) = 20 +osmo_tdef_get(10, custom-unit) = 20 +T1000=2000ms +osmo_tdef_get(1000, s) = 2 +osmo_tdef_get(1000, ms) = 2000 +osmo_tdef_get(1000, m) = 1 +osmo_tdef_get(1000, custom-unit) = 2000 +T1001=60000ms +osmo_tdef_get(1001, s) = 60 +osmo_tdef_get(1001, ms) = 60000 +osmo_tdef_get(1001, m) = 1 +osmo_tdef_get(1001, custom-unit) = 60000 +T1002=307445734561825860m +osmo_tdef_get(1002, s) = 18446744073709551600 +osmo_tdef_get(1002, ms) = 18446744073709551615 +osmo_tdef_get(1002, m) = 307445734561825860 +osmo_tdef_get(1002, custom-unit) = 307445734561825860 +T1003=18446744073709551615m +osmo_tdef_get(1003, s) = 18446744073709551615 +osmo_tdef_get(1003, ms) = 18446744073709551615 +osmo_tdef_get(1003, m) = 18446744073709551615 +osmo_tdef_get(1003, custom-unit) = 18446744073709551615 +T1004=1ms +osmo_tdef_get(1004, s) = 1 +osmo_tdef_get(1004, ms) = 1 +osmo_tdef_get(1004, m) = 1 +osmo_tdef_get(1004, custom-unit) = 1 +T1005=0ms +osmo_tdef_get(1005, s) = 0 +osmo_tdef_get(1005, ms) = 0 +osmo_tdef_get(1005, m) = 0 +osmo_tdef_get(1005, custom-unit) = 0 +T1006=0s +osmo_tdef_get(1006, s) = 0 +osmo_tdef_get(1006, ms) = 0 +osmo_tdef_get(1006, m) = 0 +osmo_tdef_get(1006, custom-unit) = 0 +T1007=0m +osmo_tdef_get(1007, s) = 0 +osmo_tdef_get(1007, ms) = 0 +osmo_tdef_get(1007, m) = 0 +osmo_tdef_get(1007, custom-unit) = 0 +T1008=0custom-unit +osmo_tdef_get(1008, s) = 0 +osmo_tdef_get(1008, ms) = 0 +osmo_tdef_get(1008, m) = 0 +osmo_tdef_get(1008, custom-unit) = 0 +T2147483647=18446744073709551615s +osmo_tdef_get(2147483647, s) = 18446744073709551615 +osmo_tdef_get(2147483647, ms) = 18446744073709551615 +osmo_tdef_get(2147483647, m) = 307445734561825861 +osmo_tdef_get(2147483647, custom-unit) = 18446744073709551615 +T2147483646=18446744073709551614s +osmo_tdef_get(2147483646, s) = 18446744073709551614 +osmo_tdef_get(2147483646, ms) = 18446744073709551615 +osmo_tdef_get(2147483646, m) = 307445734561825861 +osmo_tdef_get(2147483646, custom-unit) = 18446744073709551614 +T2147483645=9223372036854775807s +osmo_tdef_get(2147483645, s) = 9223372036854775807 +osmo_tdef_get(2147483645, ms) = 18446744073709551615 +osmo_tdef_get(2147483645, m) = 153722867280912931 +osmo_tdef_get(2147483645, custom-unit) = 9223372036854775807 +T2147483644=18446744073709551615m +osmo_tdef_get(2147483644, s) = 18446744073709551615 +osmo_tdef_get(2147483644, ms) = 18446744073709551615 +osmo_tdef_get(2147483644, m) = 18446744073709551615 +osmo_tdef_get(2147483644, custom-unit) = 18446744073709551615 +T-2147483648=18446744073709551615s +osmo_tdef_get(-2147483648, s) = 18446744073709551615 +osmo_tdef_get(-2147483648, ms) = 18446744073709551615 +osmo_tdef_get(-2147483648, m) = 307445734561825861 +osmo_tdef_get(-2147483648, custom-unit) = 18446744073709551615 +T0=1custom-unit +osmo_tdef_get(0, s) = 1 +osmo_tdef_get(0, ms) = 1 +osmo_tdef_get(0, m) = 1 +osmo_tdef_get(0, custom-unit) = 1 +T123=1s +osmo_tdef_get(123, s) = 1 +osmo_tdef_get(123, ms) = 1000 +osmo_tdef_get(123, m) = 1 +osmo_tdef_get(123, custom-unit) = 1 + +test_tdef_get_nonexisting() +osmo_tdef_get(tdefs, 5, s, 999) = 999 +osmo_tdef_get(tdefs, 5, ms, 999) = 999 +osmo_tdef_get(tdefs, 5, m, 999) = 999 +osmo_tdef_get(tdefs, 5, custom-unit, 999) = 999 + +test_tdef_set_and_get() +setting 7 = 42 +T7=42s(def=50) +osmo_tdef_get(7, ms) = 42000 +osmo_tdef_get(7, s) = 42 +osmo_tdef_get(7, m) = 1 +osmo_tdef_get(7, custom-unit) = 42 +setting 7 = 420 +T7=420s(def=50) +osmo_tdef_get(7, ms) = 420000 +osmo_tdef_get(7, s) = 420 +osmo_tdef_get(7, m) = 7 +osmo_tdef_get(7, custom-unit) = 420 +resetting +T7=50s +osmo_tdef_get(7, s) = 50 + +test_tdef_state_timeout() +Total time passed: 0.000000 s +state=A T=0, no timeout + --> A (configured as T1 100 s) rc=0; state=A T=1, 100.000000 s remaining + --> B (configured as T2 100 ms) rc=0; state=B T=2, 1.000000 s remaining + --> C (configured as T3 100 m) rc=0; state=C T=3, 6000.000000 s remaining + --> D (configured as T4 100 custom-unit) rc=0; state=D T=4, 100.000000 s remaining + --> G (configured as T7 50 s) rc=0; state=G T=7, 50.000000 s remaining + --> H (configured as T8 300 s) rc=0; state=H T=8, 300.000000 s remaining + --> I (configured as T9 5 m) rc=0; state=I T=9, 300.000000 s remaining + --> J (configured as T10 20 m) rc=0; state=J T=10, 1200.000000 s remaining +- test keep_timer: +Total time passed: 123.045678 s +state=J T=10, 1076.954322 s remaining + --> K (configured as T0(keep_timer) 1 custom-unit) rc=0; state=K T=10, 1076.954322 s remaining + --> A (configured as T1 100 s) rc=0; state=A T=1, 100.000000 s remaining +Total time passed: 146.091356 s +state=A T=1, 76.954322 s remaining + --> K (configured as T0(keep_timer) 1 custom-unit) rc=0; state=K T=1, 76.954322 s remaining + --> A (configured as T1 100 s) rc=0; state=A T=1, 100.000000 s remaining +Total time passed: 169.137034 s +state=A T=1, 76.954322 s remaining + --> L (configured as T123(keep_timer) 1 s) rc=0; state=L T=123, 76.954322 s remaining +- test large T: + --> M (configured as T2147483647 18446744073709551615 s) rc=0; state=M T=2147483647, 2147483647.000000 s remaining +- test T<0: + --> N (configured as T-2147483648 18446744073709551615 s) rc=0; state=N T=-2147483648, 2147483647.000000 s remaining +- test T=0: + --> O (no timer configured for this state) +- test no timer: + --> X (no timer configured for this state) +- test undefined timer, using default_val arg of osmo_tdef_fsm_inst_state_chg(), here passed as 999: + --> Y (configured as T666 18446744073709551615 -) rc=0; state=Y T=666, 999.000000 s remaining +- test disallowed transition: + --> Z (no timer configured for this state) + --> B (configured as T2 100 ms) rc=0; state=B T=2, 1.000000 s remaining + --> C (configured as T3 100 m) rc=0; state=C T=3, 6000.000000 s remaining + --> D (configured as T4 100 custom-unit) rc=0; state=D T=4, 100.000000 s remaining diff --git a/tests/tdef/tdef_vty_test_config_root.c b/tests/tdef/tdef_vty_test_config_root.c new file mode 100644 index 00000000..138ac00a --- /dev/null +++ b/tests/tdef/tdef_vty_test_config_root.c @@ -0,0 +1,292 @@ +/* Test implementation for osmo_tdef VTY configuration API. */ +/* + * (C) 2019 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#include "config.h" + +/* ------------------- HERE IS THE INTERESTING TDEF RELEVANT PART ------------------- */ + +/* This example keeps several separate timer groups and offers 'timer' VTY commands at the root of the config node. See + * the tdef_vty_test_config_root.vty transcript test. + */ + +static struct osmo_tdef tdefs_test[] = { + { .T=1, .default_val=100, .desc="Testing a hundred seconds" }, // default is .unit=OSMO_TDEF_S == 0 + { .T=2, .default_val=100, .unit=OSMO_TDEF_MS, .desc="Testing a hundred milliseconds" }, + { .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="Testing a hundred minutes" }, + { .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="Testing a hundred potatoes" }, + { .T=INT_MAX, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="Very large" }, + { .T=-23, .default_val=-15, .desc="Negative T number" }, + {} // <-- important! last entry shall be zero +}; + +static struct osmo_tdef tdefs_tea[] = { + { .T=1, .default_val=50, .desc="Water Boiling Timeout" }, + { .T=2, .default_val=300, .desc="Tea brewing" }, + { .T=3, .default_val=5, .unit=OSMO_TDEF_M, .desc="Let tea cool down before drinking" }, + { .T=4, .default_val=20, .unit=OSMO_TDEF_M, .desc="Forgot to drink tea while it's warm" }, + {} +}; + +static struct osmo_tdef tdefs_software[] = { + { .T=1, .default_val=30, .unit=OSMO_TDEF_M, .desc="Write code" }, + { .T=2, .default_val=20, .unit=OSMO_TDEF_MS, .desc="Hit segfault" }, + { .T=3, .default_val=480, .unit=OSMO_TDEF_M, .desc="Fix bugs" }, + {} +}; + +static struct osmo_tdef_group tdef_groups[] = { + { + .name = "tea", + .desc = "Tea time", + .tdefs = tdefs_tea, + }, + { + .name = "test", + .desc = "Test timers", + .tdefs = tdefs_test, + }, + { + .name = "software", + .desc = "Typical software development cycle", + .tdefs = tdefs_software, + }, + {} +}; + +enum tdef_vty_test_nodes { + TIMER_NODE = _LAST_OSMOVTY_NODE + 1, +}; + +/* This example puts 'timer' configuration commands directly at the root of the CONFIG_NODE. + * This TIMER_NODE is merely needed as a hook for the vty_write() command, but becomes an empty node in the VTY docs. + * It is possible to cheat around needing this if you choose to config_write_timer() in another root nodes' write cb. + * Another example using a 'network' subnode is \ref tdef_vty_test_config_subnode.c */ +static struct cmd_node timer_node = { + TIMER_NODE, + "%s(config-timer)# ", + 1, +}; + +static int config_write_timer(struct vty *vty) +{ + osmo_tdef_vty_groups_write(vty, ""); + return CMD_SUCCESS; +} + +static void timer_init_vty() +{ + /* Again, this is merely to get a vty write hook, see above. */ + install_node(&timer_node, config_write_timer); + + osmo_tdef_vty_groups_init(CONFIG_NODE, tdef_groups); +} + +/* ------------------- THE REST is just boilerplate osmo main() ------------------- */ + +void *root_ctx = NULL; + +static void print_help() +{ + printf( "options:\n" + " -h --help this text\n" + " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n" + " -D --daemonize For the process into a background daemon\n" + " -c --config-file Specify the filename of the config file\n" + " -s --disable-color Don't use colors in stderr log output\n" + " -T --timestamp Prefix every log line with a timestamp\n" + " -V --version Print version information and exit\n" + " -e --log-level Set a global log-level\n" + ); +} + +static struct { + const char *config_file; + int daemonize; +} cmdline_config = {}; + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "debug", 1, 0, 'd' }, + { "daemonize", 0, 0, 'D' }, + { "config-file", 1, 0, 'c' }, + { "disable-color", 0, 0, 's' }, + { "timestamp", 0, 0, 'T' }, + { "version", 0, 0, 'V' }, + { "log-level", 1, 0, 'e' }, + {} + }; + + c = getopt_long(argc, argv, "hc:d:Dc:sTVe:", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'd': + log_parse_category_mask(osmo_stderr_target, optarg); + break; + case 'D': + cmdline_config.daemonize = 1; + break; + case 'c': + cmdline_config.config_file = optarg; + break; + case 'T': + log_set_print_timestamp(osmo_stderr_target, 1); + break; + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 'V': + print_version(1); + exit(0); + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + exit(-1); + } + } +} + +static int quit = 0; + +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + case SIGTERM: + quit++; + break; + case SIGABRT: + osmo_generate_backtrace(); + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(root_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +static struct vty_app_info vty_info = { + .name = "tdef_vty_test", + .version = PACKAGE_VERSION, +}; + +static const struct log_info_cat default_categories[] = {}; + +const struct log_info log_info = { + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +int main(int argc, char **argv) +{ + int rc; + + root_ctx = talloc_named_const(NULL, 0, "tdef_vty_test"); + + osmo_init_logging2(root_ctx, &log_info); + + vty_info.tall_ctx = root_ctx; + vty_init(&vty_info); + osmo_talloc_vty_add_cmds(); + + timer_init_vty(); /* <---- the only tdef relevant init */ + + handle_options(argc, argv); + + if (cmdline_config.config_file) { + rc = vty_read_config_file(cmdline_config.config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", cmdline_config.config_file); + return 1; + } + } + + rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042); + if (rc < 0) + return 2; + + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + if (cmdline_config.daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + return 6; + } + } + + while (!quit) { + log_reset_context(); + osmo_select_main(0); + } + + talloc_free(root_ctx); + talloc_free(tall_vty_ctx); + + return 0; +} diff --git a/tests/tdef/tdef_vty_test_config_root.vty b/tests/tdef/tdef_vty_test_config_root.vty new file mode 100644 index 00000000..12876a68 --- /dev/null +++ b/tests/tdef/tdef_vty_test_config_root.vty @@ -0,0 +1,292 @@ +tdef_vty_test> list +... + show timer [(tea|test|software)] [TNNNN] +... + +tdef_vty_test> show timer ? + [tea] Tea time + [test] Test timers + [software] Typical software development cycle + +tdef_vty_test> show timer test ? + [TNNNN] T-number, optionally preceded by 't' or 'T'. + +tdef_vty_test> show timer +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 5 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) +test: T1 = 100 s Testing a hundred seconds (default: 100 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +test: T3 = 100 m Testing a hundred minutes (default: 100 m) +test: T4 = 100 Testing a hundred potatoes (default: 100) +test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) +software: T1 = 30 m Write code (default: 30 m) +software: T2 = 20 ms Hit segfault (default: 20 ms) +software: T3 = 480 m Fix bugs (default: 480 m) + +tdef_vty_test> enable +tdef_vty_test# show timer +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 5 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) +test: T1 = 100 s Testing a hundred seconds (default: 100 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +test: T3 = 100 m Testing a hundred minutes (default: 100 m) +test: T4 = 100 Testing a hundred potatoes (default: 100) +test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) +software: T1 = 30 m Write code (default: 30 m) +software: T2 = 20 ms Hit segfault (default: 20 ms) +software: T3 = 480 m Fix bugs (default: 480 m) + +tdef_vty_test# configure terminal + +tdef_vty_test(config)# show running-config +... !timer + +tdef_vty_test(config)# list +... + timer [(tea|test|software)] [TNNNN] [(<0-2147483647>|default)] +... + +tdef_vty_test(config)# timer ? + [tea] Tea time + [test] Test timers + [software] Typical software development cycle + +tdef_vty_test(config)# timer sof T123 ? + [<0-2147483647>] New timer value + [default] Set to default timer value + +tdef_vty_test(config)# timer sof T123 ? + [<0-2147483647>] New timer value + [default] Set to default timer value + +tdef_vty_test(config)# timer test ? + [TNNNN] T-number, optionally preceded by 't' or 'T'. + +tdef_vty_test(config)# timer test t2 ? + [<0-2147483647>] New timer value + [default] Set to default timer value + +tdef_vty_test(config)# do show timer +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 5 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) +test: T1 = 100 s Testing a hundred seconds (default: 100 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +test: T3 = 100 m Testing a hundred minutes (default: 100 m) +test: T4 = 100 Testing a hundred potatoes (default: 100) +test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) +software: T1 = 30 m Write code (default: 30 m) +software: T2 = 20 ms Hit segfault (default: 20 ms) +software: T3 = 480 m Fix bugs (default: 480 m) + +tdef_vty_test(config)# do show timer tea +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 5 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) + +tdef_vty_test(config)# do show timer tea 2 +tea: T2 = 300 s Tea brewing (default: 300 s) + +tdef_vty_test(config)# do show timer tea t2 +tea: T2 = 300 s Tea brewing (default: 300 s) + +tdef_vty_test(config)# do show timer tea T2 +tea: T2 = 300 s Tea brewing (default: 300 s) + +tdef_vty_test(config)# do show timer tea T5 +% No such timer: T5 + +tdef_vty_test(config)# do show timer tea T0 +% No such timer: T0 + +tdef_vty_test(config)# do show timer tea T-123 +% No such timer: T-123 + +tdef_vty_test(config)# do show timer t +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 5 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) +test: T1 = 100 s Testing a hundred seconds (default: 100 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +test: T3 = 100 m Testing a hundred minutes (default: 100 m) +test: T4 = 100 Testing a hundred potatoes (default: 100) +test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test(config)# do show timer te +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 5 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) +test: T1 = 100 s Testing a hundred seconds (default: 100 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +test: T3 = 100 m Testing a hundred minutes (default: 100 m) +test: T4 = 100 Testing a hundred potatoes (default: 100) +test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test(config)# do show timer te T2 +tea: T2 = 300 s Tea brewing (default: 300 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) + + +tdef_vty_test(config)# timer tea 3 30 +tdef_vty_test(config)# timer tea T3 +tea: T3 = 30 m Let tea cool down before drinking (default: 5 m) + +tdef_vty_test(config)# timer tea t3 31 +tdef_vty_test(config)# timer tea T3 +tea: T3 = 31 m Let tea cool down before drinking (default: 5 m) + +tdef_vty_test(config)# timer tea T3 32 +tdef_vty_test(config)# timer tea T3 +tea: T3 = 32 m Let tea cool down before drinking (default: 5 m) + +tdef_vty_test(config)# timer tea T-123 99 +% No such timer: T-123 + +tdef_vty_test(config)# timer tea T0 0 +% No such timer: T0 + +tdef_vty_test(config)# timer tea T123 default +% No such timer: T123 + +tdef_vty_test(config)# timer tea +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 32 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) + +tdef_vty_test(config)# timer t +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 32 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) +test: T1 = 100 s Testing a hundred seconds (default: 100 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +test: T3 = 100 m Testing a hundred minutes (default: 100 m) +test: T4 = 100 Testing a hundred potatoes (default: 100) +test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test(config)# timer te T2 +tea: T2 = 300 s Tea brewing (default: 300 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) + +tdef_vty_test(config)# timer test T2 100 + +tdef_vty_test(config)# timer tes T2 100 +% Error: no timers found + +tdef_vty_test(config)# timer te T2 100 +% Error: no timers found + + +tdef_vty_test(config)# do show timer software +software: T1 = 30 m Write code (default: 30 m) +software: T2 = 20 ms Hit segfault (default: 20 ms) +software: T3 = 480 m Fix bugs (default: 480 m) + +tdef_vty_test(config)# do show timer software 1 +software: T1 = 30 m Write code (default: 30 m) + +tdef_vty_test(config)# do show timer software t1 +software: T1 = 30 m Write code (default: 30 m) + +tdef_vty_test(config)# do show timer software T1 +software: T1 = 30 m Write code (default: 30 m) + +tdef_vty_test(config)# do show timer software T99 +% No such timer: T99 + +tdef_vty_test(config)# do show timer software T-123123 +% No such timer: T-123123 + +tdef_vty_test(config)# do show timer software T0 +% No such timer: T0 + +tdef_vty_test(config)# timer software 1 11 +tdef_vty_test(config)# timer software T1 +software: T1 = 11 m Write code (default: 30 m) + +tdef_vty_test(config)# timer software t1 12 +tdef_vty_test(config)# timer software T1 +software: T1 = 12 m Write code (default: 30 m) + +tdef_vty_test(config)# timer software T1 13 +tdef_vty_test(config)# timer software T2 0 +tdef_vty_test(config)# timer software +software: T1 = 13 m Write code (default: 30 m) +software: T2 = 0 ms Hit segfault (default: 20 ms) +software: T3 = 480 m Fix bugs (default: 480 m) + +tdef_vty_test(config)# timer softw +software: T1 = 13 m Write code (default: 30 m) +software: T2 = 0 ms Hit segfault (default: 20 ms) +software: T3 = 480 m Fix bugs (default: 480 m) + +tdef_vty_test(config)# timer softw T3 +software: T3 = 480 m Fix bugs (default: 480 m) + +tdef_vty_test(config)# timer softw T3 23 +% Error: no timers found + +tdef_vty_test(config)# timer +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 32 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) +test: T1 = 100 s Testing a hundred seconds (default: 100 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +test: T3 = 100 m Testing a hundred minutes (default: 100 m) +test: T4 = 100 Testing a hundred potatoes (default: 100) +test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) +software: T1 = 13 m Write code (default: 30 m) +software: T2 = 0 ms Hit segfault (default: 20 ms) +software: T3 = 480 m Fix bugs (default: 480 m) + +tdef_vty_test(config)# do show timer +tea: T1 = 50 s Water Boiling Timeout (default: 50 s) +tea: T2 = 300 s Tea brewing (default: 300 s) +tea: T3 = 32 m Let tea cool down before drinking (default: 5 m) +tea: T4 = 20 m Forgot to drink tea while it's warm (default: 20 m) +test: T1 = 100 s Testing a hundred seconds (default: 100 s) +test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +test: T3 = 100 m Testing a hundred minutes (default: 100 m) +test: T4 = 100 Testing a hundred potatoes (default: 100) +test: T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +test: T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) +software: T1 = 13 m Write code (default: 30 m) +software: T2 = 0 ms Hit segfault (default: 20 ms) +software: T3 = 480 m Fix bugs (default: 480 m) + +tdef_vty_test(config)# show running-config +... !timer +timer tea T3 32 +timer software T1 13 +timer software T2 0 +... !timer + +tdef_vty_test(config)# timer tea T3 default +tdef_vty_test(config)# timer software T1 default +tdef_vty_test(config)# show running-config +... !timer +timer software T2 0 +... !timer + +tdef_vty_test(config)# timer softw 2 default +% Error: no timers found +tdef_vty_test(config)# timer software 2 default +tdef_vty_test(config)# show running-config +... !timer diff --git a/tests/tdef/tdef_vty_test_config_subnode.c b/tests/tdef/tdef_vty_test_config_subnode.c new file mode 100644 index 00000000..c371c8d6 --- /dev/null +++ b/tests/tdef/tdef_vty_test_config_subnode.c @@ -0,0 +1,288 @@ +/* Test implementation for osmo_tdef VTY configuration API. */ +/* + * (C) 2019 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#include "config.h" + +/* ------------------- HERE IS THE INTERESTING TDEF RELEVANT PART ------------------- */ + +/* This example keeps a single global timer group and offers a custom 'timer' VTY command in a 'network' subnode below + * the CONFIG_NODE. + * the tdef_vty_test_config_subnode.vty transcript test. + */ + +static struct osmo_tdef global_tdefs[] = { + { .T=1, .default_val=100, .desc="Testing a hundred seconds" }, // default is .unit=OSMO_TDEF_S == 0 + { .T=2, .default_val=100, .unit=OSMO_TDEF_MS, .desc="Testing a hundred milliseconds" }, + { .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="Testing a hundred minutes" }, + { .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="Testing a hundred potatoes" }, + { .T=INT_MAX, .default_val=ULONG_MAX, .unit=OSMO_TDEF_M, .desc="Very large" }, + { .T=-23, .default_val=-15, .desc="Negative T number" }, + {} // <-- important! last entry shall be zero +}; + +enum tdef_vty_test_nodes { + GSMNET_NODE = _LAST_OSMOVTY_NODE + 1, +}; + +/* This example offers 'timer T123' commands within an "unrelated" already existing subnode. */ +static struct cmd_node gsmnet_node = { + GSMNET_NODE, + "%s(config-net)# ", + 1, +}; + +DEFUN(show_timer, show_timer_cmd, + "show timer " OSMO_TDEF_VTY_ARG_T_OPTIONAL, + SHOW_STR "Show timers\n" + OSMO_TDEF_VTY_DOC_T) +{ + const char *T_arg = argc > 0 ? argv[0] : NULL; + return osmo_tdef_vty_show_cmd(vty, global_tdefs, T_arg, NULL); +} + +DEFUN(cfg_net_timer, cfg_net_timer_cmd, + "timer " OSMO_TDEF_VTY_ARG_SET_OPTIONAL, + "Configure or show timers\n" + OSMO_TDEF_VTY_DOC_SET) +{ + /* If any arguments are missing, redirect to 'show' */ + if (argc < 2) + return show_timer(self, vty, argc, argv); + return osmo_tdef_vty_set_cmd(vty, global_tdefs, argv); +} + +DEFUN(cfg_net, cfg_net_cmd, + "network", "Enter network node\n") +{ + vty->node = GSMNET_NODE; + return CMD_SUCCESS; +} + +static int config_write_gsmnet(struct vty *vty) +{ + vty_out(vty, "net%s", VTY_NEWLINE); + /* usually, here would be the output of any other 'net' config items... */ + + osmo_tdef_vty_write(vty, global_tdefs, " timer "); + return CMD_SUCCESS; +} + +static void gsmnet_init_vty() +{ + install_node(&gsmnet_node, config_write_gsmnet); + install_element(CONFIG_NODE, &cfg_net_cmd); + + osmo_tdefs_reset(global_tdefs); + install_element_ve(&show_timer_cmd); + install_element(GSMNET_NODE, &cfg_net_timer_cmd); +} + +/* ------------------- THE REST is just boilerplate osmo main() ------------------- */ + +void *root_ctx = NULL; + +static void print_help() +{ + printf( "options:\n" + " -h --help this text\n" + " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n" + " -D --daemonize For the process into a background daemon\n" + " -c --config-file Specify the filename of the config file\n" + " -s --disable-color Don't use colors in stderr log output\n" + " -T --timestamp Prefix every log line with a timestamp\n" + " -V --version Print version information and exit\n" + " -e --log-level Set a global log-level\n" + ); +} + +static struct { + const char *config_file; + int daemonize; +} cmdline_config = {}; + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "debug", 1, 0, 'd' }, + { "daemonize", 0, 0, 'D' }, + { "config-file", 1, 0, 'c' }, + { "disable-color", 0, 0, 's' }, + { "timestamp", 0, 0, 'T' }, + { "version", 0, 0, 'V' }, + { "log-level", 1, 0, 'e' }, + {} + }; + + c = getopt_long(argc, argv, "hc:d:Dc:sTVe:", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'd': + log_parse_category_mask(osmo_stderr_target, optarg); + break; + case 'D': + cmdline_config.daemonize = 1; + break; + case 'c': + cmdline_config.config_file = optarg; + break; + case 'T': + log_set_print_timestamp(osmo_stderr_target, 1); + break; + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 'V': + print_version(1); + exit(0); + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + exit(-1); + } + } +} + +static int quit = 0; + +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + case SIGTERM: + quit++; + break; + case SIGABRT: + osmo_generate_backtrace(); + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(root_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +static struct vty_app_info vty_info = { + .name = "tdef_vty_test", + .version = PACKAGE_VERSION, +}; + +static const struct log_info_cat default_categories[] = {}; + +const struct log_info log_info = { + .cat = default_categories, + .num_cat = ARRAY_SIZE(default_categories), +}; + +int main(int argc, char **argv) +{ + int rc; + + root_ctx = talloc_named_const(NULL, 0, "tdef_vty_test"); + + osmo_init_logging2(root_ctx, &log_info); + + vty_info.tall_ctx = root_ctx; + vty_init(&vty_info); + osmo_talloc_vty_add_cmds(); + + gsmnet_init_vty(); /* <--- relevant init for this example */ + + handle_options(argc, argv); + + if (cmdline_config.config_file) { + rc = vty_read_config_file(cmdline_config.config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", cmdline_config.config_file); + return 1; + } + } + + rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042); + if (rc < 0) + return 2; + + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + if (cmdline_config.daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + return 6; + } + } + + while (!quit) { + log_reset_context(); + osmo_select_main(0); + } + + talloc_free(root_ctx); + talloc_free(tall_vty_ctx); + + return 0; +} diff --git a/tests/tdef/tdef_vty_test_config_subnode.vty b/tests/tdef/tdef_vty_test_config_subnode.vty new file mode 100644 index 00000000..6cfd3bf3 --- /dev/null +++ b/tests/tdef/tdef_vty_test_config_subnode.vty @@ -0,0 +1,107 @@ +tdef_vty_test> list +... !timer + show timer [TNNNN] +... !timer + +tdef_vty_test> show timer ? + [TNNNN] T-number, optionally preceded by 't' or 'T'. + +tdef_vty_test> show timer +T1 = 100 s Testing a hundred seconds (default: 100 s) +T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +T3 = 100 m Testing a hundred minutes (default: 100 m) +T4 = 100 Testing a hundred potatoes (default: 100) +T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test> enable +tdef_vty_test# show timer +T1 = 100 s Testing a hundred seconds (default: 100 s) +T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +T3 = 100 m Testing a hundred minutes (default: 100 m) +T4 = 100 Testing a hundred potatoes (default: 100) +T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test# configure terminal +tdef_vty_test(config)# show running-config +... !timer + +tdef_vty_test(config)# network + +tdef_vty_test(config-net)# do show timer +T1 = 100 s Testing a hundred seconds (default: 100 s) +T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +T3 = 100 m Testing a hundred minutes (default: 100 m) +T4 = 100 Testing a hundred potatoes (default: 100) +T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +T-23 = 18446744073709551601 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test(config-net)# do show timer T3 +T3 = 100 m Testing a hundred minutes (default: 100 m) +tdef_vty_test(config-net)# do show timer 3 +T3 = 100 m Testing a hundred minutes (default: 100 m) +tdef_vty_test(config-net)# do show timer t3 +T3 = 100 m Testing a hundred minutes (default: 100 m) + +tdef_vty_test(config-net)# timer T1 5 +tdef_vty_test(config-net)# timer T1 +T1 = 5 s Testing a hundred seconds (default: 100 s) + +tdef_vty_test(config-net)# timer t1 678 +tdef_vty_test(config-net)# timer T1 +T1 = 678 s Testing a hundred seconds (default: 100 s) + +tdef_vty_test(config-net)# timer 1 9012345 +tdef_vty_test(config-net)# timer T1 +T1 = 9012345 s Testing a hundred seconds (default: 100 s) + +tdef_vty_test(config-net)# do show timer T666 +% No such timer: T666 +tdef_vty_test(config-net)# do show timer t666 +% No such timer: T666 +tdef_vty_test(config-net)# do show timer 666 +% No such timer: T666 + +tdef_vty_test(config-net)# timer T666 +% No such timer: T666 +tdef_vty_test(config-net)# timer t666 +% No such timer: T666 +tdef_vty_test(config-net)# timer 666 +% No such timer: T666 + +tdef_vty_test(config-net)# timer T666 5 +% No such timer: T666 + +tdef_vty_test(config-net)# timer T-23 42 +tdef_vty_test(config-net)# timer T-23 +T-23 = 42 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test(config-net)# timer t-23 43 +tdef_vty_test(config-net)# timer T-23 +T-23 = 43 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test(config-net)# timer -23 44 +tdef_vty_test(config-net)# timer T-23 +T-23 = 44 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test(config-net)# do show timer +T1 = 9012345 s Testing a hundred seconds (default: 100 s) +T2 = 100 ms Testing a hundred milliseconds (default: 100 ms) +T3 = 100 m Testing a hundred minutes (default: 100 m) +T4 = 100 Testing a hundred potatoes (default: 100) +T2147483647 = 18446744073709551615 m Very large (default: 18446744073709551615 m) +T-23 = 44 s Negative T number (default: 18446744073709551601 s) + +tdef_vty_test(config-net)# show running-config +... !timer +net + timer T1 9012345 + timer T-23 44 +... !timer + +tdef_vty_test(config-net)# timer T1 default +tdef_vty_test(config-net)# timer T-23 default + +tdef_vty_test(config-net)# show running-config +... !timer diff --git a/tests/tdef/tdef_vty_test_dynamic.c b/tests/tdef/tdef_vty_test_dynamic.c new file mode 100644 index 00000000..20dae535 --- /dev/null +++ b/tests/tdef/tdef_vty_test_dynamic.c @@ -0,0 +1,362 @@ +/* Test implementation for osmo_tdef VTY configuration API. */ +/* + * (C) 2019 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#include "config.h" + +void *root_ctx = NULL; + +/* ------------------- HERE IS THE INTERESTING TDEF RELEVANT PART ------------------- */ + +/* This example keeps a separate list of timers for each instance of a dynamically allocated instance of a VTY node, + * for example of keeping separate timers for each BTS in a BSC. + */ + +static const struct osmo_tdef bts_default_tdefs[] = { + { .T=1111, .default_val=2, .desc="Dynamic Duo" }, + { .T=2222, .default_val=1, .desc="BATMAN" }, + { .T=3333, .default_val=12, .desc="Dadadadadadadadadadadada" }, + { .T=4444, .default_val=500, .unit=OSMO_TDEF_MS, .desc="POW!" }, + {} +}; + + +/* Boilerplate dynamic VTY node ... */ + +enum tdef_vty_test_nodes { + MEMBER_NODE = _LAST_OSMOVTY_NODE + 1, +}; + +static struct cmd_node member_node = { + MEMBER_NODE, + "%s(config-member)# ", + 1, +}; + +struct member { + struct llist_head entry; + char name[23]; + struct osmo_tdef *tdefs; +}; + +LLIST_HEAD(all_members); + +struct member *member_alloc(const char *name) +{ + struct member *m = talloc_zero(root_ctx, struct member); + osmo_strlcpy(m->name, name, sizeof(m->name)); + + /* DYNAMIC TDEF COPIES */ + m->tdefs = (struct osmo_tdef*)talloc_size(m, sizeof(bts_default_tdefs)); + memcpy((char*)m->tdefs, (char*)&bts_default_tdefs, sizeof(bts_default_tdefs)); + osmo_tdefs_reset(m->tdefs); + + llist_add_tail(&m->entry, &all_members); + return m; +} + +struct member *member_find(const char *name) +{ + struct member *m; + llist_for_each_entry(m, &all_members, entry) { + if (!strcmp(m->name, name)) + return m; + } + return NULL; +} + +DEFUN(cfg_member, cfg_member_cmd, + "member NAME", + "Enter member node\n" "Existing or new member node name\n") +{ + const char *name = argv[0]; + struct member *m = member_find(name); + if (!m) + m = member_alloc(name); + vty->index = m; + vty->node = MEMBER_NODE; + return CMD_SUCCESS; +} + + +/* TDEF SPECIFIC VTY */ + +static bool startswith(const char *str, const char *startswith_str) +{ + if (!startswith_str) + return true; + if (!str) + return false; + return strncmp(str, startswith_str, strlen(startswith_str)) == 0; +} + +DEFUN(show_timer, show_member_timer_cmd, + "show member-timer [NAME] " OSMO_TDEF_VTY_ARG_T_OPTIONAL, + SHOW_STR "Show timers for a specific member" "member name\n" + OSMO_TDEF_VTY_DOC_T) +{ + const char *name = argc > 0 ? argv[0] : NULL; + struct member *m; + const char *T_arg = argc > 1 ? argv[1] : NULL; + int shown = 0; + + llist_for_each_entry(m, &all_members, entry) { + if (!name || startswith(m->name, name)) { + osmo_tdef_vty_show_cmd(vty, m->tdefs, T_arg, "%11s: ", m->name); + shown ++; + } + } + if (!shown) { + vty_out(vty, "%% No such member: %s%s", name ? : "(none)", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(cfg_member_timer, cfg_member_timer_cmd, + "timer " OSMO_TDEF_VTY_ARG_SET_OPTIONAL, + "Configure or show timers for this member\n" + OSMO_TDEF_VTY_DOC_SET) +{ + struct member *m = vty->index; + + if (!m || !m->tdefs) { + vty_out(vty, "%% No timers here%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* If any arguments are missing, redirect to 'show' */ + if (argc < 2) { + const char *T_arg = argc > 0 ? argv[0] : NULL; + return osmo_tdef_vty_show_cmd(vty, m->tdefs, T_arg, "%11s: ", m->name); + } + + return osmo_tdef_vty_set_cmd(vty, m->tdefs, argv); +} + +static int config_write_member(struct vty *vty) +{ + struct member *m; + llist_for_each_entry(m, &all_members, entry) { + vty_out(vty, "member %s%s", m->name, VTY_NEWLINE); + osmo_tdef_vty_write(vty, m->tdefs, " timer "); + } + + return CMD_SUCCESS; +} + +static void member_init_vty() +{ + install_node(&member_node, config_write_member); + install_element(CONFIG_NODE, &cfg_member_cmd); + + install_element_ve(&show_member_timer_cmd); + install_element(MEMBER_NODE, &cfg_member_timer_cmd); +} + +/* ------------------- THE REST is just boilerpla