2017-05-18 13:24:02 +00:00
|
|
|
# osmo_gsm_tester: specifics for running an osmo-hlr
|
|
|
|
#
|
|
|
|
# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
|
|
|
|
#
|
|
|
|
# Author: Neels Hofmeyr <neels@hofmeyr.de>
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
2017-06-03 07:51:45 +00:00
|
|
|
# it under the terms of the GNU General Public License as
|
2017-05-18 13:24:02 +00:00
|
|
|
# published by the Free Software Foundation, either version 3 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
|
2017-06-03 07:51:45 +00:00
|
|
|
# GNU General Public License for more details.
|
2017-05-18 13:24:02 +00:00
|
|
|
#
|
2017-06-03 07:51:45 +00:00
|
|
|
# You should have received a copy of the GNU General Public License
|
2017-05-18 13:24:02 +00:00
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import pprint
|
2017-08-23 15:02:38 +00:00
|
|
|
import sqlite3
|
2017-05-18 13:24:02 +00:00
|
|
|
|
2020-04-10 18:46:07 +00:00
|
|
|
from ..core import log, util, config, template, process
|
2020-04-10 17:51:31 +00:00
|
|
|
from . import pcap_recorder
|
2017-05-18 13:24:02 +00:00
|
|
|
|
|
|
|
class OsmoHlr(log.Origin):
|
|
|
|
run_dir = None
|
|
|
|
config_file = None
|
|
|
|
process = None
|
|
|
|
next_subscriber_id = 1
|
|
|
|
|
2020-05-05 10:54:37 +00:00
|
|
|
def __init__(self, testenv, ip_address):
|
fix and refactor logging: drop 'with', simplify
With the recent fix of the junit report related issues, another issue arose:
the 'with log.Origin' was changed to disallow __enter__ing an object twice to
fix problems, now still code would fail because it tries to do 'with' on the
same object twice. The only reason is to ensure that logging is associated with
a given object. Instead of complicating even more, implement differently.
Refactor logging to simplify use: drop the 'with Origin' style completely, and
instead use the python stack to determine which objects are created by which,
and which object to associate a log statement with.
The new way: we rely on the convention that each class instance has a local
'self' referencing the object instance. If we need to find an origin as a new
object's parent, or to associate a log message with, we traverse each stack
frame, fetching the first local 'self' object that is a log.Origin class
instance.
How to use:
Simply call log.log() anywhere, and it finds an Origin object to log for, from
the stack. Alternatively call self.log() for any Origin() object to skip the
lookup.
Create classes as child class of log.Origin and make sure to call
super().__init__(category, name). This constructor will magically find a parent
Origin on the stack.
When an exception happens, we first escalate the exception up through call
scopes to where ever it is handled by log.log_exn(). This then finds an Origin
object in the traceback's stack frames, no need to nest in 'with' scopes.
Hence the 'with log.Origin' now "happens implicitly", we can write pure natural
python code, no more hassles with scope ordering.
Furthermore, any frame can place additional logging information in a frame by
calling log.ctx(). This is automatically inserted in the ancestry associated
with a log statement / exception.
Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
|
|
|
super().__init__(log.C_RUN, 'osmo-hlr_%s' % ip_address.get('addr'))
|
2018-08-09 11:45:55 +00:00
|
|
|
self.run_dir = None
|
|
|
|
self.config_file = None
|
|
|
|
self.process = None
|
|
|
|
self.next_subscriber_id = 1
|
2020-05-05 10:54:37 +00:00
|
|
|
self.testenv = testenv
|
2017-05-18 13:24:02 +00:00
|
|
|
self.ip_address = ip_address
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
self.log('Starting osmo-hlr')
|
2020-05-11 08:56:52 +00:00
|
|
|
self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name()))
|
2017-05-18 13:24:02 +00:00
|
|
|
self.configure()
|
|
|
|
|
2020-05-05 10:54:37 +00:00
|
|
|
inst = util.Dir(os.path.abspath(self.testenv.suite().trial().get_inst('osmo-hlr')))
|
2017-05-18 13:24:02 +00:00
|
|
|
|
|
|
|
binary = inst.child('bin', 'osmo-hlr')
|
|
|
|
if not os.path.isfile(binary):
|
fix and refactor logging: drop 'with', simplify
With the recent fix of the junit report related issues, another issue arose:
the 'with log.Origin' was changed to disallow __enter__ing an object twice to
fix problems, now still code would fail because it tries to do 'with' on the
same object twice. The only reason is to ensure that logging is associated with
a given object. Instead of complicating even more, implement differently.
Refactor logging to simplify use: drop the 'with Origin' style completely, and
instead use the python stack to determine which objects are created by which,
and which object to associate a log statement with.
The new way: we rely on the convention that each class instance has a local
'self' referencing the object instance. If we need to find an origin as a new
object's parent, or to associate a log message with, we traverse each stack
frame, fetching the first local 'self' object that is a log.Origin class
instance.
How to use:
Simply call log.log() anywhere, and it finds an Origin object to log for, from
the stack. Alternatively call self.log() for any Origin() object to skip the
lookup.
Create classes as child class of log.Origin and make sure to call
super().__init__(category, name). This constructor will magically find a parent
Origin on the stack.
When an exception happens, we first escalate the exception up through call
scopes to where ever it is handled by log.log_exn(). This then finds an Origin
object in the traceback's stack frames, no need to nest in 'with' scopes.
Hence the 'with log.Origin' now "happens implicitly", we can write pure natural
python code, no more hassles with scope ordering.
Furthermore, any frame can place additional logging information in a frame by
calling log.ctx(). This is automatically inserted in the ancestry associated
with a log statement / exception.
Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
|
|
|
raise log.Error('Binary missing:', binary)
|
2017-05-18 13:24:02 +00:00
|
|
|
lib = inst.child('lib')
|
|
|
|
if not os.path.isdir(lib):
|
fix and refactor logging: drop 'with', simplify
With the recent fix of the junit report related issues, another issue arose:
the 'with log.Origin' was changed to disallow __enter__ing an object twice to
fix problems, now still code would fail because it tries to do 'with' on the
same object twice. The only reason is to ensure that logging is associated with
a given object. Instead of complicating even more, implement differently.
Refactor logging to simplify use: drop the 'with Origin' style completely, and
instead use the python stack to determine which objects are created by which,
and which object to associate a log statement with.
The new way: we rely on the convention that each class instance has a local
'self' referencing the object instance. If we need to find an origin as a new
object's parent, or to associate a log message with, we traverse each stack
frame, fetching the first local 'self' object that is a log.Origin class
instance.
How to use:
Simply call log.log() anywhere, and it finds an Origin object to log for, from
the stack. Alternatively call self.log() for any Origin() object to skip the
lookup.
Create classes as child class of log.Origin and make sure to call
super().__init__(category, name). This constructor will magically find a parent
Origin on the stack.
When an exception happens, we first escalate the exception up through call
scopes to where ever it is handled by log.log_exn(). This then finds an Origin
object in the traceback's stack frames, no need to nest in 'with' scopes.
Hence the 'with log.Origin' now "happens implicitly", we can write pure natural
python code, no more hassles with scope ordering.
Furthermore, any frame can place additional logging information in a frame by
calling log.ctx(). This is automatically inserted in the ancestry associated
with a log statement / exception.
Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
|
|
|
raise log.Error('No lib/ in', inst)
|
2017-05-18 13:24:02 +00:00
|
|
|
|
|
|
|
# bootstrap an empty hlr.db
|
|
|
|
self.db_file = self.run_dir.new_file('hlr.db')
|
2018-07-03 09:56:03 +00:00
|
|
|
sql_input = inst.child('share/doc/osmo-hlr/sql/hlr.sql')
|
2017-05-18 13:24:02 +00:00
|
|
|
if not os.path.isfile(sql_input):
|
fix and refactor logging: drop 'with', simplify
With the recent fix of the junit report related issues, another issue arose:
the 'with log.Origin' was changed to disallow __enter__ing an object twice to
fix problems, now still code would fail because it tries to do 'with' on the
same object twice. The only reason is to ensure that logging is associated with
a given object. Instead of complicating even more, implement differently.
Refactor logging to simplify use: drop the 'with Origin' style completely, and
instead use the python stack to determine which objects are created by which,
and which object to associate a log statement with.
The new way: we rely on the convention that each class instance has a local
'self' referencing the object instance. If we need to find an origin as a new
object's parent, or to associate a log message with, we traverse each stack
frame, fetching the first local 'self' object that is a log.Origin class
instance.
How to use:
Simply call log.log() anywhere, and it finds an Origin object to log for, from
the stack. Alternatively call self.log() for any Origin() object to skip the
lookup.
Create classes as child class of log.Origin and make sure to call
super().__init__(category, name). This constructor will magically find a parent
Origin on the stack.
When an exception happens, we first escalate the exception up through call
scopes to where ever it is handled by log.log_exn(). This then finds an Origin
object in the traceback's stack frames, no need to nest in 'with' scopes.
Hence the 'with log.Origin' now "happens implicitly", we can write pure natural
python code, no more hassles with scope ordering.
Furthermore, any frame can place additional logging information in a frame by
calling log.ctx(). This is automatically inserted in the ancestry associated
with a log statement / exception.
Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
|
|
|
raise log.Error('hlr.sql missing:', sql_input)
|
2017-05-18 13:24:02 +00:00
|
|
|
self.run_local('create_hlr_db', ('/bin/sh', '-c', 'sqlite3 %r < %r' % (self.db_file, sql_input)))
|
|
|
|
|
2020-05-05 10:54:37 +00:00
|
|
|
pcap_recorder.PcapRecorder(self.testenv, self.run_dir.new_dir('pcap'), None,
|
2017-05-18 13:24:02 +00:00
|
|
|
'host %s' % self.addr())
|
|
|
|
|
|
|
|
env = { 'LD_LIBRARY_PATH': util.prepend_library_path(lib) }
|
|
|
|
|
|
|
|
self.dbg(run_dir=self.run_dir, binary=binary, env=env)
|
|
|
|
self.process = process.Process(self.name(), self.run_dir,
|
|
|
|
(binary,
|
|
|
|
'-c', os.path.abspath(self.config_file),
|
|
|
|
'--database', self.db_file),
|
|
|
|
env=env)
|
2020-05-05 10:54:37 +00:00
|
|
|
self.testenv.remember_to_stop(self.process)
|
2017-05-18 13:24:02 +00:00
|
|
|
self.process.launch()
|
|
|
|
|
|
|
|
def configure(self):
|
|
|
|
self.config_file = self.run_dir.new_file('osmo-hlr.cfg')
|
|
|
|
self.dbg(config_file=self.config_file)
|
|
|
|
|
|
|
|
values = dict(hlr=config.get_defaults('hlr'))
|
2020-05-05 10:54:37 +00:00
|
|
|
config.overlay(values, self.testenv.suite().config())
|
2017-05-18 13:24:02 +00:00
|
|
|
config.overlay(values, dict(hlr=dict(ip_address=self.ip_address)))
|
|
|
|
|
|
|
|
self.dbg('HLR CONFIG:\n' + pprint.pformat(values))
|
|
|
|
|
|
|
|
with open(self.config_file, 'w') as f:
|
|
|
|
r = template.render('osmo-hlr.cfg', values)
|
|
|
|
self.dbg(r)
|
|
|
|
f.write(r)
|
|
|
|
|
|
|
|
def addr(self):
|
|
|
|
return self.ip_address.get('addr')
|
|
|
|
|
|
|
|
def running(self):
|
|
|
|
return not self.process.terminated()
|
|
|
|
|
|
|
|
def run_local(self, name, popen_args):
|
fix and refactor logging: drop 'with', simplify
With the recent fix of the junit report related issues, another issue arose:
the 'with log.Origin' was changed to disallow __enter__ing an object twice to
fix problems, now still code would fail because it tries to do 'with' on the
same object twice. The only reason is to ensure that logging is associated with
a given object. Instead of complicating even more, implement differently.
Refactor logging to simplify use: drop the 'with Origin' style completely, and
instead use the python stack to determine which objects are created by which,
and which object to associate a log statement with.
The new way: we rely on the convention that each class instance has a local
'self' referencing the object instance. If we need to find an origin as a new
object's parent, or to associate a log message with, we traverse each stack
frame, fetching the first local 'self' object that is a log.Origin class
instance.
How to use:
Simply call log.log() anywhere, and it finds an Origin object to log for, from
the stack. Alternatively call self.log() for any Origin() object to skip the
lookup.
Create classes as child class of log.Origin and make sure to call
super().__init__(category, name). This constructor will magically find a parent
Origin on the stack.
When an exception happens, we first escalate the exception up through call
scopes to where ever it is handled by log.log_exn(). This then finds an Origin
object in the traceback's stack frames, no need to nest in 'with' scopes.
Hence the 'with log.Origin' now "happens implicitly", we can write pure natural
python code, no more hassles with scope ordering.
Furthermore, any frame can place additional logging information in a frame by
calling log.ctx(). This is automatically inserted in the ancestry associated
with a log statement / exception.
Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
|
|
|
run_dir = self.run_dir.new_dir(name)
|
|
|
|
proc = process.Process(name, run_dir, popen_args)
|
|
|
|
proc.launch()
|
|
|
|
proc.wait()
|
|
|
|
if proc.result != 0:
|
|
|
|
log.ctx(proc)
|
|
|
|
raise log.Error('Exited in error')
|
2017-05-18 13:24:02 +00:00
|
|
|
|
osmo_hlr.py: fix auth algo mapping, properly indicate COMP128v1
So far the resources.conf says we're using XOR, but we wrongly map 'xor' to 1,
which is actually comp128v1 in enum osmo_auth_algo from libosmocore (which
osmo-hlr uses to interpret the numbers from the hlr.db).
This explains why our "xor" tests are succeeding even though libosmocore
doesn't support XOR at all: we were using comp128v1 all the while.
Fix the auth algo mapping:
- define correct mappings, copying enum osmo_auth_algo, in util.py
- add a function to get the enum value from name, in util.py
- use this in osmo_hlr.py
Change subscriber_add() API to take the algorithm string instead of a number.
The number is libosmocore internal and we should not expose it within our API
beyond above dict. There are no callers using this parameter yet anyway.
Adjust resources.conf to indicate COMP128v1 which we are actually using and
which means we're still using algorithm number 1 after this change.
BTW, osmo-nitb uses the ctrl interface which interprets the names, so is not
vulnerable to mapping wrong numbers and needs no fix. (If osmo-hlr featured
similar CTRL, which it doesn't yet, this code could be more robust.)
Related: OS#2758
Change-Id: I7a6ce92468a6ae46136ad4f62381da261fd196c8
2017-12-14 14:18:05 +00:00
|
|
|
def subscriber_add(self, modem, msisdn=None, algo_str=None):
|
2017-05-18 13:24:02 +00:00
|
|
|
if msisdn is None:
|
2020-05-05 10:54:37 +00:00
|
|
|
msisdn = self.testenv.msisdn()
|
2017-05-18 13:24:02 +00:00
|
|
|
modem.set_msisdn(msisdn)
|
|
|
|
subscriber_id = self.next_subscriber_id
|
|
|
|
self.next_subscriber_id += 1
|
2017-08-24 14:57:17 +00:00
|
|
|
|
osmo_hlr.py: fix auth algo mapping, properly indicate COMP128v1
So far the resources.conf says we're using XOR, but we wrongly map 'xor' to 1,
which is actually comp128v1 in enum osmo_auth_algo from libosmocore (which
osmo-hlr uses to interpret the numbers from the hlr.db).
This explains why our "xor" tests are succeeding even though libosmocore
doesn't support XOR at all: we were using comp128v1 all the while.
Fix the auth algo mapping:
- define correct mappings, copying enum osmo_auth_algo, in util.py
- add a function to get the enum value from name, in util.py
- use this in osmo_hlr.py
Change subscriber_add() API to take the algorithm string instead of a number.
The number is libosmocore internal and we should not expose it within our API
beyond above dict. There are no callers using this parameter yet anyway.
Adjust resources.conf to indicate COMP128v1 which we are actually using and
which means we're still using algorithm number 1 after this change.
BTW, osmo-nitb uses the ctrl interface which interprets the names, so is not
vulnerable to mapping wrong numbers and needs no fix. (If osmo-hlr featured
similar CTRL, which it doesn't yet, this code could be more robust.)
Related: OS#2758
Change-Id: I7a6ce92468a6ae46136ad4f62381da261fd196c8
2017-12-14 14:18:05 +00:00
|
|
|
if algo_str is None:
|
|
|
|
algo_str = modem.auth_algo() or util.OSMO_AUTH_ALGO_NONE
|
|
|
|
|
|
|
|
if algo_str != util.OSMO_AUTH_ALGO_NONE and not modem.ki():
|
|
|
|
raise log.Error("Auth algo %r selected but no KI specified" % algo_str)
|
|
|
|
|
|
|
|
algo = util.osmo_auth_algo_by_name(algo_str)
|
|
|
|
|
|
|
|
self.log('Add subscriber', msisdn=msisdn, imsi=modem.imsi(), subscriber_id=subscriber_id,
|
|
|
|
algo_str=algo_str, algo=algo)
|
2017-08-23 15:02:38 +00:00
|
|
|
conn = sqlite3.connect(self.db_file)
|
|
|
|
try:
|
|
|
|
c = conn.cursor()
|
|
|
|
c.execute('insert into subscriber (id, imsi, msisdn) values (?, ?, ?)',
|
2020-10-14 12:48:21 +00:00
|
|
|
(subscriber_id, modem.imsi(), modem.msisdn(),))
|
2017-08-23 15:02:38 +00:00
|
|
|
c.execute('insert into auc_2g (subscriber_id, algo_id_2g, ki) values (?, ?, ?)',
|
|
|
|
(subscriber_id, algo, modem.ki(),))
|
|
|
|
conn.commit()
|
|
|
|
finally:
|
|
|
|
conn.close()
|
2017-05-18 13:24:02 +00:00
|
|
|
return subscriber_id
|
|
|
|
|
2017-08-23 15:03:48 +00:00
|
|
|
def subscriber_delete(self, modem):
|
|
|
|
self.log('Add subscriber', imsi=modem.imsi())
|
|
|
|
conn = sqlite3.connect(self.db_file)
|
|
|
|
try:
|
|
|
|
c = conn.cursor()
|
|
|
|
c.execute('select id from subscriber where imsi = ?', (modem.imsi(),))
|
|
|
|
subscriber_id = c.fetchone()[0]
|
|
|
|
c.execute('delete from subscriber where id = ?', (subscriber_id,))
|
|
|
|
c.execute('delete from auc_2g where subscriber_id = ?', (subscriber_id,))
|
|
|
|
conn.commit()
|
|
|
|
finally:
|
|
|
|
conn.close()
|
|
|
|
|
2017-11-23 10:07:02 +00:00
|
|
|
def conf_for_client(self):
|
2017-06-08 21:08:59 +00:00
|
|
|
return dict(hlr=dict(ip_address=self.ip_address))
|
|
|
|
|
2017-05-18 13:24:02 +00:00
|
|
|
# vim: expandtab tabstop=4 shiftwidth=4
|