[PATCH] osmo-gsm-tester[master]: ofono_client: Implement network registration during connect()

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

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

Pau Espin Pedrol gerrit-no-reply at lists.osmocom.org
Wed Jun 7 15:38:03 UTC 2017


Hello Neels Hofmeyr, Jenkins Builder,

I'd like you to reexamine a change.  Please visit

    https://gerrit.osmocom.org/2779

to look at the new patch set (#8).

ofono_client: Implement network registration during connect()

A new mncc_mnc parameter is now optionally passed to connect() in order
to manually register to a specific network with a given MCC+MNC pair.
If no parameter is passed (or None), then the modem will be instructed
to attempt an automatic registration with any available network which
permits it.

We get the MNC+MCC parameter from the MSC/NITB and we pass it to the
modem object at connect time as shown in the modified tests. Two new
simple tests to check network registration is working are added in this
commit.

Ofono modems seem to be automatically registering at some point after
they are set Online=true, and we were actually using that 'feature'
before this patch. Thus, it is possible that a modem quickly becomes
registered, and we then check so before starting the scan+registration
process, which can take a few seconds.

The scanning method can take a few seconds to complete. To avoid
blocking in the dbus ofono Scan() method, this commit adds some code to
make use of glib/gdbus async methods, which are not yet supported
directly by pydbus. This way, we can continue polling while waiting for
the scan process to complete. When it completes, a callback is run which
attempts to register. If no MNC+MCC was passed, as we just finished
scanning the modem should have enough fresh operator information to take
good and quick decisions on where to connect. If we have an MNC+MCC,
then we check the operator list received by Scan() method. If operator
with desired MCC+MNC is there, we register with it. If it's not there,
we start scanning() again asynchronously hoping the operator will show
up in next scan.i

As scanning() and registration is done in the background, tests are
expected to call connect(), and then later on wait for the modem to
register by waiting/polling the method "modem.is_connected()"

Change-Id: I8d9eb47eac1044550d3885adb55105c304b0c15c
---
M src/osmo_gsm_tester/ofono_client.py
M src/osmo_gsm_tester/osmo_msc.py
M src/osmo_gsm_tester/osmo_nitb.py
M suites/aoip_debug/interactive.py
M suites/aoip_sms/mo_mt_sms.py
M suites/debug/interactive.py
A suites/netreg/register.py
A suites/netreg/register_default.py
A suites/netreg/suite.conf
M suites/sms/mo_mt_sms.py
10 files changed, 228 insertions(+), 10 deletions(-)


  git pull ssh://gerrit.osmocom.org:29418/osmo-gsm-tester refs/changes/79/2779/8

diff --git a/src/osmo_gsm_tester/ofono_client.py b/src/osmo_gsm_tester/ofono_client.py
index 46300ec..58ee6dc 100644
--- a/src/osmo_gsm_tester/ofono_client.py
+++ b/src/osmo_gsm_tester/ofono_client.py
@@ -22,6 +22,7 @@
 from pydbus import SystemBus, Variant
 import time
 import pprint
+import sys
 
 from gi.repository import GLib
 glib_main_loop = GLib.MainLoop()
@@ -31,6 +32,12 @@
 I_MODEM = 'org.ofono.Modem'
 I_NETREG = 'org.ofono.NetworkRegistration'
 I_SMS = 'org.ofono.MessageManager'
+
+# See https://github.com/intgr/ofono/blob/master/doc/network-api.txt#L78
+NETREG_ST_REGISTERED = 'registered'
+NETREG_ST_ROAMING = 'roaming'
+
+NETREG_MAX_REGISTER_ATTEMPTS = 3
 
 class DeferredHandling:
     defer_queue = []
@@ -72,6 +79,53 @@
 def list_modems():
     root = systembus_get('/')
     return sorted(root.GetModems())
+
+def _async_result_handler(obj, result, user_data):
+    '''Generic callback dispatcher called from glib loop when an async method
+    call has returned. This callback is set up by method dbus_async_call.'''
+    (result_callback, error_callback, real_user_data) = user_data
+    try:
+        ret = obj.call_finish(result)
+    except Exception as e:
+        # return exception as value
+        if error_callback:
+            error_callback(obj, e, real_user_data)
+        else:
+            result_callback(obj, e, real_user_data)
+        return
+
+    ret = ret.unpack()
+    # to be compatible with standard Python behaviour, unbox
+    # single-element tuples and return None for empty result tuples
+    if len(ret) == 1:
+        ret = ret[0]
+    elif len(ret) == 0:
+        ret = None
+    result_callback(obj, ret, real_user_data)
+
+def dbus_async_call(instance, proxymethod, *proxymethod_args,
+                    result_handler=None, error_handler=None,
+                    user_data=None, timeout=30,
+                    **proxymethod_kwargs):
+    '''pydbus doesn't support asynchronous methods. This method adds support for
+    it until pydbus implements it'''
+
+    argdiff = len(proxymethod_args) - len(proxymethod._inargs)
+    if argdiff < 0:
+        raise TypeError(proxymethod.__qualname__ + " missing {} required positional argument(s)".format(-argdiff))
+    elif argdiff > 0:
+        raise TypeError(proxymethod.__qualname__ + " takes {} positional argument(s) but {} was/were given".format(len(proxymethod._inargs), len(proxymethod_args)))
+
+    timeout = timeout * 1000
+    user_data = (result_handler, error_handler, user_data)
+
+    ret = instance._bus.con.call(
+        instance._bus_name, instance._path,
+        proxymethod._iface_name, proxymethod.__name__,
+        GLib.Variant(proxymethod._sinargs, proxymethod_args),
+        GLib.VariantType.new(proxymethod._soutargs),
+        0, timeout, None,
+        _async_result_handler, user_data)
 
 class ModemDbusInteraction(log.Origin):
     '''Work around inconveniences specific to pydbus and ofono.
@@ -255,8 +309,10 @@
         self.path = conf.get('path')
         self.set_name(self.path)
         self.set_log_category(log.C_TST)
+        self.mcc_mnc = None
         self.sms_received_list = []
         self.dbus = ModemDbusInteraction(self.path)
+        self.register_attempts = 0
         self.dbus.required_signals = {
                 I_SMS: ( ('IncomingMessage', self._on_incoming_message), ),
                 I_NETREG: ( ('PropertyChanged', self._on_netreg_property_changed), ),
@@ -323,18 +379,99 @@
     def _on_netreg_property_changed(self, name, value):
         self.dbg('%r.PropertyChanged() -> %s=%s' % (I_NETREG, name, value))
 
-    def connect(self, nitb):
-        'set the modem up to connect to MCC+MNC from NITB config'
-        self.log('connect to', nitb)
+    def is_connected(self, mcc_mnc=None):
+        netreg = self.dbus.interface(I_NETREG)
+        prop = netreg.GetProperties()
+        status = prop.get('Status')
+        if not (status == NETREG_ST_REGISTERED or status == NETREG_ST_ROAMING):
+            return False
+        if mcc_mnc is None: # Any network is fine and we are registered.
+            return True
+        mcc = prop.get('MobileCountryCode')
+        mnc = prop.get('MobileNetworkCode')
+        if (mcc, mnc) == mcc_mnc:
+            return True
+        return False
+
+    def schedule_scan_register(self):
+        if self.register_attempts > NETREG_MAX_REGISTER_ATTEMPTS:
+            self.raise_exn('Failed to find Network Operator', mcc_mnc=self.mcc_mnc, attempts=self.register_attempts)
+        self.register_attempts += 1
+        netreg = self.dbus.interface(I_NETREG)
+        self.dbg('Scanning for operators...')
+        # Scan method can take several seconds, and we don't want to block
+        # waiting for that. Make it async and try to register when the scan is
+        # finished.
+        dbus_async_call(netreg, netreg.Scan, timeout=30, result_handler=self.scan_result_cb,
+                        error_handler=self.scan_error_cb, user_data=None)
+
+    def scan_error_cb(self, obj, e, user_data):
+        self.raise_exn('Scan() failed:', e)
+
+    def scan_result_cb(self, obj, result, user_data):
+        self.register(result)
+
+    def register_automatic(self):
+        if self.is_connected():
+            self.log('Already registered with a network')
+            return
+        self.log('Registering with the default network')
+        netreg = self.dbus.interface(I_NETREG)
+        netreg.Register()
+
+    def register(self, scanned_operators):
+        if self.mcc_mnc is None:
+            self.register_automatic()
+            return
+        self.dbg('scanned operators: ', scanned_operators);
+        matching_op_path = None
+        for op in scanned_operators:
+            op_path, op_prop = op
+            mcc = op_prop.get('MobileCountryCode')
+            mnc = op_prop.get('MobileNetworkCode')
+            if (mcc, mnc) == self.mcc_mnc:
+                if op_prop.get('Status') == 'current':
+                    self.log('Already registered with network', self.mcc_mnc)
+                    # We discovered the network and we are already registered
+                    # with it. Avoid calling op.Register() in this case (it
+                    # won't act as a NO-OP, it actually returns an error).
+                    return
+                matching_op_path = op_path
+                break
+        if matching_op_path is None:
+            self.dbg('Failed to find Network Operator', mcc_mnc=self.mcc_mnc, attempts=self.register_attempts)
+            self.schedule_scan_register()
+            return
+        dbus_op = systembus_get(matching_op_path)
+        self.log('Registering with operator', matching_op_path, self.mcc_mnc)
+        dbus_op.Register()
+
+    def power_cycle(self):
+        'Power the modem and put it online, power cycle it if it was already on'
         if self.is_powered():
-            self.dbg('is powered')
+            self.dbg('Power cycling')
             self.set_online(False)
             self.set_powered(False)
             event_loop.wait(self, lambda: not self.dbus.has_interface(I_NETREG, I_SMS), timeout=10)
+        else:
+            self.dbg('Powering on')
         self.set_powered()
         self.set_online()
         event_loop.wait(self, self.dbus.has_interface, I_NETREG, I_SMS, timeout=10)
 
+    def connect(self, mcc_mnc=None):
+        'Connect to MCC+MNC'
+        if (mcc_mnc is not None) and (len(mcc_mnc) != 2 or None in mcc_mnc):
+            self.raise_exn('mcc_mnc value is invalid. It should be None or contain both valid mcc and mnc values:', mcc_mnc=mcc_mnc)
+        self.mcc_mnc = mcc_mnc
+        self.power_cycle()
+        self.register_attempts = 0
+        if self.is_connected(self.mcc_mnc):
+            self.log('Already registered with', self.mcc_mnc if self.mcc_mnc else 'default network')
+        else:
+            self.log('Connect to', self.mcc_mnc if self.mcc_mnc else 'default network')
+            self.schedule_scan_register()
+
     def sms_send(self, to_msisdn_or_modem, *tokens):
         if isinstance(to_msisdn_or_modem, Modem):
             to_msisdn = to_msisdn_or_modem.msisdn
diff --git a/src/osmo_gsm_tester/osmo_msc.py b/src/osmo_gsm_tester/osmo_msc.py
index ca0e246..d53735b 100644
--- a/src/osmo_gsm_tester/osmo_msc.py
+++ b/src/osmo_gsm_tester/osmo_msc.py
@@ -29,6 +29,7 @@
     config_file = None
     process = None
     hlr = None
+    config = None
 
     def __init__(self, suite_run, hlr, mgcpgw, ip_address):
         self.suite_run = suite_run
@@ -72,6 +73,7 @@
         config.overlay(values, self.suite_run.config())
         config.overlay(values, dict(msc=dict(ip_address=self.ip_address)))
         config.overlay(values, self.mgcpgw.conf_for_msc())
+        self.config = values
 
         self.dbg('MSC CONFIG:\n' + pprint.pformat(values))
 
@@ -83,6 +85,15 @@
     def addr(self):
         return self.ip_address.get('addr')
 
+    def mcc(self):
+        return self.config['msc']['net']['mcc']
+
+    def mnc(self):
+        return self.config['msc']['net']['mnc']
+
+    def mcc_mnc(self):
+        return (self.mcc(), self.mnc())
+
     def subscriber_attached(self, *modems):
         return self.imsi_attached(*[m.imsi() for m in modems])
 
diff --git a/src/osmo_gsm_tester/osmo_nitb.py b/src/osmo_gsm_tester/osmo_nitb.py
index b2ddfb7..b4bf247 100644
--- a/src/osmo_gsm_tester/osmo_nitb.py
+++ b/src/osmo_gsm_tester/osmo_nitb.py
@@ -76,6 +76,7 @@
         for bts in self.bts:
             bts_list.append(bts.conf_for_bsc())
         config.overlay(values, dict(nitb=dict(net=dict(bts_list=bts_list))))
+        self.config = values
 
         self.dbg('NITB CONFIG:\n' + pprint.pformat(values))
 
@@ -91,6 +92,15 @@
         self.bts.append(bts)
         bts.set_bsc(self)
 
+    def mcc(self):
+        return self.config['nitb']['net']['mcc']
+
+    def mnc(self):
+        return self.config['nitb']['net']['mnc']
+
+    def mcc_mnc(self):
+        return (self.mcc(), self.mnc())
+
     def subscriber_add(self, modem, msisdn=None):
         if msisdn is None:
             msisdn = self.suite_run.resources_pool.next_msisdn(modem)
diff --git a/suites/aoip_debug/interactive.py b/suites/aoip_debug/interactive.py
index 7cc1b76..cfedd3a 100755
--- a/suites/aoip_debug/interactive.py
+++ b/suites/aoip_debug/interactive.py
@@ -18,7 +18,7 @@
 
 for m in modems:
   hlr.subscriber_add(m)
-  m.connect(bsc)
+  m.connect(msc.mcc_mnc())
 
 while True:
   cmd = prompt('Enter command: (q)uit (s)ms (g)et-registered (w)ait-registered')
@@ -30,6 +30,8 @@
     break
   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.')
diff --git a/suites/aoip_sms/mo_mt_sms.py b/suites/aoip_sms/mo_mt_sms.py
index b9383ea..8eba842 100755
--- a/suites/aoip_sms/mo_mt_sms.py
+++ b/suites/aoip_sms/mo_mt_sms.py
@@ -21,13 +21,15 @@
 hlr.subscriber_add(ms_mo)
 hlr.subscriber_add(ms_mt)
 
-ms_mo.connect(bsc)
-ms_mt.connect(bsc)
+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)
diff --git a/suites/debug/interactive.py b/suites/debug/interactive.py
index 603e395..37076dc 100755
--- a/suites/debug/interactive.py
+++ b/suites/debug/interactive.py
@@ -13,7 +13,7 @@
 
 for m in modems:
   nitb.subscriber_add(m)
-  m.connect(nitb)
+  m.connect(nitb.mcc_mnc())
 
 while True:
   cmd = prompt('Enter command: (q)uit (s)ms (g)et-registered (w)ait-registered')
@@ -25,6 +25,8 @@
     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.')
diff --git a/suites/netreg/register.py b/suites/netreg/register.py
new file mode 100755
index 0000000..9141986
--- /dev/null
+++ b/suites/netreg/register.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.test 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()
+
+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/suites/netreg/register_default.py b/suites/netreg/register_default.py
new file mode 100755
index 0000000..d15b3f5
--- /dev/null
+++ b/suites/netreg/register_default.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+from osmo_gsm_tester.test 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()
+
+nitb.subscriber_add(ms)
+
+ms.connect()
+
+print(ms.info())
+
+wait(ms.is_connected)
+wait(nitb.subscriber_attached, ms)
diff --git a/suites/netreg/suite.conf b/suites/netreg/suite.conf
new file mode 100644
index 0000000..1bb1dbb
--- /dev/null
+++ b/suites/netreg/suite.conf
@@ -0,0 +1,10 @@
+resources:
+  ip_address:
+  - times: 1
+  bts:
+  - times: 1
+  modem:
+  - times: 1
+
+defaults:
+  timeout: 40s
diff --git a/suites/sms/mo_mt_sms.py b/suites/sms/mo_mt_sms.py
index 860f5e7..4e0ba08 100755
--- a/suites/sms/mo_mt_sms.py
+++ b/suites/sms/mo_mt_sms.py
@@ -14,13 +14,15 @@
 nitb.subscriber_add(ms_mo)
 nitb.subscriber_add(ms_mt)
 
-ms_mo.connect(nitb)
-ms_mt.connect(nitb)
+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)

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

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



More information about the gerrit-log mailing list