2020-02-11 16:45:26 +00:00
|
|
|
# osmo_gsm_tester: specifics for running an SRS UE process
|
|
|
|
#
|
|
|
|
# Copyright (C) 2020 by sysmocom - s.f.m.c. GmbH
|
|
|
|
#
|
|
|
|
# Author: Pau Espin Pedrol <pespin@sysmocom.de>
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 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
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import pprint
|
2020-05-27 16:46:12 +00:00
|
|
|
import re
|
2020-02-11 16:45:26 +00:00
|
|
|
|
2020-04-10 18:46:07 +00:00
|
|
|
from ..core import log, util, config, template, process, remote
|
2020-05-04 10:05:05 +00:00
|
|
|
from ..core import schema
|
2020-02-11 16:45:26 +00:00
|
|
|
from .run_node import RunNode
|
|
|
|
from .ms import MS
|
2020-06-19 13:47:32 +00:00
|
|
|
from .srslte_common import srslte_common
|
2020-02-11 16:45:26 +00:00
|
|
|
|
2020-02-13 18:29:55 +00:00
|
|
|
def rf_type_valid(rf_type_str):
|
2020-04-01 12:12:10 +00:00
|
|
|
return rf_type_str in ('zmq', 'uhd', 'soapy', 'bladerf')
|
2020-02-13 18:29:55 +00:00
|
|
|
|
2020-05-04 10:05:05 +00:00
|
|
|
def on_register_schemas():
|
|
|
|
resource_schema = {
|
|
|
|
'rf_dev_type': schema.STR,
|
|
|
|
'rf_dev_args': schema.STR,
|
2020-10-12 14:57:10 +00:00
|
|
|
'rf_dev_sync': schema.STR,
|
2020-05-04 10:05:05 +00:00
|
|
|
'num_carriers': schema.UINT,
|
2020-06-15 15:01:16 +00:00
|
|
|
'additional_args[]': schema.STR,
|
2020-05-04 10:05:05 +00:00
|
|
|
'airplane_t_on_ms': schema.INT,
|
|
|
|
'airplane_t_off_ms': schema.INT,
|
|
|
|
'tx_gain': schema.UINT,
|
|
|
|
'rx_gain': schema.UINT,
|
2020-10-06 07:52:46 +00:00
|
|
|
'freq_offset': schema.INT,
|
2020-05-04 10:05:05 +00:00
|
|
|
}
|
2020-05-26 11:48:10 +00:00
|
|
|
for key, val in RunNode.schema().items():
|
|
|
|
resource_schema['run_node.%s' % key] = val
|
2020-05-04 10:05:05 +00:00
|
|
|
schema.register_resource_schema('modem', resource_schema)
|
|
|
|
|
|
|
|
config_schema = {
|
|
|
|
'enable_pcap': schema.BOOL_STR,
|
2020-06-18 12:52:39 +00:00
|
|
|
'log_all_level': schema.STR,
|
2020-05-04 10:05:05 +00:00
|
|
|
}
|
|
|
|
schema.register_config_schema('modem', config_schema)
|
|
|
|
|
2020-03-02 10:50:23 +00:00
|
|
|
#reference: srsLTE.git srslte_symbol_sz()
|
|
|
|
def num_prb2symbol_sz(num_prb):
|
2020-06-02 20:35:27 +00:00
|
|
|
if num_prb == 6:
|
2020-03-02 10:50:23 +00:00
|
|
|
return 128
|
2020-06-02 20:35:27 +00:00
|
|
|
if num_prb == 50:
|
2020-03-02 10:50:23 +00:00
|
|
|
return 768
|
2020-06-02 20:35:27 +00:00
|
|
|
if num_prb == 75:
|
2020-03-02 10:50:23 +00:00
|
|
|
return 1024
|
2020-06-02 20:35:27 +00:00
|
|
|
return 1536
|
2020-03-02 10:50:23 +00:00
|
|
|
|
|
|
|
def num_prb2base_srate(num_prb):
|
|
|
|
return num_prb2symbol_sz(num_prb) * 15 * 1000
|
|
|
|
|
2020-06-19 13:47:32 +00:00
|
|
|
class srsUE(MS, srslte_common):
|
2020-02-11 16:45:26 +00:00
|
|
|
|
|
|
|
REMOTE_DIR = '/osmo-gsm-tester-srsue'
|
|
|
|
BINFILE = 'srsue'
|
|
|
|
CFGFILE = 'srsue.conf'
|
|
|
|
PCAPFILE = 'srsue.pcap'
|
|
|
|
LOGFILE = 'srsue.log'
|
2020-03-02 10:06:51 +00:00
|
|
|
METRICSFILE = 'srsue_metrics.csv'
|
2020-02-11 16:45:26 +00:00
|
|
|
|
2020-05-05 10:54:37 +00:00
|
|
|
def __init__(self, testenv, conf):
|
2020-05-26 11:48:10 +00:00
|
|
|
self._run_node = RunNode.from_conf(conf.get('run_node', {}))
|
|
|
|
super().__init__('srsue_%s' % self.addr(), conf)
|
2020-09-14 16:14:15 +00:00
|
|
|
srslte_common.__init__(self)
|
2020-02-11 16:45:26 +00:00
|
|
|
self.enb = None
|
|
|
|
self.run_dir = None
|
|
|
|
self.config_file = None
|
|
|
|
self.log_file = None
|
|
|
|
self.pcap_file = None
|
2020-03-02 10:06:51 +00:00
|
|
|
self.metrics_file = None
|
2020-06-18 08:05:31 +00:00
|
|
|
self.have_metrics_file = False
|
2020-02-11 16:45:26 +00:00
|
|
|
self.process = None
|
|
|
|
self.rem_host = None
|
2020-04-16 13:40:22 +00:00
|
|
|
self.remote_inst = None
|
2020-05-25 13:44:09 +00:00
|
|
|
self.remote_run_dir = None
|
2020-02-11 16:45:26 +00:00
|
|
|
self.remote_config_file = None
|
|
|
|
self.remote_log_file = None
|
|
|
|
self.remote_pcap_file = None
|
2020-03-02 10:06:51 +00:00
|
|
|
self.remote_metrics_file = None
|
2020-03-16 11:42:17 +00:00
|
|
|
self.enable_pcap = False
|
2020-04-20 18:42:15 +00:00
|
|
|
self.num_carriers = 1
|
2020-05-05 10:54:37 +00:00
|
|
|
self.testenv = testenv
|
2020-04-01 17:51:08 +00:00
|
|
|
self._additional_args = []
|
2020-02-13 18:29:55 +00:00
|
|
|
if not rf_type_valid(conf.get('rf_dev_type', None)):
|
|
|
|
raise log.Error('Invalid rf_dev_type=%s' % conf.get('rf_dev_type', None))
|
2020-10-05 15:25:16 +00:00
|
|
|
self._zmq_base_bind_port = None
|
|
|
|
if conf.get('rf_dev_type') == 'zmq':
|
|
|
|
# Define all 4 possible local RF ports (2x CA with 2x2 MIMO)
|
|
|
|
self._zmq_base_bind_port = self.testenv.suite().resource_pool().next_zmq_port_range(self, 4)
|
2020-02-11 16:45:26 +00:00
|
|
|
|
|
|
|
def cleanup(self):
|
|
|
|
if self.process is None:
|
|
|
|
return
|
2020-05-26 11:48:10 +00:00
|
|
|
if self._run_node.is_local():
|
2020-02-11 16:45:26 +00:00
|
|
|
return
|
2020-05-21 12:34:55 +00:00
|
|
|
|
|
|
|
# Make sure we give the UE time to tear down
|
|
|
|
self.sleep_after_stop()
|
|
|
|
|
2020-02-11 16:45:26 +00:00
|
|
|
# copy back files (may not exist, for instance if there was an early error of process):
|
2020-06-18 08:05:31 +00:00
|
|
|
self.scp_back_metrics(raiseException=False)
|
2020-02-11 16:45:26 +00:00
|
|
|
try:
|
|
|
|
self.rem_host.scpfrom('scp-back-log', self.remote_log_file, self.log_file)
|
|
|
|
except Exception as e:
|
|
|
|
self.log(repr(e))
|
2020-03-16 11:42:17 +00:00
|
|
|
if self.enable_pcap:
|
|
|
|
try:
|
|
|
|
self.rem_host.scpfrom('scp-back-pcap', self.remote_pcap_file, self.pcap_file)
|
|
|
|
except Exception as e:
|
|
|
|
self.log(repr(e))
|
2020-02-11 16:45:26 +00:00
|
|
|
|
2020-06-19 13:47:32 +00:00
|
|
|
# Collect KPIs for each TC
|
|
|
|
self.testenv.test().set_kpis(self.get_kpis())
|
|
|
|
|
2020-06-18 08:05:31 +00:00
|
|
|
def scp_back_metrics(self, raiseException=True):
|
|
|
|
''' Copy back metrics only if they have not been copied back yet '''
|
|
|
|
if not self.have_metrics_file:
|
|
|
|
# file is not properly flushed until the process has stopped.
|
|
|
|
if self.running():
|
|
|
|
self.stop()
|
|
|
|
|
|
|
|
# only SCP back if not running locally
|
|
|
|
if not self._run_node.is_local():
|
|
|
|
try:
|
|
|
|
self.rem_host.scpfrom('scp-back-metrics', self.remote_metrics_file, self.metrics_file)
|
|
|
|
except Exception as e:
|
|
|
|
if raiseException:
|
|
|
|
self.err('Failed copying back metrics file from remote host')
|
|
|
|
raise e
|
|
|
|
else:
|
|
|
|
# only log error
|
|
|
|
self.log(repr(e))
|
|
|
|
# make sure to only call it once
|
|
|
|
self.have_metrics_file = True
|
|
|
|
else:
|
|
|
|
self.dbg('Metrics have already been copied back')
|
|
|
|
|
2020-02-11 16:45:26 +00:00
|
|
|
def netns(self):
|
|
|
|
return "srsue1"
|
|
|
|
|
2020-10-05 15:25:16 +00:00
|
|
|
def zmq_base_bind_port(self):
|
|
|
|
return self._zmq_base_bind_port
|
|
|
|
|
2020-02-11 16:45:26 +00:00
|
|
|
def connect(self, enb):
|
|
|
|
self.log('Starting srsue')
|
|
|
|
self.enb = enb
|
2020-05-11 08:56:52 +00:00
|
|
|
self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name()))
|
2020-02-11 16:45:26 +00:00
|
|
|
self.configure()
|
2020-05-26 11:48:10 +00:00
|
|
|
if self._run_node.is_local():
|
2020-02-11 16:45:26 +00:00
|
|
|
self.start_locally()
|
|
|
|
else:
|
|
|
|
self.start_remotely()
|
|
|
|
|
2020-03-21 20:18:39 +00:00
|
|
|
# send t+Enter to enable console trace
|
|
|
|
self.dbg('Enabling console trace')
|
|
|
|
self.process.stdin_write('t\n')
|
|
|
|
|
2020-02-11 16:45:26 +00:00
|
|
|
def start_remotely(self):
|
2020-04-16 13:40:22 +00:00
|
|
|
remote_lib = self.remote_inst.child('lib')
|
|
|
|
remote_binary = self.remote_inst.child('bin', srsUE.BINFILE)
|
2020-02-11 16:45:26 +00:00
|
|
|
# setting capabilities will later disable use of LD_LIBRARY_PATH from ELF loader -> modify RPATH instead.
|
|
|
|
self.log('Setting RPATH for srsue')
|
|
|
|
# srsue binary needs patchelf >= 0.9+52 to avoid failing during patch. OS#4389, patchelf-GH#192.
|
|
|
|
self.rem_host.change_elf_rpath(remote_binary, remote_lib)
|
|
|
|
|
2020-05-21 11:21:56 +00:00
|
|
|
# srsue requires CAP_SYS_ADMIN to jump to net network namespace: netns(CLONE_NEWNET):
|
2020-02-11 16:45:26 +00:00
|
|
|
# srsue requires CAP_NET_ADMIN to create tunnel devices: ioctl(TUNSETIFF):
|
|
|
|
self.log('Applying CAP_SYS_ADMIN+CAP_NET_ADMIN capability to srsue')
|
|
|
|
self.rem_host.setcap_netsys_admin(remote_binary)
|
|
|
|
|
2020-02-24 09:58:59 +00:00
|
|
|
self.log('Creating netns %s' % self.netns())
|
|
|
|
self.rem_host.create_netns(self.netns())
|
|
|
|
|
2020-04-16 13:40:22 +00:00
|
|
|
args = (remote_binary, self.remote_config_file, '--gw.netns=' + self.netns())
|
2020-04-01 17:51:08 +00:00
|
|
|
args += tuple(self._additional_args)
|
2020-02-11 16:45:26 +00:00
|
|
|
|
2020-05-25 13:44:09 +00:00
|
|
|
self.process = self.rem_host.RemoteProcessSafeExit(srsUE.BINFILE, self.remote_run_dir, args)
|
2020-05-05 10:54:37 +00:00
|
|
|
self.testenv.remember_to_stop(self.process)
|
2020-02-11 16:45:26 +00:00
|
|
|
self.process.launch()
|
|
|
|
|
|
|
|
def start_locally(self):
|
2020-04-16 13:40:22 +00:00
|
|
|
binary = self.inst.child('bin', srsUE.BINFILE)
|
|
|
|
lib = self.inst.child('lib')
|
2020-02-11 16:45:26 +00:00
|
|
|
env = {}
|
|
|
|
|
|
|
|
# setting capabilities will later disable use of LD_LIBRARY_PATH from ELF loader -> modify RPATH instead.
|
|
|
|
self.log('Setting RPATH for srsue')
|
|
|
|
util.change_elf_rpath(binary, util.prepend_library_path(lib), self.run_dir.new_dir('patchelf'))
|
|
|
|
|
2020-05-21 11:21:56 +00:00
|
|
|
# srsue requires CAP_SYS_ADMIN to jump to net network namespace: netns(CLONE_NEWNET):
|
2020-02-11 16:45:26 +00:00
|
|
|
# srsue requires CAP_NET_ADMIN to create tunnel devices: ioctl(TUNSETIFF):
|
|
|
|
self.log('Applying CAP_SYS_ADMIN+CAP_NET_ADMIN capability to srsue')
|
|
|
|
util.setcap_netsys_admin(binary, self.run_dir.new_dir('setcap_netsys_admin'))
|
|
|
|
|
2020-02-24 09:58:59 +00:00
|
|
|
self.log('Creating netns %s' % self.netns())
|
|
|
|
util.create_netns(self.netns(), self.run_dir.new_dir('create_netns'))
|
|
|
|
|
2020-04-16 13:40:22 +00:00
|
|
|
args = (binary, os.path.abspath(self.config_file), '--gw.netns=' + self.netns())
|
2020-04-01 17:51:08 +00:00
|
|
|
args += tuple(self._additional_args)
|
2020-02-11 16:45:26 +00:00
|
|
|
|
|
|
|
self.process = process.Process(self.name(), self.run_dir, args, env=env)
|
2020-05-05 10:54:37 +00:00
|
|
|
self.testenv.remember_to_stop(self.process)
|
2020-02-11 16:45:26 +00:00
|
|
|
self.process.launch()
|
|
|
|
|
|
|
|
def configure(self):
|
2020-05-26 11:55:19 +00:00
|
|
|
self.inst = util.Dir(os.path.abspath(self.testenv.suite().trial().get_inst('srslte', self._run_node.run_label())))
|
2020-04-16 13:40:22 +00:00
|
|
|
if not os.path.isdir(self.inst.child('lib')):
|
|
|
|
raise log.Error('No lib/ in', self.inst)
|
|
|
|
if not self.inst.isfile('bin', srsUE.BINFILE):
|
|
|
|
raise log.Error('No %s binary in' % srsUE.BINFILE, self.inst)
|
|
|
|
|
2020-03-16 11:42:17 +00:00
|
|
|
self.config_file = self.run_dir.child(srsUE.CFGFILE)
|
2020-02-11 16:45:26 +00:00
|
|
|
self.log_file = self.run_dir.child(srsUE.LOGFILE)
|
2020-03-16 11:42:17 +00:00
|
|
|
self.pcap_file = self.run_dir.child(srsUE.PCAPFILE)
|
2020-03-02 10:06:51 +00:00
|
|
|
self.metrics_file = self.run_dir.child(srsUE.METRICSFILE)
|
2020-04-16 13:40:22 +00:00
|
|
|
|
2020-05-26 11:48:10 +00:00
|
|
|
if not self._run_node.is_local():
|
|
|
|
self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr())
|
2020-04-16 13:40:22 +00:00
|
|
|
remote_prefix_dir = util.Dir(srsUE.REMOTE_DIR)
|
|
|
|
self.remote_inst = util.Dir(remote_prefix_dir.child(os.path.basename(str(self.inst))))
|
2020-05-25 13:44:09 +00:00
|
|
|
self.remote_run_dir = util.Dir(remote_prefix_dir.child(srsUE.BINFILE))
|
|
|
|
self.remote_config_file = self.remote_run_dir.child(srsUE.CFGFILE)
|
|
|
|
self.remote_log_file = self.remote_run_dir.child(srsUE.LOGFILE)
|
|
|
|
self.remote_pcap_file = self.remote_run_dir.child(srsUE.PCAPFILE)
|
|
|
|
self.remote_metrics_file = self.remote_run_dir.child(srsUE.METRICSFILE)
|
2020-02-11 16:45:26 +00:00
|
|
|
|
|
|
|
values = dict(ue=config.get_defaults('srsue'))
|
2020-05-05 10:54:37 +00:00
|
|
|
config.overlay(values, dict(ue=self.testenv.suite().config().get('modem', {})))
|
2020-02-11 16:45:26 +00:00
|
|
|
config.overlay(values, dict(ue=self._conf))
|
2020-07-13 10:01:10 +00:00
|
|
|
config.overlay(values, dict(ue=dict(num_antennas = self.enb.num_ports(),
|
|
|
|
opc = self.opc())))
|
2020-02-11 16:45:26 +00:00
|
|
|
|
2020-05-26 11:48:10 +00:00
|
|
|
metricsfile = self.metrics_file if self._run_node.is_local() else self.remote_metrics_file
|
|
|
|
logfile = self.log_file if self._run_node.is_local() else self.remote_log_file
|
|
|
|
pcapfile = self.pcap_file if self._run_node.is_local() else self.remote_pcap_file
|
2020-04-16 13:40:22 +00:00
|
|
|
config.overlay(values, dict(ue=dict(metrics_filename=metricsfile,
|
|
|
|
log_filename=logfile,
|
|
|
|
pcap_filename=pcapfile)))
|
|
|
|
|
2020-03-16 11:42:17 +00:00
|
|
|
# Convert parsed boolean string to Python boolean:
|
|
|
|
self.enable_pcap = util.str2bool(values['ue'].get('enable_pcap', 'false'))
|
|
|
|
config.overlay(values, dict(ue={'enable_pcap': self.enable_pcap}))
|
|
|
|
|
2020-06-15 20:12:46 +00:00
|
|
|
self._additional_args = []
|
2020-06-15 15:01:16 +00:00
|
|
|
for add_args in values['ue'].get('additional_args', []):
|
|
|
|
self._additional_args += add_args.split()
|
|
|
|
|
2020-04-20 18:42:15 +00:00
|
|
|
self.num_carriers = int(values['ue'].get('num_carriers', 1))
|
|
|
|
|
2020-10-12 14:57:10 +00:00
|
|
|
# Simply pass-through the sync options
|
|
|
|
config.overlay(values, dict(ue={'rf_dev_sync': values['ue'].get('rf_dev_sync', None)}))
|
|
|
|
|
2020-02-13 18:29:55 +00:00
|
|
|
# We need to set some specific variables programatically here to match IP addresses:
|
|
|
|
if self._conf.get('rf_dev_type') == 'zmq':
|
2020-03-02 10:50:23 +00:00
|
|
|
base_srate = num_prb2base_srate(self.enb.num_prb())
|
4g: Introduce ZMQ GnuRadio stream broker
srsENB currently creates 1 zmq stream (1 tx, 1 rx) for each cell (2 if
MIMO is enabled). Each cell transceives on a given EARFCN (and several
cells can transmit on same EARFCN).
However, for handover test purposes, we want to join all cells operating
on the same EARFCN to transceive on the same ZMQ conn, so that an srsUE
can interact with them at the same time (same as if the medium was shared).
Furthermore, we want to set different gains on each of those paths
before merging them in order to emulate RF conditions like handover.
In order to do so, a new element called the Broker is introduced, which
is placed in between ENBs and UEs ZMQ conenctions, multiplexing the
connections on the ENB side towards the UE side.
A separate process for the broker is run remotely (ENB run host) which
listens on a ctrl socket for commands. An internal Broker class is used
in osmo-gsm-tester to interact with the remote script, for instance to
configure the ports, start and stop the remote process, send commands to
it, etc.
On each ENB, when the rfemu "gnuradio_zmq" rfemu implementation is selected
in configuration, it will configure its zmq connections and the UE ones to
go over the Broker.
As a result, that means the UE zmq port configuration is expected to be
different than when no broker is in used, since there's the multiplexing
per EARFCN in between.
In this commit, only 1 ENB is supported, but multi-enb support is
planned in the future.
The handover test passes in the docker setup with this config:
"""
OSMO_GSM_TESTER_OPTS="-T -l dbg -s 4g:srsue-rftype@zmq+srsenb-rftype@zmq+" \
"mod-enb-nprb@6+mod-enb-ncells@2+mod-enb-cells-2ca+suite-4g@10,2+" \
"mod-enb-meas-enable -t =handover.py"
"""
and in resources.conf (or scenario), added:
"""
enb:
...
cell_list:
- dl_rfemu:
type: gnuradio_zmq
- dl_rfemu:
type: gnuradio_zmq
"""
Note that since the broker is used, there's not need for mod-srsue-ncarriers@2
since the broker is joining the 2 enb cells into 1 stream on the UE side.
Change-Id: I6282cda400558dcb356276786d91e6388524c5b1
2020-10-05 17:23:38 +00:00
|
|
|
|
2020-04-20 18:42:15 +00:00
|
|
|
# Define all 8 possible RF ports (2x CA with 2x2 MIMO)
|
4g: Introduce ZMQ GnuRadio stream broker
srsENB currently creates 1 zmq stream (1 tx, 1 rx) for each cell (2 if
MIMO is enabled). Each cell transceives on a given EARFCN (and several
cells can transmit on same EARFCN).
However, for handover test purposes, we want to join all cells operating
on the same EARFCN to transceive on the same ZMQ conn, so that an srsUE
can interact with them at the same time (same as if the medium was shared).
Furthermore, we want to set different gains on each of those paths
before merging them in order to emulate RF conditions like handover.
In order to do so, a new element called the Broker is introduced, which
is placed in between ENBs and UEs ZMQ conenctions, multiplexing the
connections on the ENB side towards the UE side.
A separate process for the broker is run remotely (ENB run host) which
listens on a ctrl socket for commands. An internal Broker class is used
in osmo-gsm-tester to interact with the remote script, for instance to
configure the ports, start and stop the remote process, send commands to
it, etc.
On each ENB, when the rfemu "gnuradio_zmq" rfemu implementation is selected
in configuration, it will configure its zmq connections and the UE ones to
go over the Broker.
As a result, that means the UE zmq port configuration is expected to be
different than when no broker is in used, since there's the multiplexing
per EARFCN in between.
In this commit, only 1 ENB is supported, but multi-enb support is
planned in the future.
The handover test passes in the docker setup with this config:
"""
OSMO_GSM_TESTER_OPTS="-T -l dbg -s 4g:srsue-rftype@zmq+srsenb-rftype@zmq+" \
"mod-enb-nprb@6+mod-enb-ncells@2+mod-enb-cells-2ca+suite-4g@10,2+" \
"mod-enb-meas-enable -t =handover.py"
"""
and in resources.conf (or scenario), added:
"""
enb:
...
cell_list:
- dl_rfemu:
type: gnuradio_zmq
- dl_rfemu:
type: gnuradio_zmq
"""
Note that since the broker is used, there's not need for mod-srsue-ncarriers@2
since the broker is joining the 2 enb cells into 1 stream on the UE side.
Change-Id: I6282cda400558dcb356276786d91e6388524c5b1
2020-10-05 17:23:38 +00:00
|
|
|
rf_dev_args = self.enb.get_zmq_rf_dev_args_for_ue(self)
|
2020-04-20 18:42:15 +00:00
|
|
|
|
|
|
|
if self.num_carriers == 1:
|
|
|
|
# Single carrier
|
|
|
|
if self.enb.num_ports() == 1:
|
|
|
|
# SISO
|
|
|
|
rf_dev_args += ',rx_freq0=2630e6,tx_freq0=2510e6'
|
|
|
|
elif self.enb.num_ports() == 2:
|
|
|
|
# MIMO
|
|
|
|
rf_dev_args += ',rx_freq0=2630e6,rx_freq1=2630e6,tx_freq0=2510e6,tx_freq1=2510e6'
|
|
|
|
elif self.num_carriers == 2:
|
|
|
|
# 2x CA
|
|
|
|
if self.enb.num_ports() == 1:
|
|
|
|
# SISO
|
|
|
|
rf_dev_args += ',rx_freq0=2630e6,rx_freq1=2650e6,tx_freq0=2510e6,tx_freq1=2530e6'
|
|
|
|
elif self.enb.num_ports() == 2:
|
|
|
|
# MIMO
|
|
|
|
rf_dev_args += ',rx_freq0=2630e6,rx_freq1=2630e6,rx_freq2=2650e6,rx_freq3=2650e6,tx_freq0=2510e6,tx_freq1=2510e6,tx_freq2=2530e6,tx_freq3=2530e6'
|
2020-09-16 19:16:46 +00:00
|
|
|
elif self.num_carriers == 4:
|
|
|
|
# 4x CA
|
|
|
|
if self.enb.num_ports() == 1:
|
|
|
|
# SISO
|
|
|
|
rf_dev_args += ',rx_freq0=2630e6,rx_freq1=2650e6,rx_freq2=2670e6,rx_freq3=2680e6,tx_freq0=2510e6,tx_freq1=2530e6,tx_freq2=2550e6,tx_freq3=2560e6'
|
|
|
|
elif self.enb.num_ports() == 2:
|
|
|
|
# MIMO
|
|
|
|
raise log.Error("4 carriers with MIMO isn't supported")
|
|
|
|
else:
|
|
|
|
# flag
|
|
|
|
raise log.Error('No rx/tx frequencies given for {} carriers' % self.num_carriers)
|
2020-04-20 18:42:15 +00:00
|
|
|
|
|
|
|
rf_dev_args += ',id=ue,base_srate='+ str(base_srate)
|
|
|
|
config.overlay(values, dict(ue=dict(rf_dev_args=rf_dev_args)))
|
2020-02-13 18:29:55 +00:00
|
|
|
|
2020-03-26 19:54:45 +00:00
|
|
|
# Set UHD frame size as a function of the cell bandwidth on B2XX
|
2020-04-07 16:51:57 +00:00
|
|
|
if self._conf.get('rf_dev_type') == 'uhd' and values['ue'].get('rf_dev_args', None) is not None:
|
2020-03-26 19:54:45 +00:00
|
|
|
if 'b200' in values['ue'].get('rf_dev_args'):
|
|
|
|
rf_dev_args = values['ue'].get('rf_dev_args', '')
|
|
|
|
rf_dev_args += ',' if rf_dev_args != '' and not rf_dev_args.endswith(',') else ''
|
|
|
|
|
2020-09-23 15:07:07 +00:00
|
|
|
if self.enb.num_prb() == 75:
|
|
|
|
rf_dev_args += 'master_clock_rate=15.36e6,'
|
|
|
|
|
|
|
|
if self.enb.num_ports() == 1:
|
|
|
|
# SISO config
|
|
|
|
if self.enb.num_prb() < 25:
|
|
|
|
rf_dev_args += 'send_frame_size=512,recv_frame_size=512'
|
|
|
|
elif self.enb.num_prb() == 25:
|
|
|
|
rf_dev_args += 'send_frame_size=1024,recv_frame_size=1024'
|
|
|
|
else:
|
|
|
|
rf_dev_args += ''
|
|
|
|
else:
|
|
|
|
# MIMO config
|
2020-03-26 19:54:45 +00:00
|
|
|
rf_dev_args += 'num_recv_frames=64,num_send_frames=64'
|
2020-09-23 15:07:07 +00:00
|
|
|
# For the UE the otw12 format doesn't seem to work very well
|
2020-03-26 19:54:45 +00:00
|
|
|
|
|
|
|
config.overlay(values, dict(ue=dict(rf_dev_args=rf_dev_args)))
|
|
|
|
|
2020-02-11 16:45:26 +00:00
|
|
|
self.dbg('SRSUE CONFIG:\n' + pprint.pformat(values))
|
|
|
|
|
|
|
|
with open(self.config_file, 'w') as f:
|
|
|
|
r = template.render(srsUE.CFGFILE, values)
|
|
|
|
self.dbg(r)
|
|
|
|
f.write(r)
|
|
|
|
|
2020-05-26 11:48:10 +00:00
|
|
|
if not self._run_node.is_local():
|
2020-04-16 13:40:22 +00:00
|
|
|
self.rem_host.recreate_remote_dir(self.remote_inst)
|
|
|
|
self.rem_host.scp('scp-inst-to-remote', str(self.inst), remote_prefix_dir)
|
2020-05-25 13:44:09 +00:00
|
|
|
self.rem_host.recreate_remote_dir(self.remote_run_dir)
|
2020-04-16 13:40:22 +00:00
|
|
|
self.rem_host.scp('scp-cfg-to-remote', self.config_file, self.remote_config_file)
|
|
|
|
|
2020-05-27 16:46:12 +00:00
|
|
|
def is_rrc_connected(self):
|
|
|
|
''' Check whether UE is RRC connected using console message '''
|
|
|
|
pos_connected = (self.process.get_stdout() or '').rfind('RRC Connected')
|
|
|
|
pos_released = (self.process.get_stdout() or '').rfind('RRC IDLE')
|
|
|
|
return pos_connected > pos_released
|
|
|
|
|
|
|
|
def is_registered(self, mcc_mnc=None):
|
|
|
|
''' Checks if UE is EMM registered '''
|
2020-02-11 16:45:26 +00:00
|
|
|
return 'Network attach successful.' in (self.process.get_stdout() or '')
|
|
|
|
|
2020-05-27 16:46:12 +00:00
|
|
|
def get_assigned_addr(self, ipv6=False):
|
|
|
|
if ipv6:
|
|
|
|
raise log.Error('IPv6 not implemented!')
|
|
|
|
else:
|
|
|
|
stdout_lines = (self.process.get_stdout() or '').splitlines()
|
|
|
|
for line in reversed(stdout_lines):
|
|
|
|
if line.find('Network attach successful. IP: ') != -1:
|
|
|
|
ipv4_addr = re.findall( r'[0-9]+(?:\.[0-9]+){3}', line)
|
|
|
|
return ipv4_addr[0]
|
|
|
|
return None
|
2020-02-11 16:45:26 +00:00
|
|
|
|
|
|
|
def running(self):
|
|
|
|
return not self.process.terminated()
|
|
|
|
|
|
|
|
def addr(self):
|
2020-05-26 11:48:10 +00:00
|
|
|
return self._run_node.run_addr()
|
2020-02-11 16:45:26 +00:00
|
|
|
|
|
|
|
def run_node(self):
|
2020-05-26 11:48:10 +00:00
|
|
|
return self._run_node
|
2020-02-11 16:45:26 +00:00
|
|
|
|
|
|
|
def run_netns_wait(self, name, popen_args):
|
2020-05-26 11:48:10 +00:00
|
|
|
if self._run_node.is_local():
|
2020-02-11 16:45:26 +00:00
|
|
|
proc = process.NetNSProcess(name, self.run_dir.new_dir(name), self.netns(), popen_args, env={})
|
|
|
|
else:
|
|
|
|
proc = self.rem_host.RemoteNetNSProcess(name, self.netns(), popen_args, env={})
|
|
|
|
proc.launch_sync()
|
2020-03-05 17:30:37 +00:00
|
|
|
return proc
|
2020-02-11 16:45:26 +00:00
|
|
|
|
2020-05-27 19:38:59 +00:00
|
|
|
def _get_counter_stdout(self, keyword):
|
|
|
|
# Match stdout against keyword
|
2020-04-20 11:29:59 +00:00
|
|
|
n = 0
|
|
|
|
stdout_lines = (self.process.get_stdout() or '').splitlines()
|
|
|
|
for l in stdout_lines:
|
2020-05-27 19:38:59 +00:00
|
|
|
if keyword in l:
|
2020-04-20 11:29:59 +00:00
|
|
|
n += 1
|
|
|
|
return n
|
|
|
|
|
|
|
|
def get_counter(self, counter_name):
|
|
|
|
if counter_name == 'handover_success':
|
2020-05-27 19:38:59 +00:00
|
|
|
return self._get_counter_stdout('HO successful')
|
|
|
|
if counter_name == 'prach_sent':
|
|
|
|
return self._get_counter_stdout('Random Access Transmission')
|
|
|
|
if counter_name == 'paging_received':
|
|
|
|
return self._get_counter_stdout('S-TMSI match in paging message')
|
|
|
|
if counter_name == 'reestablishment_attempts':
|
|
|
|
return self._get_counter_stdout('RRC Connection Reestablishment')
|
|
|
|
if counter_name == 'reestablishment_ok':
|
|
|
|
return self._get_counter_stdout('Reestablishment OK')
|
|
|
|
if counter_name == 'rrc_connected_transitions':
|
|
|
|
return self._get_counter_stdout('RRC Connected')
|
|
|
|
if counter_name == 'rrc_idle_transitions':
|
|
|
|
return self._get_counter_stdout('RRC IDLE')
|
2020-04-20 11:29:59 +00:00
|
|
|
raise log.Error('counter %s not implemented!' % counter_name)
|
|
|
|
|
2020-05-22 14:15:25 +00:00
|
|
|
def verify_metric(self, value, operation='avg', metric='dl_brate', criterion='gt', window=1):
|
2020-06-18 08:05:31 +00:00
|
|
|
# copy back metrics if we have not already done so
|
|
|
|
self.scp_back_metrics(self)
|
2020-03-02 13:14:27 +00:00
|
|
|
metrics = srsUEMetrics(self.metrics_file)
|
2020-05-22 14:15:25 +00:00
|
|
|
return metrics.verify(value, operation, metric, criterion, window)
|
2020-03-02 13:14:27 +00:00
|
|
|
|
2020-05-11 09:41:47 +00:00
|
|
|
numpy = None
|
|
|
|
|
2020-03-02 13:14:27 +00:00
|
|
|
class srsUEMetrics(log.Origin):
|
|
|
|
|
2020-06-30 16:08:41 +00:00
|
|
|
VALID_OPERATIONS = ['avg', 'sum', 'max_rolling_avg', 'min_rolling_avg']
|
2020-03-02 13:14:27 +00:00
|
|
|
VALID_CRITERION = ['eq','gt','lt']
|
|
|
|
CRITERION_TO_SYM = { 'eq' : '==', 'gt' : '>', 'lt' : '<' }
|
|
|
|
CRYTERION_TO_SYM_OPPOSITE = { 'eq' : '!=', 'gt' : '<=', 'lt' : '>=' }
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, metrics_file):
|
|
|
|
super().__init__(log.C_RUN, 'srsue_metrics')
|
|
|
|
self.raw_data = None
|
|
|
|
self.metrics_file = metrics_file
|
2020-05-11 09:41:47 +00:00
|
|
|
global numpy
|
|
|
|
if numpy is None:
|
|
|
|
import numpy as numpy_module
|
|
|
|
numpy = numpy_module
|
2020-03-02 13:14:27 +00:00
|
|
|
# read CSV, guessing data type with first row being the legend
|
|
|
|
try:
|
|
|
|
self.raw_data = numpy.genfromtxt(self.metrics_file, names=True, delimiter=';', dtype=None)
|
|
|
|
except (ValueError, IndexError, IOError) as error:
|
|
|
|
self.err("Error parsing metrics CSV file %s" % self.metrics_file)
|
|
|
|
raise error
|
|
|
|
|
2020-05-25 20:36:58 +00:00
|
|
|
def verify(self, value, operation='avg', metric_str='dl_brate', criterion='gt', window=1):
|
2020-03-02 13:14:27 +00:00
|
|
|
if operation not in self.VALID_OPERATIONS:
|
|
|
|
raise log.Error('Unknown operation %s not in %r' % (operation, self.VALID_OPERATIONS))
|
|
|
|
if criterion not in self.VALID_CRITERION:
|
|
|
|
raise log.Error('Unknown operation %s not in %r' % (operation, self.VALID_CRITERION))
|
|
|
|
# check if given metric exists in data
|
2020-05-25 20:36:58 +00:00
|
|
|
sel_data = numpy.array([])
|
|
|
|
metrics_list = metric_str.split('+') # allow addition operator for columns
|
|
|
|
for metric in metrics_list:
|
|
|
|
try:
|
|
|
|
vec = numpy.array(self.raw_data[metric])
|
|
|
|
except ValueError as err:
|
|
|
|
print('metric %s not available' % metric)
|
|
|
|
raise err
|
|
|
|
if sel_data.size == 0:
|
|
|
|
# Initialize with dimension of first metric vector
|
|
|
|
sel_data = vec
|
|
|
|
else:
|
|
|
|
# Sum them up assuming same array dimension
|
|
|
|
sel_data += vec
|
2020-03-02 13:14:27 +00:00
|
|
|
|
2020-05-26 21:00:10 +00:00
|
|
|
# Sum up all component carriers for rate metrics
|
|
|
|
if metric_str.find('brate'):
|
|
|
|
# Determine number of component carriers
|
|
|
|
num_cc = numpy.amax(numpy.array(self.raw_data['cc'])) + 1 # account for zero index
|
|
|
|
tmp_values = sel_data
|
|
|
|
sel_data = numpy.array(tmp_values[::num_cc]) # first carrier, every num_cc'th item in list
|
|
|
|
for cc in range(1, num_cc):
|
|
|
|
sel_data += numpy.array(tmp_values[cc::num_cc]) # all other carriers, start at cc index
|
|
|
|
|
2020-03-02 13:14:27 +00:00
|
|
|
if operation == 'avg':
|
|
|
|
result = numpy.average(sel_data)
|
|
|
|
elif operation == 'sum':
|
|
|
|
result = numpy.sum(sel_data)
|
2020-05-22 14:15:25 +00:00
|
|
|
elif operation == 'max_rolling_avg':
|
|
|
|
# calculate rolling average over window and take maximum value
|
|
|
|
result = numpy.amax(numpy.convolve(sel_data, numpy.ones((window,))/window, mode='valid'))
|
2020-06-30 16:08:41 +00:00
|
|
|
elif operation == 'min_rolling_avg':
|
2020-08-27 13:34:04 +00:00
|
|
|
# trim leading zeros to avoid false negative when UE attach takes longer
|
|
|
|
sel_data = numpy.trim_zeros(sel_data, 'f')
|
2020-06-30 16:08:41 +00:00
|
|
|
# calculate rolling average over window and take minimum value
|
|
|
|
result = numpy.amin(numpy.convolve(sel_data, numpy.ones((window,))/window, mode='valid'))
|
2020-05-22 14:15:25 +00:00
|
|
|
|
2020-03-02 13:14:27 +00:00
|
|
|
self.dbg(result=result, value=value)
|
|
|
|
|
|
|
|
success = False
|
|
|
|
if criterion == 'eq' and result == value or \
|
|
|
|
criterion == 'gt' and result > value or \
|
|
|
|
criterion == 'lt' and result < value:
|
|
|
|
success = True
|
|
|
|
|
|
|
|
# Convert bitrate in Mbit/s:
|
2020-05-25 20:36:58 +00:00
|
|
|
if metric_str.find('brate') > 0:
|
2020-03-02 13:14:27 +00:00
|
|
|
result /= 1e6
|
|
|
|
value /= 1e6
|
|
|
|
mbit_str = ' Mbit/s'
|
|
|
|
else:
|
|
|
|
mbit_str = ''
|
|
|
|
|
|
|
|
if not success:
|
|
|
|
result_msg = "{:.2f}{} {} {:.2f}{}".format(result, mbit_str, self.CRYTERION_TO_SYM_OPPOSITE[criterion], value, mbit_str)
|
|
|
|
raise log.Error(result_msg)
|
|
|
|
result_msg = "{:.2f}{} {} {:.2f}{}".format(result, mbit_str, self.CRITERION_TO_SYM[criterion], value, mbit_str)
|
|
|
|
# TODO: overwrite test system-out with this text.
|
|
|
|
return result_msg
|
|
|
|
|
2020-02-11 16:45:26 +00:00
|
|
|
# vim: expandtab tabstop=4 shiftwidth=4
|