aboutsummaryrefslogtreecommitdiffstats
path: root/example
diff options
context:
space:
mode:
authorPau Espin Pedrol <pespin@sysmocom.de>2020-04-07 16:40:41 +0200
committerpespin <pespin@sysmocom.de>2020-04-07 16:12:47 +0000
commit69488f6d8ae8e7a82c4b136bd0408c6c7d59378b (patch)
tree197e5eab6243f6f8a17c58c471055da016bc94c4 /example
parent29b7132eb8fca86ef4701275f3f7e28934732b46 (diff)
Move suites/ dir inside example/
example/paths.conf and documentation are updated accordingly. Test suites should have been moved a long time ago, since the they are user or setup-specific based on what needs to be tested. Change-Id: I154b19979b545deba8b232b60172903f63fd9e28
Diffstat (limited to 'example')
-rw-r--r--example/paths.conf2
-rwxr-xr-xexample/suites/4g/iperf3_dl.py49
-rwxr-xr-xexample/suites/4g/iperf3_ul.py49
-rwxr-xr-xexample/suites/4g/ping.py25
-rw-r--r--example/suites/4g/suite.conf12
-rwxr-xr-xexample/suites/debug/interactive.py163
-rw-r--r--example/suites/debug/suite.conf7
-rw-r--r--example/suites/dynts/suite.conf10
-rwxr-xr-xexample/suites/dynts/switch_tch_pdch.py98
-rw-r--r--example/suites/encryption/lib/testlib.py54
-rwxr-xr-xexample/suites/encryption/register_a5_0_authopt.py8
-rwxr-xr-xexample/suites/encryption/register_a5_0_authreq.py8
-rwxr-xr-xexample/suites/encryption/register_a5_1_authreq.py8
-rwxr-xr-xexample/suites/encryption/register_a5_3_authreq.py8
-rw-r--r--example/suites/encryption/suite.conf16
-rwxr-xr-xexample/suites/gprs/cs_paging_gprs_active.py38
-rwxr-xr-xexample/suites/gprs/iperf3.py8
-rwxr-xr-xexample/suites/gprs/iperf3m4.py8
-rw-r--r--example/suites/gprs/lib/testlib.py116
-rwxr-xr-xexample/suites/gprs/ping.py71
-rwxr-xr-xexample/suites/gprs/ping_idle_ping.py81
-rw-r--r--example/suites/gprs/suite.conf13
-rw-r--r--example/suites/nitb_debug/error.py5
-rw-r--r--example/suites/nitb_debug/fail.py5
-rw-r--r--example/suites/nitb_debug/fail_raise.py8
-rwxr-xr-xexample/suites/nitb_debug/interactive.py111
-rw-r--r--example/suites/nitb_debug/pass.py5
-rw-r--r--example/suites/nitb_debug/suite.conf10
-rwxr-xr-xexample/suites/nitb_netreg/register.py22
-rwxr-xr-xexample/suites/nitb_netreg/register_default.py22
-rw-r--r--example/suites/nitb_netreg/suite.conf10
-rw-r--r--example/suites/nitb_netreg_mass/register_default_mass.py53
-rw-r--r--example/suites/nitb_netreg_mass/suite.conf11
-rwxr-xr-xexample/suites/nitb_smpp/esme_connect_policy_acceptall.py32
-rwxr-xr-xexample/suites/nitb_smpp/esme_connect_policy_closed.py51
-rwxr-xr-xexample/suites/nitb_smpp/esme_ms_sms_storeforward.py53
-rwxr-xr-xexample/suites/nitb_smpp/esme_ms_sms_transaction.py49
-rw-r--r--example/suites/nitb_smpp/suite.conf12
-rwxr-xr-xexample/suites/nitb_sms/mo_mt_sms.py30
-rw-r--r--example/suites/nitb_sms/suite.conf12
-rwxr-xr-xexample/suites/nitb_ussd/assert_extension.py37
-rw-r--r--example/suites/nitb_ussd/suite.conf12
-rwxr-xr-xexample/suites/smpp/esme_connect_policy_acceptall.py38
-rwxr-xr-xexample/suites/smpp/esme_connect_policy_closed.py59
-rwxr-xr-xexample/suites/smpp/esme_ms_sms_storeforward.py69
-rwxr-xr-xexample/suites/smpp/esme_ms_sms_transaction.py59
-rw-r--r--example/suites/smpp/suite.conf12
-rwxr-xr-xexample/suites/sms/mo_mt_sms.py41
-rw-r--r--example/suites/sms/suite.conf9
-rwxr-xr-xexample/suites/ussd/assert_extension.py50
-rw-r--r--example/suites/ussd/suite.conf9
-rw-r--r--example/suites/voice/lib/testlib.py68
-rwxr-xr-xexample/suites/voice/mo_mt_call.py8
-rwxr-xr-xexample/suites/voice/mo_mt_call_osmux.py8
-rw-r--r--example/suites/voice/suite.conf9
55 files changed, 1840 insertions, 1 deletions
diff --git a/example/paths.conf b/example/paths.conf
index 554d942..27c5818 100644
--- a/example/paths.conf
+++ b/example/paths.conf
@@ -1,3 +1,3 @@
state_dir: '/var/tmp/osmo-gsm-tester/state'
-suites_dir: '../suites'
+suites_dir: './suites'
scenarios_dir: './scenarios'
diff --git a/example/suites/4g/iperf3_dl.py b/example/suites/4g/iperf3_dl.py
new file mode 100755
index 0000000..88ae82d
--- /dev/null
+++ b/example/suites/4g/iperf3_dl.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+def print_result_node(result, node_str):
+ sent = result['end']['sum_sent']
+ recv = result['end']['sum_received']
+ print("Result %s:" % node_str)
+ print("\tSEND: %d KB, %d kbps, %d seconds (%s retrans)" % (sent['bytes']/1000, sent['bits_per_second']/1000, sent['seconds'], str(sent.get('retransmits', 'unknown'))))
+ print("\tRECV: %d KB, %d kbps, %d seconds" % (recv['bytes']/1000, recv['bits_per_second']/1000, recv['seconds']))
+
+def print_results(cli_res, srv_res):
+ print_result_node(cli_res, 'client')
+ print_result_node(srv_res, 'server')
+
+epc = suite.epc()
+enb = suite.enb()
+ue = suite.modem()
+iperf3srv = suite.iperf3srv({'addr': epc.tun_addr()})
+iperf3srv.set_run_node(epc.run_node())
+iperf3cli = iperf3srv.create_client()
+iperf3cli.set_run_node(ue.run_node())
+
+epc.subscriber_add(ue)
+epc.start()
+enb.ue_add(ue)
+enb.start(epc)
+
+print('waiting for ENB to connect to EPC...')
+wait(epc.enb_is_connected, enb)
+print('ENB is connected to EPC')
+
+ue.connect(enb)
+
+iperf3srv.start()
+proc = iperf3cli.prepare_test_proc(True, ue.netns())
+
+print('waiting for UE to attach...')
+wait(ue.is_connected, None)
+print('UE is attached')
+
+print("Running iperf3 client to %s through %s" % (str(iperf3cli), ue.netns()))
+proc.launch_sync()
+iperf3srv.stop()
+print_results(iperf3cli.get_results(), iperf3srv.get_results())
+
+max_rate = enb.ue_max_rate(downlink=True)
+res_str = ue.verify_metric(max_rate * 0.8, operation='avg', metric='dl_brate', criterion='gt')
+print(res_str)
+test.set_report_stdout(res_str)
diff --git a/example/suites/4g/iperf3_ul.py b/example/suites/4g/iperf3_ul.py
new file mode 100755
index 0000000..597b50d
--- /dev/null
+++ b/example/suites/4g/iperf3_ul.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+def print_result_node(result, node_str):
+ sent = result['end']['sum_sent']
+ recv = result['end']['sum_received']
+ print("Result %s:" % node_str)
+ print("\tSEND: %d KB, %d kbps, %d seconds (%s retrans)" % (sent['bytes']/1000, sent['bits_per_second']/1000, sent['seconds'], str(sent.get('retransmits', 'unknown'))))
+ print("\tRECV: %d KB, %d kbps, %d seconds" % (recv['bytes']/1000, recv['bits_per_second']/1000, recv['seconds']))
+
+def print_results(cli_res, srv_res):
+ print_result_node(cli_res, 'client')
+ print_result_node(srv_res, 'server')
+
+epc = suite.epc()
+enb = suite.enb()
+ue = suite.modem()
+iperf3srv = suite.iperf3srv({'addr': epc.tun_addr()})
+iperf3srv.set_run_node(epc.run_node())
+iperf3cli = iperf3srv.create_client()
+iperf3cli.set_run_node(ue.run_node())
+
+epc.subscriber_add(ue)
+epc.start()
+enb.ue_add(ue)
+enb.start(epc)
+
+print('waiting for ENB to connect to EPC...')
+wait(epc.enb_is_connected, enb)
+print('ENB is connected to EPC')
+
+ue.connect(enb)
+
+iperf3srv.start()
+proc = iperf3cli.prepare_test_proc(False, ue.netns())
+
+print('waiting for UE to attach...')
+wait(ue.is_connected, None)
+print('UE is attached')
+
+print("Running iperf3 client to %s through %s" % (str(iperf3cli), ue.netns()))
+proc.launch_sync()
+iperf3srv.stop()
+print_results(iperf3cli.get_results(), iperf3srv.get_results())
+
+max_rate = enb.ue_max_rate(downlink=False)
+res_str = ue.verify_metric(max_rate * 0.8, operation='avg', metric='ul_brate', criterion='gt')
+print(res_str)
+test.set_report_stdout(res_str)
diff --git a/example/suites/4g/ping.py b/example/suites/4g/ping.py
new file mode 100755
index 0000000..17eee78
--- /dev/null
+++ b/example/suites/4g/ping.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+epc = suite.epc()
+enb = suite.enb()
+ue = suite.modem()
+
+epc.subscriber_add(ue)
+epc.start()
+enb.ue_add(ue)
+enb.start(epc)
+
+print('waiting for ENB to connect to EPC...')
+wait(epc.enb_is_connected, enb)
+print('ENB is connected to EPC')
+
+ue.connect(enb)
+print('waiting for UE to attach...')
+wait(ue.is_connected, None)
+print('UE is attached')
+
+proc = ue.run_netns_wait('ping', ('ping', '-c', '10', epc.tun_addr()))
+output = proc.get_stdout()
+print(output)
+test.set_report_stdout(output)
diff --git a/example/suites/4g/suite.conf b/example/suites/4g/suite.conf
new file mode 100644
index 0000000..e439e99
--- /dev/null
+++ b/example/suites/4g/suite.conf
@@ -0,0 +1,12 @@
+resources:
+ run_node: # for EPC
+ - times: 1
+ enb:
+ - times: 1
+ modem:
+ - times: 1
+ features:
+ - 4g
+
+defaults:
+ timeout: 180s
diff --git a/example/suites/debug/interactive.py b/example/suites/debug/interactive.py
new file mode 100755
index 0000000..89f967f
--- /dev/null
+++ b/example/suites/debug/interactive.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+hlr = suite.hlr()
+bts = suite.bts()
+pcu = bts.pcu()
+mgw_msc = suite.mgw()
+mgw_bsc = suite.mgw()
+stp = suite.stp()
+ggsn = suite.ggsn()
+sgsn = suite.sgsn(hlr, ggsn)
+msc = suite.msc(hlr, mgw_msc, stp)
+bsc = suite.bsc(msc, mgw_bsc, stp)
+
+modems = suite.modems(int(prompt('How many modems?')))
+
+bsc.bts_add(bts)
+sgsn.bts_add(bts)
+
+hlr.start()
+stp.start()
+ggsn.start()
+sgsn.start()
+msc.start()
+mgw_msc.start()
+mgw_bsc.start()
+bsc.start()
+
+bts.start()
+print('Waiting for bts to connect to bsc...')
+wait(bsc.bts_is_connected, bts)
+print('Waiting for bts to be ready...')
+wait(bts.ready_for_pcu)
+pcu.start()
+
+for m in modems:
+ hlr.subscriber_add(m)
+ m.connect(msc.mcc_mnc())
+
+while True:
+ cmd = prompt('Enter command: (q)uit (d)ebug (s)ms (g)et-registered (w)ait-registered, call-list [<ms_msisdn>], call-dial <src_msisdn> <dst_msisdn>, call-wait-incoming <src_msisdn> <dst_msisdn>, call-answer <mt_msisdn> <call_id>, call-hangup <ms_msisdn> <call_id>, ussd <command>, data-attach, data-wait, data-detach, data-activate')
+ cmd = cmd.strip().lower()
+
+ if not cmd:
+ continue
+
+ params = cmd.split()
+
+ if 'quit'.startswith(cmd):
+ break
+
+ elif 'debug'.startswith(cmd):
+ import pdb; pdb.set_trace()
+
+ elif 'wait-registered'.startswith(cmd):
+ try:
+ for m in modems:
+ wait(m.is_connected, msc.mcc_mnc())
+ wait(msc.subscriber_attached, *modems)
+ except Timeout:
+ print('Timeout while waiting for registration.')
+
+ elif 'get-registered'.startswith(cmd):
+ print(msc.imsi_list_attached())
+ print('RESULT: %s' %
+ ('All modems are registered.' if msc.subscriber_attached(*modems)
+ else 'Some modem(s) not registered yet.'))
+
+ elif 'sms'.startswith(cmd):
+ for mo in modems:
+ for mt in modems:
+ mo.sms_send(mt.msisdn, 'to ' + mt.name())
+
+ elif cmd.startswith('call-list'):
+ if len(params) != 1 and len(params) != 2:
+ print('wrong format')
+ continue
+ for ms in modems:
+ if len(params) == 1 or str(ms.msisdn) == params[1]:
+ print('call-list: %r %r' % (ms.name(), ms.call_id_list()))
+
+ elif cmd.startswith('call-dial'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ src_msisdn, dst_msisdn = params[1:]
+ for mo in modems:
+ if str(mo.msisdn) == src_msisdn:
+ print('dialing %s->%s' % (src_msisdn, dst_msisdn))
+ call_id = mo.call_dial(dst_msisdn)
+ print('dial success: call_id=%r' % call_id)
+
+ elif cmd.startswith('call-wait-incoming'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ src_msisdn, dst_msisdn = params[1:]
+ for mt in modems:
+ if str(mt.msisdn) == dst_msisdn:
+ print('waiting for incoming %s->%s' % (src_msisdn, dst_msisdn))
+ call_id = mt.call_wait_incoming(src_msisdn)
+ print('incoming call success: call_id=%r' % call_id)
+
+ elif cmd.startswith('call-answer'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ mt_msisdn, call_id = params[1:]
+ for mt in modems:
+ if str(mt.msisdn) == mt_msisdn:
+ print('answering %s %r' % (mt.name(), call_id))
+ mt.call_answer(call_id)
+
+ elif cmd.startswith('call-hangup'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ ms_msisdn, call_id = params[1:]
+ for ms in modems:
+ if str(ms.msisdn) == ms_msisdn:
+ print('hanging up %s %r' % (ms.name(), call_id))
+ ms.call_hangup(call_id)
+
+ elif cmd.startswith('ussd'):
+ if len(params) != 2:
+ print('wrong format')
+ continue
+ ussd_cmd = params[1]
+ for ms in modems:
+ print('modem %s: ussd %s' % (ms.name(), ussd_cmd))
+ response = ms.ussd_send(ussd_cmd)
+ print('modem %s: response=%r' % (ms.name(), response))
+
+ elif cmd.startswith('data-attach'):
+ if len(params) != 1:
+ print('wrong format')
+ continue
+ for ms in modems:
+ print('modem %s: attach' % ms.name())
+ ms.attach()
+ wait(ms.is_attached)
+ print('modem %s: attached' % ms.name())
+
+ elif cmd.startswith('data-detach'):
+ if len(params) != 1:
+ print('wrong format')
+ continue
+ for ms in modems:
+ print('modem %s: detach' % ms.name())
+ ms.attach()
+ wait(lambda: not ms.is_attached())
+ print('modem %s: detached' % ms.name())
+
+ elif cmd.startswith('data-activate'):
+ if len(params) != 1:
+ print('wrong format')
+ continue
+ for ms in modems:
+ print('modem %s: activate' % ms.name())
+ response = ms.activate_context()
+ print('modem %s: response=%r' % (ms.name(), response))
+
+ else:
+ print('Unknown command: %s' % cmd)
diff --git a/example/suites/debug/suite.conf b/example/suites/debug/suite.conf
new file mode 100644
index 0000000..2f36e1d
--- /dev/null
+++ b/example/suites/debug/suite.conf
@@ -0,0 +1,7 @@
+resources:
+ ip_address:
+ - times: 8
+ bts:
+ - times: 1
+ modem:
+ - times: 4
diff --git a/example/suites/dynts/suite.conf b/example/suites/dynts/suite.conf
new file mode 100644
index 0000000..3b32480
--- /dev/null
+++ b/example/suites/dynts/suite.conf
@@ -0,0 +1,10 @@
+resources:
+ ip_address:
+ - times: 8 # msc, bsc, hlr, stp, mgw*2, sgsn, ggsn
+ bts:
+ - times: 1
+ modem:
+ - times: 2
+ features:
+ - gprs
+ - voice
diff --git a/example/suites/dynts/switch_tch_pdch.py b/example/suites/dynts/switch_tch_pdch.py
new file mode 100755
index 0000000..f0bbd38
--- /dev/null
+++ b/example/suites/dynts/switch_tch_pdch.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+
+def activate_pdp(ms_mo, ms_mt):
+ # We need to use inet46 since ofono qmi only uses ipv4v6 eua (OS#2713)
+ ctx_id_v4_mo = ms_mo.activate_context(apn='inet46', protocol=ms_mo.CTX_PROT_IPv4)
+ print('ms_mo pdp ctx %r activated' % repr(ctx_id_v4_mo))
+ ctx_id_v4_mt = ms_mt.activate_context(apn='inet46', protocol=ms_mt.CTX_PROT_IPv4)
+ print('ms_mt pdp ctx %r activated' % repr(ctx_id_v4_mt))
+ sleep(5)
+ ms_mo.deactivate_context(ctx_id_v4_mo)
+ ms_mt.deactivate_context(ctx_id_v4_mt)
+
+def make_call(ms_mo, ms_mt):
+ assert len(ms_mo.call_id_list()) == 0 and len(ms_mt.call_id_list()) == 0
+ mo_cid = ms_mo.call_dial(ms_mt)
+ mt_cid = ms_mt.call_wait_incoming(ms_mo)
+ print('dial success')
+
+ assert not ms_mo.call_is_active(mo_cid) and not ms_mt.call_is_active(mt_cid)
+ ms_mt.call_answer(mt_cid)
+ wait(ms_mo.call_is_active, mo_cid)
+ wait(ms_mt.call_is_active, mt_cid)
+ print('answer success, call established and ongoing')
+
+ sleep(5) # maintain the call active for 5 seconds
+
+ assert ms_mo.call_is_active(mo_cid) and ms_mt.call_is_active(mt_cid)
+ ms_mo.call_hangup(mo_cid)
+ ms_mt.call_hangup(mt_cid)
+ wait(lambda: len(ms_mo.call_id_list()) == 0 and len(ms_mt.call_id_list()) == 0)
+ print('hangup success')
+
+hlr = suite.hlr()
+bts = suite.bts()
+pcu = bts.pcu()
+mgw_msc = suite.mgw()
+mgw_bsc = suite.mgw()
+stp = suite.stp()
+ggsn = suite.ggsn()
+sgsn = suite.sgsn(hlr, ggsn)
+msc = suite.msc(hlr, mgw_msc, stp)
+bsc = suite.bsc(msc, mgw_bsc, stp)
+ms_mo = suite.modem()
+ms_mt = suite.modem()
+
+bsc.bts_add(bts)
+sgsn.bts_add(bts)
+
+print('start network...')
+hlr.start()
+stp.start()
+ggsn.start()
+sgsn.start()
+msc.start()
+mgw_msc.start()
+mgw_bsc.start()
+bsc.start()
+
+bts.start()
+wait(bsc.bts_is_connected, bts)
+print('Waiting for bts to be ready...')
+wait(bts.ready_for_pcu)
+pcu.start()
+
+hlr.subscriber_add(ms_mo)
+hlr.subscriber_add(ms_mt)
+
+ms_mo.connect(msc.mcc_mnc())
+ms_mt.connect(msc.mcc_mnc())
+ms_mo.attach()
+ms_mt.attach()
+
+ms_mo.log_info()
+ms_mt.log_info()
+
+print('waiting for modems to attach...')
+wait(ms_mo.is_connected, msc.mcc_mnc())
+wait(ms_mt.is_connected, msc.mcc_mnc())
+wait(msc.subscriber_attached, ms_mo)
+wait(msc.subscriber_attached, ms_mt)
+
+print('waiting for modems to attach to data services...')
+wait(ms_mo.is_attached)
+wait(ms_mt.is_attached)
+
+print('1: activate_pdp')
+activate_pdp(ms_mo, ms_mt)
+print('2: make_call')
+make_call(ms_mo, ms_mt)
+print('3: Wait 30 seconds to let PCU handle the PDCH channels again')
+sleep(30)
+print('3: activate_pdp')
+activate_pdp(ms_mo, ms_mt)
+print('4: make_call')
+make_call(ms_mo, ms_mt)
+print('Done!')
diff --git a/example/suites/encryption/lib/testlib.py b/example/suites/encryption/lib/testlib.py
new file mode 100644
index 0000000..3948941
--- /dev/null
+++ b/example/suites/encryption/lib/testlib.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+def encryption_test_setup_run(enable_auth, algo):
+ hlr = suite.hlr()
+ bts = suite.bts()
+ mgw_msc = suite.mgw()
+ mgw_bsc = suite.mgw()
+ stp = suite.stp()
+ msc = suite.msc(hlr, mgw_msc, stp)
+ bsc = suite.bsc(msc, mgw_bsc, stp)
+ ms = suite.modem()
+
+ print('start network...')
+ msc.set_authentication(enable_auth)
+ msc.set_encryption(algo)
+ bsc.set_encryption(algo)
+ hlr.start()
+ stp.start()
+ msc.start()
+ mgw_msc.start()
+ mgw_bsc.start()
+ bsc.bts_add(bts)
+ bsc.start()
+ bts.start()
+ wait(bsc.bts_is_connected, bts)
+
+ ms.log_info()
+ good_ki = ms.ki()
+ bad_ki = ("%1X" % (int(good_ki[0], 16) ^ 0x01)) + good_ki[1:]
+
+ print('KI changed: ' + good_ki + " => " + bad_ki)
+ ms.set_ki(bad_ki)
+ hlr.subscriber_add(ms)
+ if enable_auth:
+ print('Attempt connection with wrong KI...')
+ ms.connect(msc.mcc_mnc())
+
+ sleep(40) # TODO: read pcap or CTRL interface and look for Rejected? (gsm_a.dtap.msg_mm_type == 0x04)
+ print('Asserting modem did not register')
+ # FIXME: this can fail because ofono qmi signals registered before being accepted by network. See OS#2458
+ # assert not ms.is_connected(msc.mcc_mnc())
+ assert not msc.subscriber_attached(ms)
+
+ hlr.subscriber_delete(ms)
+ print('KI changed: ' + bad_ki + " => " + good_ki)
+ ms.set_ki(good_ki)
+ hlr.subscriber_add(ms, ms.msisdn)
+ print('Attempt connection with correct KI...')
+ else:
+ print('Attempt connection with wrong KI, should work as it is not used...')
+ ms.connect(msc.mcc_mnc())
+ wait(ms.is_connected, msc.mcc_mnc())
+ wait(msc.subscriber_attached, ms)
diff --git a/example/suites/encryption/register_a5_0_authopt.py b/example/suites/encryption/register_a5_0_authopt.py
new file mode 100755
index 0000000..1b7f471
--- /dev/null
+++ b/example/suites/encryption/register_a5_0_authopt.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+import testlib
+suite.test_import_modules_register_for_cleanup(testlib)
+from testlib import encryption_test_setup_run
+
+encryption_test_setup_run(False, 'a5_0')
diff --git a/example/suites/encryption/register_a5_0_authreq.py b/example/suites/encryption/register_a5_0_authreq.py
new file mode 100755
index 0000000..feca525
--- /dev/null
+++ b/example/suites/encryption/register_a5_0_authreq.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+import testlib
+suite.test_import_modules_register_for_cleanup(testlib)
+from testlib import encryption_test_setup_run
+
+encryption_test_setup_run(True, 'a5_0')
diff --git a/example/suites/encryption/register_a5_1_authreq.py b/example/suites/encryption/register_a5_1_authreq.py
new file mode 100755
index 0000000..077819b
--- /dev/null
+++ b/example/suites/encryption/register_a5_1_authreq.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+import testlib
+suite.test_import_modules_register_for_cleanup(testlib)
+from testlib import encryption_test_setup_run
+
+encryption_test_setup_run(True, 'a5_1')
diff --git a/example/suites/encryption/register_a5_3_authreq.py b/example/suites/encryption/register_a5_3_authreq.py
new file mode 100755
index 0000000..219c109
--- /dev/null
+++ b/example/suites/encryption/register_a5_3_authreq.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+import testlib
+suite.test_import_modules_register_for_cleanup(testlib)
+from testlib import encryption_test_setup_run
+
+encryption_test_setup_run(True, 'a5_3')
diff --git a/example/suites/encryption/suite.conf b/example/suites/encryption/suite.conf
new file mode 100644
index 0000000..18e94a3
--- /dev/null
+++ b/example/suites/encryption/suite.conf
@@ -0,0 +1,16 @@
+resources:
+ ip_address:
+ - times: 6 # msc, bsc, hlr, stp, mgw*2
+ bts:
+ - times: 1
+ ciphers:
+ - a5_0
+ - a5_1
+ modem:
+ - times: 1
+ ciphers:
+ - a5_0
+ - a5_1
+
+defaults:
+ timeout: 120s
diff --git a/example/suites/gprs/cs_paging_gprs_active.py b/example/suites/gprs/cs_paging_gprs_active.py
new file mode 100755
index 0000000..b7dead2
--- /dev/null
+++ b/example/suites/gprs/cs_paging_gprs_active.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+# Following test verifies CS paging works when MS is GPRS attached.
+# See OS#2204 for more information.
+
+from osmo_gsm_tester.testenv import *
+
+import testlib
+suite.test_import_modules_register_for_cleanup(testlib)
+from testlib import setup_run_iperf3_test_parallel
+
+def ready_cb_place_voicecall(ms_li):
+ print('waiting a few secs to make sure iperf3 test is running')
+ sleep(2)
+ # At this point in time, TBF should be enabled on both MS since they are sending/receiving data.
+ print('iperf3 running, let\'s place a call')
+ ms_mo = ms_li[0]
+ ms_mt = ms_li[1]
+ assert len(ms_mo.call_id_list()) == 0 and len(ms_mt.call_id_list()) == 0
+ mo_cid = ms_mo.call_dial(ms_mt)
+ mt_cid = ms_mt.call_wait_incoming(ms_mo)
+ print('dial success')
+
+ assert not ms_mo.call_is_active(mo_cid) and not ms_mt.call_is_active(mt_cid)
+ ms_mt.call_answer(mt_cid)
+ wait(ms_mo.call_is_active, mo_cid)
+ wait(ms_mt.call_is_active, mt_cid)
+ print('answer success, call established and ongoing')
+
+ sleep(5) # maintain the call active for 5 seconds
+
+ assert ms_mo.call_is_active(mo_cid) and ms_mt.call_is_active(mt_cid)
+ ms_mt.call_hangup(mt_cid)
+ wait(lambda: len(ms_mo.call_id_list()) == 0 and len(ms_mt.call_id_list()) == 0)
+ print('hangup success')
+
+
+setup_run_iperf3_test_parallel(2, ready_cb=ready_cb_place_voicecall)
diff --git a/example/suites/gprs/iperf3.py b/example/suites/gprs/iperf3.py
new file mode 100755
index 0000000..e25519a
--- /dev/null
+++ b/example/suites/gprs/iperf3.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+import testlib
+suite.test_import_modules_register_for_cleanup(testlib)
+from testlib import setup_run_iperf3_test_parallel
+
+setup_run_iperf3_test_parallel(1)
diff --git a/example/suites/gprs/iperf3m4.py b/example/suites/gprs/iperf3m4.py
new file mode 100755
index 0000000..1cc27ed
--- /dev/null
+++ b/example/suites/gprs/iperf3m4.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+import testlib
+suite.test_import_modules_register_for_cleanup(testlib)
+from testlib import setup_run_iperf3_test_parallel
+
+setup_run_iperf3_test_parallel(4)
diff --git a/example/suites/gprs/lib/testlib.py b/example/suites/gprs/lib/testlib.py
new file mode 100644
index 0000000..c1a1bc1
--- /dev/null
+++ b/example/suites/gprs/lib/testlib.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+def print_result_node(result, node_str):
+ sent = result['end']['sum_sent']
+ recv = result['end']['sum_received']
+ print("Result %s:" % node_str)
+ print("\tSEND: %d KB, %d kbps, %d seconds (%s retrans)" % (sent['bytes']/1000, sent['bits_per_second']/1000, sent['seconds'], str(sent.get('retransmits', 'unknown'))))
+ print("\tRECV: %d KB, %d kbps, %d seconds" % (recv['bytes']/1000, recv['bits_per_second']/1000, recv['seconds']))
+
+def print_results(cli_res, srv_res):
+ print_result_node(cli_res, 'client')
+ print_result_node(srv_res, 'server')
+
+def run_iperf3_cli_parallel(iperf3clients, ms_li, ready_cb):
+ assert len(iperf3clients) == len(ms_li)
+ procs = []
+ for i in range(len(iperf3clients)):
+ print("Running iperf3 client to %s through %r" % (str(iperf3clients[i]), repr(ms_li[i].tmp_ctx_id)))
+ procs.append(iperf3clients[i].prepare_test_proc(False, ms_li[i].netns()))
+ try:
+ for proc in procs:
+ proc.launch()
+ if ready_cb:
+ ready_cb(ms_li)
+ for proc in procs:
+ proc.wait()
+ except Exception as e:
+ for proc in procs:
+ try:
+ proc.terminate()
+ except Exception:
+ print("Exception while terminating process %r" % repr(process))
+ raise e
+
+
+def setup_run_iperf3_test_parallel(num_ms, ready_cb=None):
+ hlr = suite.hlr()
+ bts = suite.bts()
+ pcu = bts.pcu()
+ mgw_msc = suite.mgw()
+ mgw_bsc = suite.mgw()
+ stp = suite.stp()
+ ggsn = suite.ggsn()
+ sgsn = suite.sgsn(hlr, ggsn)
+ msc = suite.msc(hlr, mgw_msc, stp)
+ bsc = suite.bsc(msc, mgw_bsc, stp)
+
+ iperf3srv_addr = suite.ip_address()
+ servers = []
+ clients = []
+ ms_li = []
+ for i in range(num_ms):
+ iperf3srv = suite.iperf3srv(iperf3srv_addr)
+ iperf3srv.set_port(iperf3srv.DEFAULT_SRV_PORT + i)
+ servers.append(iperf3srv)
+
+ iperf3cli = iperf3srv.create_client()
+ clients.append(iperf3cli)
+
+ ms = suite.modem()
+ ms_li.append(ms)
+
+ bsc.bts_add(bts)
+ sgsn.bts_add(bts)
+
+ for iperf3srv in servers:
+ print('start iperfv3 server %s...' % str(iperf3srv) )
+ iperf3srv.start()
+
+ print('start network...')
+ hlr.start()
+ stp.start()
+ ggsn.start()
+ sgsn.start()
+ msc.start()
+ mgw_msc.start()
+ mgw_bsc.start()
+ bsc.start()
+
+ bts.start()
+ wait(bsc.bts_is_connected, bts)
+ print('Waiting for bts to be ready...')
+ wait(bts.ready_for_pcu)
+ pcu.start()
+
+ for ms in ms_li:
+ hlr.subscriber_add(ms)
+ ms.connect(msc.mcc_mnc())
+ ms.attach()
+ ms.log_info()
+
+ print('waiting for modems to attach...')
+ for ms in ms_li:
+ wait(ms.is_connected, msc.mcc_mnc())
+ wait(msc.subscriber_attached, *ms_li)
+
+ print('waiting for modems to attach to data services...')
+ for ms in ms_li:
+ wait(ms.is_attached)
+ # We need to use inet46 since ofono qmi only uses ipv4v6 eua (OS#2713)
+ ctx_id_v4 = ms.activate_context(apn='inet46', protocol=ms.CTX_PROT_IPv4)
+ print("Setting up data plan for %r" % repr(ctx_id_v4))
+ ms.setup_context_data_plane(ctx_id_v4)
+ setattr(ms, 'tmp_ctx_id', ctx_id_v4)
+
+ run_iperf3_cli_parallel(clients, ms_li, ready_cb)
+
+ for i in range(num_ms):
+ servers[i].stop()
+ print("Results for %s through %r" % (str(servers[i]), repr(ms_li[i].tmp_ctx_id)))
+ print_results(clients[i].get_results(), servers[i].get_results())
+
+ for ms in ms_li:
+ ms.deactivate_context(ms.tmp_ctx_id)
+ delattr(ms, 'tmp_ctx_id')
diff --git a/example/suites/gprs/ping.py b/example/suites/gprs/ping.py
new file mode 100755
index 0000000..22fe80a
--- /dev/null
+++ b/example/suites/gprs/ping.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+hlr = suite.hlr()
+bts = suite.bts()
+pcu = bts.pcu()
+mgw_msc = suite.mgw()
+mgw_bsc = suite.mgw()
+stp = suite.stp()
+ggsn = suite.ggsn()
+sgsn = suite.sgsn(hlr, ggsn)
+msc = suite.msc(hlr, mgw_msc, stp)
+bsc = suite.bsc(msc, mgw_bsc, stp)
+ms = suite.modem()
+
+bsc.bts_add(bts)
+sgsn.bts_add(bts)
+
+print('start network...')
+hlr.start()
+stp.start()
+ggsn.start()
+sgsn.start()
+msc.start()
+mgw_msc.start()
+mgw_bsc.start()
+bsc.start()
+
+bts.start()
+wait(bsc.bts_is_connected, bts)
+print('Waiting for bts to be ready...')
+wait(bts.ready_for_pcu)
+pcu.start()
+
+hlr.subscriber_add(ms)
+
+ms.connect(msc.mcc_mnc())
+ms.attach()
+
+ms.log_info()
+
+print('waiting for modems to attach...')
+wait(ms.is_connected, msc.mcc_mnc())
+wait(msc.subscriber_attached, ms)
+
+print('waiting for modems to attach to data services...')
+wait(ms.is_attached)
+
+# We need to use inet46 since ofono qmi only uses ipv4v6 eua (OS#2713)
+ctx_id_v4 = ms.activate_context(apn='inet46', protocol=ms.CTX_PROT_IPv4)
+print("Setting up data plan for %r" % repr(ctx_id_v4))
+ms.setup_context_data_plane(ctx_id_v4)
+print("Running 10 ping requests for %r" % repr(ctx_id_v4))
+proc = ms.run_netns_wait('ping', ('ping', '-c', '10', ggsn.addr()))
+output = proc.get_stdout()
+ms.deactivate_context(ctx_id_v4)
+
+# We need to use inet46 since ofono qmi only uses ipv4v6 eua (OS#2713)
+ctx_id_v6 = ms.activate_context(apn='inet46', protocol=ms.CTX_PROT_IPv6)
+sleep(5)
+# TODO: send ping to server or open TCP conn with a socket in python
+ms.deactivate_context(ctx_id_v6)
+
+# IPv46 (dual) not supported in ofono qmi: org.ofono.Error.Failed: Operation failed (36)
+# ctx_id_v46 = ms.activate_context(apn='inet46', protocol=ms.CTX_PROT_IPv46)
+# sleep(5)
+# TODO: send ping to server or open TCP conn with a socket in python
+# ms.deactivate_context(ctx_id_v46)
+
+print(output)
+test.set_report_stdout(output)
diff --git a/example/suites/gprs/ping_idle_ping.py b/example/suites/gprs/ping_idle_ping.py
new file mode 100755
index 0000000..02e2cdf
--- /dev/null
+++ b/example/suites/gprs/ping_idle_ping.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+
+# Following test verifies GPRS works fine after MS stays idle (no data
+# sent/received) for a long while.
+# See OS#3678 and OS#2455 for more information.
+
+from osmo_gsm_tester.testenv import *
+
+hlr = suite.hlr()
+bts = suite.bts()
+pcu = bts.pcu()
+mgw_msc = suite.mgw()
+mgw_bsc = suite.mgw()
+stp = suite.stp()
+ggsn = suite.ggsn()
+sgsn = suite.sgsn(hlr, ggsn)
+msc = suite.msc(hlr, mgw_msc, stp)
+bsc = suite.bsc(msc, mgw_bsc, stp)
+ms = suite.modem()
+
+bsc.bts_add(bts)
+sgsn.bts_add(bts)
+
+print('start network...')
+hlr.start()
+stp.start()
+ggsn.start()
+sgsn.start()
+msc.start()
+mgw_msc.start()
+mgw_bsc.start()
+bsc.start()
+
+bts.start()
+wait(bsc.bts_is_connected, bts)
+print('Waiting for bts to be ready...')
+wait(bts.ready_for_pcu)
+pcu.start()
+
+hlr.subscriber_add(ms)
+
+ms.connect(msc.mcc_mnc())
+ms.attach()
+
+ms.log_info()
+
+print('waiting for modems to attach...')
+wait(ms.is_connected, msc.mcc_mnc())
+wait(msc.subscriber_attached, ms)
+
+print('waiting for modems to attach to data services...')
+wait(ms.is_attached)
+
+# We need to use inet46 since ofono qmi only uses ipv4v6 eua (OS#2713)
+ctx_id_v4 = ms.activate_context(apn='inet46', protocol=ms.CTX_PROT_IPv4)
+print("Setting up data plane for %r" % repr(ctx_id_v4))
+ms.setup_context_data_plane(ctx_id_v4)
+str = "[1] Running 10 ping requests for %r" % repr(ctx_id_v4)
+output = str + '\n'
+print(str)
+proc = ms.run_netns_wait('ping1', ('ping', '-c', '10', ggsn.addr()))
+str = proc.get_stdout()
+output += str
+print(str)
+
+str = "Sleeping for 60 seconds"
+output += str + '\n'
+print(str)
+sleep(60)
+
+str = "[2] Running 10 ping requests for %r" % repr(ctx_id_v4)
+output += str + '\n'
+print(str)
+proc = ms.run_netns_wait('ping2', ('ping', '-c', '10', ggsn.addr()))
+str = proc.get_stdout()
+output += str
+print(str)
+
+ms.deactivate_context(ctx_id_v4)
+
+test.set_report_stdout(output)
diff --git a/example/suites/gprs/suite.conf b/example/suites/gprs/suite.conf
new file mode 100644
index 0000000..d40c1e5
--- /dev/null
+++ b/example/suites/gprs/suite.conf
@@ -0,0 +1,13 @@
+resources:
+ ip_address:
+ - times: 9 # msc, bsc, hlr, stp, mgw*2, sgsn, ggsn, iperf3srv
+ bts:
+ - times: 1
+ modem:
+ - times: 2
+ features:
+ - gprs
+ - voice
+ - times: 2
+ features:
+ - gprs
diff --git a/example/suites/nitb_debug/error.py b/example/suites/nitb_debug/error.py
new file mode 100644
index 0000000..032e26c
--- /dev/null
+++ b/example/suites/nitb_debug/error.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+# This can be used to verify that a test error is reported properly.
+assert False
diff --git a/example/suites/nitb_debug/fail.py b/example/suites/nitb_debug/fail.py
new file mode 100644
index 0000000..a2d9e3a
--- /dev/null
+++ b/example/suites/nitb_debug/fail.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+# This can be used to verify that a test failure is reported properly.
+test.set_fail('ExpectedFail', 'This failure is expected')
diff --git a/example/suites/nitb_debug/fail_raise.py b/example/suites/nitb_debug/fail_raise.py
new file mode 100644
index 0000000..c30a4f5
--- /dev/null
+++ b/example/suites/nitb_debug/fail_raise.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+class ExpectedExn(Exception):
+ pass
+
+# This can be used to verify that a test failure is reported properly.
+raise ExpectedExn('This failure is expected')
diff --git a/example/suites/nitb_debug/interactive.py b/example/suites/nitb_debug/interactive.py
new file mode 100755
index 0000000..595cfd9
--- /dev/null
+++ b/example/suites/nitb_debug/interactive.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+print('use resources...')
+nitb = suite.nitb()
+bts = suite.bts()
+modems = suite.modems(int(prompt('How many modems?')))
+
+print('start nitb and bts...')
+nitb.bts_add(bts)
+nitb.start()
+bts.start()
+wait(nitb.bts_is_connected, bts)
+
+for m in modems:
+ nitb.subscriber_add(m)
+ m.connect(nitb.mcc_mnc())
+
+while True:
+ cmd = prompt('Enter command: (q)uit (s)ms (g)et-registered (w)ait-registered, call-list [<ms_msisdn>], call-dial <src_msisdn> <dst_msisdn>, call-wait-incoming <src_msisdn> <dst_msisdn>, call-answer <mt_msisdn> <call_id>, call-hangup <ms_msisdn> <call_id>, ussd <command>')
+ cmd = cmd.strip().lower()
+
+ if not cmd:
+ continue
+
+ params = cmd.split()
+
+ if 'quit'.startswith(cmd):
+ break
+
+ elif 'wait-registered'.startswith(cmd):
+ try:
+ for m in modems:
+ wait(m.is_connected, nitb.mcc_mnc())
+ wait(nitb.subscriber_attached, *modems)
+ except Timeout:
+ print('Timeout while waiting for registration.')
+
+ elif 'get-registered'.startswith(cmd):
+ print(nitb.imsi_list_attached())
+ print('RESULT: %s' %
+ ('All modems are registered.' if nitb.subscriber_attached(*modems)
+ else 'Some modem(s) not registered yet.'))
+
+ elif 'sms'.startswith(cmd):
+ for mo in modems:
+ for mt in modems:
+ mo.sms_send(mt.msisdn, 'to ' + mt.name())
+
+ elif cmd.startswith('call-list'):
+ if len(params) != 1 and len(params) != 2:
+ print('wrong format')
+ continue
+ for ms in modems:
+ if len(params) == 1 or str(ms.msisdn) == params[1]:
+ print('call-list: %r %r' % (ms.name(), ms.call_id_list()))
+
+ elif cmd.startswith('call-dial'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ src_msisdn, dst_msisdn = params[1:]
+ for mo in modems:
+ if str(mo.msisdn) == src_msisdn:
+ print('dialing %s->%s' % (src_msisdn, dst_msisdn))
+ call_id = mo.call_dial(dst_msisdn)
+ print('dial success: call_id=%r' % call_id)
+
+ elif cmd.startswith('call-wait-incoming'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ src_msisdn, dst_msisdn = params[1:]
+ for mt in modems:
+ if str(mt.msisdn) == dst_msisdn:
+ print('waiting for incoming %s->%s' % (src_msisdn, dst_msisdn))
+ call_id = mt.call_wait_incoming(src_msisdn)
+ print('incoming call success: call_id=%r' % call_id)
+
+ elif cmd.startswith('call-answer'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ mt_msisdn, call_id = params[1:]
+ for mt in modems:
+ if str(mt.msisdn) == mt_msisdn:
+ print('answering %s %r' % (mt.name(), call_id))
+ mt.call_answer(call_id)
+
+ elif cmd.startswith('call-hangup'):
+ if len(params) != 3:
+ print('wrong format')
+ continue
+ ms_msisdn, call_id = params[1:]
+ for ms in modems:
+ if str(ms.msisdn) == ms_msisdn:
+ print('hanging up %s %r' % (ms.name(), call_id))
+ ms.call_hangup(call_id)
+
+ elif cmd.startswith('ussd'):
+ if len(params) != 2:
+ print('wrong format')
+ continue
+ ussd_cmd = params[1]
+ for ms in modems:
+ print('modem %s: ussd %s' % (ms.name(), ussd_cmd))
+ response = ms.ussd_send(ussd_cmd)
+ print('modem %s: response=%r' % (ms.name(), response))
+
+ else:
+ print('Unknown command: %s' % cmd)
diff --git a/example/suites/nitb_debug/pass.py b/example/suites/nitb_debug/pass.py
new file mode 100644
index 0000000..c07f079
--- /dev/null
+++ b/example/suites/nitb_debug/pass.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+# This can be used to verify that a test passes correctly.
+pass
diff --git a/example/suites/nitb_debug/suite.conf b/example/suites/nitb_debug/suite.conf
new file mode 100644
index 0000000..adfc161
--- /dev/null
+++ b/example/suites/nitb_debug/suite.conf
@@ -0,0 +1,10 @@
+resources:
+ ip_address:
+ - times: 1
+ bts:
+ - times: 1
+ modem:
+ - times: 4
+
+defaults:
+ timeout: 60s
diff --git a/example/suites/nitb_netreg/register.py b/example/suites/nitb_netreg/register.py
new file mode 100755
index 0000000..d5fbeb7
--- /dev/null
+++ b/example/suites/nitb_netreg/register.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+print('use resources...')
+nitb = suite.nitb()
+bts = suite.bts()
+ms = suite.modem()
+
+print('start nitb and bts...')
+nitb.bts_add(bts)
+nitb.start()
+bts.start()
+wait(nitb.bts_is_connected, bts)
+
+nitb.subscriber_add(ms)
+
+ms.connect(nitb.mcc_mnc())
+
+print(ms.info())
+
+wait(ms.is_connected, nitb.mcc_mnc())
+wait(nitb.subscriber_attached, ms)
diff --git a/example/suites/nitb_netreg/register_default.py b/example/suites/nitb_netreg/register_default.py
new file mode 100755
index 0000000..545525d
--- /dev/null
+++ b/example/suites/nitb_netreg/register_default.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+print('use resources...')
+nitb = suite.nitb()
+bts = suite.bts()
+ms = suite.modem()
+
+print('start nitb and bts...')
+nitb.bts_add(bts)
+nitb.start()
+bts.start()
+wait(nitb.bts_is_connected, bts)
+
+nitb.subscriber_add(ms)
+
+ms.connect()
+
+print(ms.info())
+
+wait(ms.is_connected)
+wait(nitb.subscriber_attached, ms)
diff --git a/example/suites/nitb_netreg/suite.conf b/example/suites/nitb_netreg/suite.conf
new file mode 100644
index 0000000..1bb1dbb
--- /dev/null
+++ b/example/suites/nitb_netreg/suite.conf
@@ -0,0 +1,10 @@
+resources:
+ ip_address:
+ - times: 1
+ bts:
+ - times: 1
+ modem:
+ - times: 1
+
+defaults:
+ timeout: 40s
diff --git a/example/suites/nitb_netreg_mass/register_default_mass.py b/example/suites/nitb_netreg_mass/register_default_mass.py
new file mode 100644
index 0000000..262b271
--- /dev/null
+++ b/example/suites/nitb_netreg_mass/register_default_mass.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+"""
+Runs a network registration with a 'massive' amount of MS
+using the ms_driver infrastructure.
+"""
+from osmo_gsm_tester.testenv import *
+from datetime import timedelta
+
+print('Claiming resources for the test')
+nitb = suite.nitb()
+bts = suite.bts()
+ms_driver = suite.ms_driver()
+ul = ms_driver.add_test('ul_test')
+modems = suite.all_resources(suite.modem)
+
+print('Launching a simple network')
+nitb.bts_add(bts)
+nitb.start()
+bts.start()
+wait(nitb.bts_is_connected, bts)
+
+# Configure all MS that are available to this test.
+for modem in modems:
+ nitb.subscriber_add(modem)
+ ms_driver.subscriber_add(modem)
+
+# Run the base test.
+ms_driver.run_test()
+
+# Print the stats of the run.
+ms_driver.print_stats()
+
+# Evaluate if this run was successful or not. Our initial acceptance criteria
+# is quite basic but it should allow us to scale to a larger number of MS and
+# reasons (e.g. have a full BCCH).
+#
+# 99% of LUs should complete
+# 99% of successful LUs should complete within 10s.
+stats = ul.get_stats()
+if len(modems) > 0 and stats.num_completed < 1:
+ raise Exception("No run completed.")
+completion_ratio = stats.num_completed / stats.num_attempted
+
+# Verify that 99% of LUs completed.
+if completion_ratio < 0.99:
+ raise Exception("Completion ratio of %f%% lower than threshold." % (completion_ratio * 100.0))
+
+# Check how many results are below our threshold.
+acceptable_delay = timedelta(seconds=30)
+quick_enough = len(ul.lus_less_than(acceptable_delay))
+latency_ratio = quick_enough / stats.num_attempted
+if latency_ratio < 0.99:
+ raise Exception("Latency ratio of %f%% lower than threshold." % (latency_ratio * 100.0))
diff --git a/example/suites/nitb_netreg_mass/suite.conf b/example/suites/nitb_netreg_mass/suite.conf
new file mode 100644
index 0000000..bb1585b
--- /dev/null
+++ b/example/suites/nitb_netreg_mass/suite.conf
@@ -0,0 +1,11 @@
+resources:
+ ip_address:
+ - times: 1
+ bts:
+ - type: osmo-bts-virtual
+ modem:
+ - times: 100
+ type: osmo-mobile
+
+defaults:
+ timeout: 50s
diff --git a/example/suites/nitb_smpp/esme_connect_policy_acceptall.py b/example/suites/nitb_smpp/esme_connect_policy_acceptall.py
new file mode 100755
index 0000000..904226b
--- /dev/null
+++ b/example/suites/nitb_smpp/esme_connect_policy_acceptall.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+
+# This test checks following use-cases while in 'accept-all' policy:
+# * SMPP interface of SMSC accepts SMPP clients (ESMEs) which do not appear on
+# the config file
+
+from osmo_gsm_tester.testenv import *
+
+nitb = suite.nitb()
+smsc = nitb.smsc
+esme = suite.esme()
+
+# Here we deliberately omit calling smsc.esme_add() to avoid having it included
+# in the smsc config.
+smsc.set_smsc_policy(smsc.SMSC_POLICY_ACCEPT_ALL)
+esme.set_smsc(smsc)
+
+nitb.start()
+
+# Due to accept-all policy, connect() should work even if we didn't previously
+# configure the esme in the smsc, no matter the system_id / password we use.
+log('Test connect with non-empty values in system_id and password')
+esme.set_system_id('foo')
+esme.set_password('bar')
+esme.connect()
+esme.disconnect()
+
+log('Test connect with empty values in system_id and password')
+esme.set_system_id('')
+esme.set_password('')
+esme.connect()
+esme.disconnect()
diff --git a/example/suites/nitb_smpp/esme_connect_policy_closed.py b/example/suites/nitb_smpp/esme_connect_policy_closed.py
new file mode 100755
index 0000000..eaabb3d
--- /dev/null
+++ b/example/suites/nitb_smpp/esme_connect_policy_closed.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+
+# This test checks following use-cases while in 'closed' policy:
+# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously
+# defined in its configuration file.
+# * SMPP interface of SMSC rejects ESMEs with known system id but wrong password.
+# * SMPP interface of SMSC rejects ESEMs with unknown system id
+
+from osmo_gsm_tester.testenv import *
+
+SMPP_ESME_RINVPASWD = 0x0000000E
+SMPP_ESME_RINVSYSID = 0x0000000F
+
+nitb = suite.nitb()
+smsc = nitb.smsc
+esme = suite.esme()
+esme_no_pwd = suite.esme()
+esme_no_pwd.set_password('')
+
+smsc.set_smsc_policy(smsc.SMSC_POLICY_CLOSED)
+smsc.esme_add(esme)
+smsc.esme_add(esme_no_pwd)
+
+nitb.start()
+
+log('Test with correct credentials (no password)')
+esme_no_pwd.connect()
+esme_no_pwd.disconnect()
+
+log('Test with correct credentials (no password, non empty)')
+esme_no_pwd.set_password('foobar')
+esme_no_pwd.connect()
+esme_no_pwd.disconnect()
+
+log('Test with correct credentials')
+esme.connect()
+esme.disconnect()
+
+log('Test with bad password, checking for failure')
+correct_password = esme.password
+new_password = 'barfoo' if correct_password == 'foobar' else 'foobar'
+esme.set_password(new_password)
+esme.run_method_expect_failure(SMPP_ESME_RINVPASWD, esme.connect)
+esme.set_password(correct_password)
+
+log('Test with bad system_id, checking for failure')
+correct_system_id = esme.system_id
+new_system_id = 'barfoo' if correct_system_id == 'foobar' else 'foobar'
+esme.set_system_id(new_system_id)
+esme.run_method_expect_failure(SMPP_ESME_RINVSYSID, esme.connect)
+esme.set_system_id(correct_system_id)
diff --git a/example/suites/nitb_smpp/esme_ms_sms_storeforward.py b/example/suites/nitb_smpp/esme_ms_sms_storeforward.py
new file mode 100755
index 0000000..391a040
--- /dev/null
+++ b/example/suites/nitb_smpp/esme_ms_sms_storeforward.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+
+# This test checks following use-cases:
+# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously
+# defined in its configuration file.
+# * When SMS is sent in 'store & forward' mode, ESME fails to send an SMS to non registered MS.
+# * When SMS is sent in 'store & forward' mode, ESME can send an SMS to a not yet registered MS.
+# * When SMS is sent in 'store & forward' mode, ESME can send an SMS to an already registered MS.
+# * When SMS is sent in 'store & forward' mode, ESME receives a SMS receipt if it asked for it.
+
+from osmo_gsm_tester.testenv import *
+
+SMPP_ESME_RINVDSTADR = 0x0000000B
+
+nitb = suite.nitb()
+bts = suite.bts()
+ms = suite.modem()
+esme = suite.esme()
+
+print('start nitb and bts...')
+nitb.bts_add(bts)
+nitb.smsc.esme_add(esme)
+nitb.start()
+bts.start()
+wait(nitb.bts_is_connected, bts)
+
+esme.connect()
+nitb.subscriber_add(ms)
+
+wrong_msisdn = ms.msisdn + esme.msisdn
+print('sending sms with wrong msisdn %s, it will fail' % wrong_msisdn)
+msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest')
+esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send_wait_resp, msg, esme.MSGMODE_STOREFORWARD)
+
+print('sending sms, it will be stored...')
+msg = Sms(esme.msisdn, ms.msisdn, 'smpp send not-yet-registered message')
+umref = esme.sms_send_wait_resp(msg, esme.MSGMODE_STOREFORWARD, receipt=True)
+
+print('MS registers and will receive the SMS...')
+ms.connect(nitb.mcc_mnc())
+wait(ms.is_connected, nitb.mcc_mnc())
+wait(nitb.subscriber_attached, ms)
+wait(ms.sms_was_received, msg)
+print('Waiting to receive and consume sms receipt with reference', umref)
+wait(esme.receipt_was_received, umref)
+
+print('checking MS can receive SMS while registered...')
+msg = Sms(esme.msisdn, ms.msisdn, 'smpp send already-registered message')
+umref = esme.sms_send_wait_resp(msg, esme.MSGMODE_STOREFORWARD, receipt=True)
+wait(ms.sms_was_received, msg)
+print('Waiting to receive and consume sms receipt with reference', umref)
+wait(esme.receipt_was_received, umref)
+esme.disconnect()
diff --git a/example/suites/nitb_smpp/esme_ms_sms_transaction.py b/example/suites/nitb_smpp/esme_ms_sms_transaction.py
new file mode 100755
index 0000000..adc9dae
--- /dev/null
+++ b/example/suites/nitb_smpp/esme_ms_sms_transaction.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+
+# This test checks following use-cases:
+# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously
+# defined in its configuration file.
+# * When SMS is sent in 'transaction' mode, ESME can send an SMS to an already registered MS.
+# * When SMS is sent in 'transaction' mode, ESME fails to send an SMS to non registered MS.
+
+from osmo_gsm_tester.testenv import *
+
+SMPP_ESME_RINVDSTADR = 0x0000000B
+
+nitb = suite.nitb()
+bts = suite.bts()
+ms = suite.modem()
+esme = suite.esme()
+
+print('start nitb and bts...')
+nitb.bts_add(bts)
+nitb.smsc.esme_add(esme)
+nitb.start()
+bts.start()
+wait(nitb.bts_is_connected, bts)
+
+esme.connect()
+nitb.subscriber_add(ms)
+ms.connect(nitb.mcc_mnc())
+
+ms.log_info()
+print('waiting for modem to attach...')
+wait(ms.is_connected, nitb.mcc_mnc())
+wait(nitb.subscriber_attached, ms)
+
+print('sending first sms...')
+msg = Sms(esme.msisdn, ms.msisdn, 'smpp send message')
+esme.sms_send(msg, esme.MSGMODE_TRANSACTION)
+wait(ms.sms_was_received, msg)
+
+print('sending second sms (unicode chars not in gsm aplhabet)...')
+msg = Sms(esme.msisdn, ms.msisdn, 'chars:[кизаçйж]')
+esme.sms_send(msg, esme.MSGMODE_TRANSACTION)
+wait(ms.sms_was_received, msg)
+
+wrong_msisdn = ms.msisdn + esme.msisdn
+print('sending third sms (with wrong msisdn %s)' % wrong_msisdn)
+msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest')
+esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send_wait_resp, msg, esme.MSGMODE_TRANSACTION)
+
+esme.disconnect()
diff --git a/example/suites/nitb_smpp/suite.conf b/example/suites/nitb_smpp/suite.conf
new file mode 100644
index 0000000..eb5dc01
--- /dev/null
+++ b/example/suites/nitb_smpp/suite.conf
@@ -0,0 +1,12 @@
+resources:
+ ip_address:
+ - times: 1
+ bts:
+ - times: 1
+ modem:
+ - times: 1
+ features:
+ - sms
+
+defaults:
+ timeout: 60s
diff --git a/example/suites/nitb_sms/mo_mt_sms.py b/example/suites/nitb_sms/mo_mt_sms.py
new file mode 100755
index 0000000..10897ff
--- /dev/null
+++ b/example/suites/nitb_sms/mo_mt_sms.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+nitb = suite.nitb()
+bts = suite.bts()
+ms_mo = suite.modem()
+ms_mt = suite.modem()
+
+print('start nitb and bts...')
+nitb.bts_add(bts)
+nitb.start()
+bts.start()
+wait(nitb.bts_is_connected, bts)
+
+nitb.subscriber_add(ms_mo)
+nitb.subscriber_add(ms_mt)
+
+ms_mo.connect(nitb.mcc_mnc())
+ms_mt.connect(nitb.mcc_mnc())
+
+ms_mo.log_info()
+ms_mt.log_info()
+
+print('waiting for modems to attach...')
+wait(ms_mo.is_connected, nitb.mcc_mnc())
+wait(ms_mt.is_connected, nitb.mcc_mnc())
+wait(nitb.subscriber_attached, ms_mo, ms_mt)
+
+sms = ms_mo.sms_send(ms_mt)
+wait(ms_mt.sms_was_received, sms)
diff --git a/example/suites/nitb_sms/suite.conf b/example/suites/nitb_sms/suite.conf
new file mode 100644
index 0000000..485402b
--- /dev/null
+++ b/example/suites/nitb_sms/suite.conf
@@ -0,0 +1,12 @@
+resources:
+ ip_address:
+ - times: 1
+ bts:
+ - times: 1
+ modem:
+ - times: 2
+ features:
+ - sms
+
+defaults:
+ timeout: 60s
diff --git a/example/suites/nitb_ussd/assert_extension.py b/example/suites/nitb_ussd/assert_extension.py
new file mode 100755
index 0000000..8ccab2d
--- /dev/null
+++ b/example/suites/nitb_ussd/assert_extension.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+USSD_COMMAND_GET_EXTENSION = '*#100#'
+
+nitb = suite.nitb()
+bts = suite.bts()
+ms = suite.modem()
+
+print('start nitb and bts...')
+nitb.bts_add(bts)
+nitb.start()
+bts.start()
+wait(nitb.bts_is_connected, bts)
+
+nitb.subscriber_add(ms)
+
+ms.connect(nitb.mcc_mnc())
+ms.log_info()
+
+print('waiting for modems to attach...')
+wait(ms.is_connected, nitb.mcc_mnc())
+wait(nitb.subscriber_attached, ms)
+
+# ofono (qmi) currently changes state to 'registered' jut after sending
+# 'Location Update Request', but before receiving 'Location Updating Accept'.
+# Which means we can reach lines below and send USSD code while still not being
+# attached, which will then fail. See OsmoGsmTester #2239 for more detailed
+# information.
+# Until we find an ofono fix or a better way to workaround this, let's just
+# sleep for a while in order to receive the 'Location Updating Accept' message
+# before attemting to send the USSD.
+sleep(10)
+
+print('Sending ussd code %s' % USSD_COMMAND_GET_EXTENSION)
+response = ms.ussd_send(USSD_COMMAND_GET_EXTENSION)
+assert ' ' + ms.msisdn + '\r' in response
diff --git a/example/suites/nitb_ussd/suite.conf b/example/suites/nitb_ussd/suite.conf
new file mode 100644
index 0000000..232a5d8
--- /dev/null
+++ b/example/suites/nitb_ussd/suite.conf
@@ -0,0 +1,12 @@
+resources:
+ ip_address:
+ - times: 1
+ bts:
+ - times: 1
+ modem:
+ - times: 1
+ features:
+ - ussd
+
+defaults:
+ timeout: 60s
diff --git a/example/suites/smpp/esme_connect_policy_acceptall.py b/example/suites/smpp/esme_connect_policy_acceptall.py
new file mode 100755
index 0000000..168b4f3
--- /dev/null
+++ b/example/suites/smpp/esme_connect_policy_acceptall.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+
+# This test checks following use-cases while in 'accept-all' policy:
+# * SMPP interface of SMSC accepts SMPP clients (ESMEs) which do not appear on
+# the config file
+
+from osmo_gsm_tester.testenv import *
+
+hlr = suite.hlr()
+mgw_msc = suite.mgw()
+stp = suite.stp()
+msc = suite.msc(hlr, mgw_msc, stp)
+smsc = msc.smsc
+esme = suite.esme()
+
+# Here we deliberately omit calling smsc.esme_add() to avoid having it included
+# in the smsc config.
+smsc.set_smsc_policy(smsc.SMSC_POLICY_ACCEPT_ALL)
+esme.set_smsc(smsc)
+
+stp.start()
+hlr.start()
+msc.start()
+mgw_msc.start()
+
+# Due to accept-all policy, connect() should work even if we didn't previously
+# configure the esme in the smsc, no matter the system_id / password we use.
+log('Test connect with non-empty values in system_id and password')
+esme.set_system_id('foo')
+esme.set_password('bar')
+esme.connect()
+esme.disconnect()
+
+log('Test connect with empty values in system_id and password')
+esme.set_system_id('')
+esme.set_password('')
+esme.connect()
+esme.disconnect()
diff --git a/example/suites/smpp/esme_connect_policy_closed.py b/example/suites/smpp/esme_connect_policy_closed.py
new file mode 100755
index 0000000..487e5a4
--- /dev/null
+++ b/example/suites/smpp/esme_connect_policy_closed.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+
+# This test checks following use-cases while in 'closed' policy:
+# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously
+# defined in its configuration file.
+# * SMPP interface of SMSC rejects ESMEs with known system id but wrong password.
+# * SMPP interface of SMSC rejects ESEMs with unknown system id
+
+from osmo_gsm_tester.testenv import *
+
+SMPP_ESME_RINVPASWD = 0x0000000E
+SMPP_ESME_RINVSYSID = 0x0000000F
+
+hlr = suite.hlr()
+bts = suite.bts()
+mgw_msc = suite.mgw()
+stp = suite.stp()
+msc = suite.msc(hlr, mgw_msc, stp)
+smsc = msc.smsc
+
+esme = suite.esme()
+esme_no_pwd = suite.esme()
+esme_no_pwd.set_password('')
+
+smsc.set_smsc_policy(smsc.SMSC_POLICY_CLOSED)
+smsc.esme_add(esme)
+smsc.esme_add(esme_no_pwd)
+
+stp.start()
+hlr.start()
+msc.start()
+mgw_msc.start()
+
+log('Test with correct credentials (no password)')
+esme_no_pwd.connect()
+esme_no_pwd.disconnect()
+
+log('Test with correct credentials (no password, non empty)')
+esme_no_pwd.set_password('foobar')
+esme_no_pwd.connect()
+esme_no_pwd.disconnect()
+
+log('Test with correct credentials')
+esme.connect()
+esme.disconnect()
+
+log('Test with bad password, checking for failure')
+correct_password = esme.password
+new_password = 'barfoo' if correct_password == 'foobar' else 'foobar'
+esme.set_password(new_password)
+esme.run_method_expect_failure(SMPP_ESME_RINVPASWD, esme.connect)
+esme.set_password(correct_password)
+
+log('Test with bad system_id, checking for failure')
+correct_system_id = esme.system_id
+new_system_id = 'barfoo' if correct_system_id == 'foobar' else 'foobar'
+esme.set_system_id(new_system_id)
+esme.run_method_expect_failure(SMPP_ESME_RINVSYSID, esme.connect)
+esme.set_system_id(correct_system_id)
diff --git a/example/suites/smpp/esme_ms_sms_storeforward.py b/example/suites/smpp/esme_ms_sms_storeforward.py
new file mode 100755
index 0000000..681bc29
--- /dev/null
+++ b/example/suites/smpp/esme_ms_sms_storeforward.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+
+# This test checks following use-cases:
+# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously
+# defined in its configuration file.
+# * When SMS is sent in 'store & forward' mode, ESME fails to send an SMS to non registered MS.
+# * When SMS is sent in 'store & forward' mode, ESME can send an SMS to a not yet registered MS.
+# * When SMS is sent in 'store & forward' mode, ESME can send an SMS to an already registered MS.
+# * When SMS is sent in 'store & forward' mode, ESME receives a SMS receipt if it asked for it.
+
+from osmo_gsm_tester.testenv import *
+
+SMPP_ESME_RINVDSTADR = 0x0000000B
+
+hlr = suite.hlr()
+bts = suite.bts()
+mgw_msc = suite.mgw()
+mgw_bsc = suite.mgw()
+stp = suite.stp()
+msc = suite.msc(hlr, mgw_msc, stp)
+bsc = suite.bsc(msc, mgw_bsc, stp)
+bsc.bts_add(bts)
+
+ms = suite.modem()
+esme = suite.esme()
+msc.smsc.esme_add(esme)
+
+hlr.start()
+stp.start()
+msc.start()
+mgw_msc.start()
+mgw_bsc.start()
+bsc.start()
+bts.start()
+wait(bsc.bts_is_connected, bts)
+
+esme.connect()
+hlr.subscriber_add(ms)
+
+wrong_msisdn = ms.msisdn + esme.msisdn
+print('sending sms with wrong msisdn %s, it will be stored but not delivered' % wrong_msisdn)
+msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest')
+# Since osmo-msc 1e67fea7ba5c6336, we accept all sms in store&forward mode without looking at HLR
+# esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send_wait_resp, msg, esme.MSGMODE_STOREFORWARD)
+umref_wrong = esme.sms_send_wait_resp(msg, esme.MSGMODE_STOREFORWARD, receipt=True)
+
+print('sending sms, it will be stored...')
+msg = Sms(esme.msisdn, ms.msisdn, 'smpp send not-yet-registered message')
+umref = esme.sms_send_wait_resp(msg, esme.MSGMODE_STOREFORWARD, receipt=True)
+
+print('MS registers and will receive the SMS...')
+ms.connect(msc.mcc_mnc())
+wait(ms.is_connected, msc.mcc_mnc())
+wait(msc.subscriber_attached, ms)
+wait(ms.sms_was_received, msg)
+print('Waiting to receive and consume sms receipt with reference', umref)
+wait(esme.receipt_was_received, umref)
+
+print('checking MS can receive SMS while registered...')
+msg = Sms(esme.msisdn, ms.msisdn, 'smpp send already-registered message')
+umref = esme.sms_send_wait_resp(msg, esme.MSGMODE_STOREFORWARD, receipt=True)
+wait(ms.sms_was_received, msg)
+print('Waiting to receive and consume sms receipt with reference', umref)
+wait(esme.receipt_was_received, umref)
+
+print('Asserting the sms with wrong msisdn was not delivered', umref_wrong)
+assert not esme.receipt_was_received(umref_wrong)
+
+esme.disconnect()
diff --git a/example/suites/smpp/esme_ms_sms_transaction.py b/example/suites/smpp/esme_ms_sms_transaction.py
new file mode 100755
index 0000000..16b01cc
--- /dev/null
+++ b/example/suites/smpp/esme_ms_sms_transaction.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+
+# This test checks following use-cases:
+# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously
+# defined in its configuration file.
+# * When SMS is sent in 'transaction' mode, ESME can send an SMS to an already registered MS.
+# * When SMS is sent in 'transaction' mode, ESME fails to send an SMS to non registered MS.
+
+from osmo_gsm_tester.testenv import *
+
+SMPP_ESME_RINVDSTADR = 0x0000000B
+
+hlr = suite.hlr()
+bts = suite.bts()
+mgw_msc = suite.mgw()
+mgw_bsc = suite.mgw()
+stp = suite.stp()
+msc = suite.msc(hlr, mgw_msc, stp)
+bsc = suite.bsc(msc, mgw_bsc, stp)
+bsc.bts_add(bts)
+
+ms = suite.modem()
+esme = suite.esme()
+msc.smsc.esme_add(esme)
+
+hlr.start()
+stp.start()
+msc.start()
+mgw_msc.start()
+mgw_bsc.start()
+bsc.start()
+bts.start()
+wait(bsc.bts_is_connected, bts)
+
+esme.connect()
+hlr.subscriber_add(ms)
+ms.connect(msc.mcc_mnc())
+
+ms.log_info()
+print('waiting for modem to attach...')
+wait(ms.is_connected, msc.mcc_mnc())
+wait(msc.subscriber_attached, ms)
+
+print('sending first sms...')
+msg = Sms(esme.msisdn, ms.msisdn, 'smpp send message')
+esme.sms_send(msg, esme.MSGMODE_TRANSACTION)
+wait(ms.sms_was_received, msg)
+
+print('sending second sms (unicode chars not in gsm aplhabet)...')
+msg = Sms(esme.msisdn, ms.msisdn, 'chars:[кизаçйж]')
+esme.sms_send(msg, esme.MSGMODE_TRANSACTION)
+wait(ms.sms_was_received, msg)
+
+wrong_msisdn = ms.msisdn + esme.msisdn
+print('sending third sms (with wrong msisdn %s)' % wrong_msisdn)
+msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest')
+esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send_wait_resp, msg, esme.MSGMODE_TRANSACTION)
+
+esme.disconnect()
diff --git a/example/suites/smpp/suite.conf b/example/suites/smpp/suite.conf
new file mode 100644
index 0000000..61e7015
--- /dev/null
+++ b/example/suites/smpp/suite.conf
@@ -0,0 +1,12 @@
+resources:
+ ip_address:
+ - times: 6 # msc, bsc, hlr, stp, mgw*2
+ bts:
+ - times: 1
+ modem:
+ - times: 1
+ features:
+ - sms
+
+defaults:
+ timeout: 60s
diff --git a/example/suites/sms/mo_mt_sms.py b/example/suites/sms/mo_mt_sms.py
new file mode 100755
index 0000000..7654ea6
--- /dev/null
+++ b/example/suites/sms/mo_mt_sms.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+hlr = suite.hlr()
+bts = suite.bts()
+mgw_msc = suite.mgw()
+mgw_bsc = suite.mgw()
+stp = suite.stp()
+msc = suite.msc(hlr, mgw_msc, stp)
+bsc = suite.bsc(msc, mgw_bsc, stp)
+ms_mo = suite.modem()
+ms_mt = suite.modem()
+
+hlr.start()
+stp.start()
+msc.start()
+mgw_msc.start()
+mgw_bsc.start()
+
+bsc.bts_add(bts)
+bsc.start()
+
+bts.start()
+wait(bsc.bts_is_connected, bts)
+
+hlr.subscriber_add(ms_mo)
+hlr.subscriber_add(ms_mt)
+
+ms_mo.connect(msc.mcc_mnc())
+ms_mt.connect(msc.mcc_mnc())
+
+ms_mo.log_info()
+ms_mt.log_info()
+
+print('waiting for modems to attach...')
+wait(ms_mo.is_connected, msc.mcc_mnc())
+wait(ms_mt.is_connected, msc.mcc_mnc())
+wait(msc.subscriber_attached, ms_mo, ms_mt)
+
+sms = ms_mo.sms_send(ms_mt)
+wait(ms_mt.sms_was_received, sms)
diff --git a/example/suites/sms/suite.conf b/example/suites/sms/suite.conf
new file mode 100644
index 0000000..28a81ea
--- /dev/null
+++ b/example/suites/sms/suite.conf
@@ -0,0 +1,9 @@
+resources:
+ ip_address:
+ - times: 6 # msc, bsc, hlr, stp, mgw*2
+ bts:
+ - times: 1
+ modem:
+ - times: 2
+ features:
+ - sms
diff --git a/example/suites/ussd/assert_extension.py b/example/suites/ussd/assert_extension.py
new file mode 100755
index 0000000..475de09
--- /dev/null
+++ b/example/suites/ussd/assert_extension.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+USSD_COMMAND_GET_EXTENSION = '*#100#'
+
+hlr = suite.hlr()
+bts = suite.bts()
+mgw_msc = suite.mgw()
+mgw_bsc = suite.mgw()
+stp = suite.stp()
+msc = suite.msc(hlr, mgw_msc, stp)
+bsc = suite.bsc(msc, mgw_bsc, stp)
+ms = suite.modem()
+
+hlr.start()
+stp.start()
+msc.start()
+mgw_msc.start()
+mgw_bsc.start()
+
+bsc.bts_add(bts)
+bsc.start()
+
+bts.start()
+wait(bsc.bts_is_connected, bts)
+
+hlr.subscriber_add(ms)
+
+ms.connect(msc.mcc_mnc())
+
+ms.log_info()
+
+print('waiting for modems to attach...')
+wait(ms.is_connected, msc.mcc_mnc())
+wait(msc.subscriber_attached, ms)
+
+# ofono (qmi) currently changes state to 'registered' jut after sending
+# 'Location Update Request', but before receiving 'Location Updating Accept'.
+# Which means we can reach lines below and send USSD code while still not being
+# attached, which will then fail. See OsmoGsmTester #2239 for more detailed
+# information.
+# Until we find an ofono fix or a better way to workaround this, let's just
+# sleep for a while in order to receive the 'Location Updating Accept' message
+# before attemting to send the USSD.
+sleep(10)
+
+print('Sending ussd code %s' % USSD_COMMAND_GET_EXTENSION)
+response = ms.ussd_send(USSD_COMMAND_GET_EXTENSION)
+log('got ussd response: %r' % repr(response))
+assert response.endswith(' ' + ms.msisdn)
diff --git a/example/suites/ussd/suite.conf b/example/suites/ussd/suite.conf
new file mode 100644
index 0000000..460147a
--- /dev/null
+++ b/example/suites/ussd/suite.conf
@@ -0,0 +1,9 @@
+resources:
+ ip_address:
+ - times: 6 # msc, bsc, hlr, stp, mgw*2
+ bts:
+ - times: 1
+ modem:
+ - times: 1
+ features:
+ - ussd
diff --git a/example/suites/voice/lib/testlib.py b/example/suites/voice/lib/testlib.py
new file mode 100644
index 0000000..7d934f1
--- /dev/null
+++ b/example/suites/voice/lib/testlib.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+def test_mo_mt_call(use_osmux=False, force_osmux=False):
+ hlr = suite.hlr()
+ bts = suite.bts()
+ mgw_msc = suite.mgw()
+ mgw_bsc = suite.mgw()
+ stp = suite.stp()
+ msc = suite.msc(hlr, mgw_msc, stp)
+ bsc = suite.bsc(msc, mgw_bsc, stp)
+ ms_mo = suite.modem()
+ ms_mt = suite.modem()
+
+ hlr.start()
+ stp.start()
+
+ msc.set_use_osmux(use_osmux, force_osmux)
+ msc.start()
+
+ # osmo-msc still doesn't support linking 2 internal leg calls through Osmux
+ # if both calls are using Osmux. Currently, RTP is always used between the 2
+ # endpoints of the MGW. See OS#4065.
+ mgw_msc.set_use_osmux(use_osmux, False)
+ mgw_msc.start()
+
+ # We don't want to force Osmux in BSC_MGW since in MGW BTS-side is still RTP.
+ mgw_bsc.set_use_osmux(use_osmux, False)
+ mgw_bsc.start()
+
+ bsc.set_use_osmux(use_osmux, force_osmux)
+ bsc.bts_add(bts)
+ bsc.start()
+
+ bts.start()
+ wait(bsc.bts_is_connected, bts)
+
+ hlr.subscriber_add(ms_mo)
+ hlr.subscriber_add(ms_mt)
+
+ ms_mo.connect(msc.mcc_mnc())
+ ms_mt.connect(msc.mcc_mnc())
+
+ ms_mo.log_info()
+ ms_mt.log_info()
+
+ print('waiting for modems to attach...')
+ wait(ms_mo.is_connected, msc.mcc_mnc())
+ wait(ms_mt.is_connected, msc.mcc_mnc())
+ wait(msc.subscriber_attached, ms_mo, ms_mt)
+
+ assert len(ms_mo.call_id_list()) == 0 and len(ms_mt.call_id_list()) == 0
+ mo_cid = ms_mo.call_dial(ms_mt)
+ mt_cid = ms_mt.call_wait_incoming(ms_mo)
+ print('dial success')
+
+ assert not ms_mo.call_is_active(mo_cid) and not ms_mt.call_is_active(mt_cid)
+ ms_mt.call_answer(mt_cid)
+ wait(ms_mo.call_is_active, mo_cid)
+ wait(ms_mt.call_is_active, mt_cid)
+ print('answer success, call established and ongoing')
+
+ sleep(5) # maintain the call active for 5 seconds
+
+ assert ms_mo.call_is_active(mo_cid) and ms_mt.call_is_active(mt_cid)
+ ms_mt.call_hangup(mt_cid)
+ wait(lambda: len(ms_mo.call_id_list()) == 0 and len(ms_mt.call_id_list()) == 0)
+ print('hangup success')
diff --git a/example/suites/voice/mo_mt_call.py b/example/suites/voice/mo_mt_call.py
new file mode 100755
index 0000000..740b1fe
--- /dev/null
+++ b/example/suites/voice/mo_mt_call.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+import testlib
+suite.test_import_modules_register_for_cleanup(testlib)
+from testlib import test_mo_mt_call
+
+test_mo_mt_call(False, False)
diff --git a/example/suites/voice/mo_mt_call_osmux.py b/example/suites/voice/mo_mt_call_osmux.py
new file mode 100755
index 0000000..acf7d71
--- /dev/null
+++ b/example/suites/voice/mo_mt_call_osmux.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.testenv import *
+
+import testlib
+suite.test_import_modules_register_for_cleanup(testlib)
+from testlib import test_mo_mt_call
+
+test_mo_mt_call(True, True)
diff --git a/example/suites/voice/suite.conf b/example/suites/voice/suite.conf
new file mode 100644
index 0000000..522b1ca
--- /dev/null
+++ b/example/suites/voice/suite.conf
@@ -0,0 +1,9 @@
+resources:
+ ip_address:
+ - times: 6 # msc, bsc, hlr, stp, mgw*2
+ bts:
+ - times: 1
+ modem:
+ - times: 2
+ features:
+ - voice