The r2lab API¶
Testbed preparation¶
This module contains a utility for preparing the testbed.
The logic is that you write a scheduler that implements your pure experimental logic.
You can then pass this scheduler to r2lab_prepare_scheduler()
,
that returns a scheduler where your original one is embedded.
This overall scheduler will prepend instructions for preparing the testbed, in terms of loading images on nodes, turning off unused nodes, and similar.
- r2lab.prepare.prepare_testbed_scheduler(gateway, load_flag, experiment_scheduler, images_mapping, nodes_left_alone=None, sdrs_left_alone=None, phones_left_alone=None, verbose_jobs=False)[source]¶
This function is designed as a standard way for experiments to warm up. Experimenters only need to write a scheduler that defines the behaviour of their core experiment, this function will add additional steps that take care of a) checking for a valid lease, b) load images on nodes, and c) turn off unused devices.
It is generally desirable to write an experiment script that has a –load/-l boolean flag; typically, one would use the
--load
flag the first time that an experiment is launched during a given timeslot, while subsequent calls won’t. That is the purpose of theload_flag
below; when set to False, only step a) is performed, otherwise the resulting scheduler will go for the full monty.- Parameters:
gateway_sshnode – the ssh handle to the gateway
load_flag (bool) – if not set, only the lease is checked
experiment_scheduler (
Scheduler
) – core scheduler for the experimentimages_mapping – a dictionary that specifies images to be loaded on nodes; see examples below
nodes_left_alone – a list of node numbers that should be left intact, neither loaded nor turned off;
phones_left_alone – a list of node numbers that should be left intact, i.e. not switched to airplane mode.
- Return :
The overall scheduler where the input
experiment_scheduler
is embedded.
Examples
Specify a mapping like the following:
images_mapping = { "ubuntu" : [1, 4, 5], "gnuradio": [16]}
Note that the format for
images_mapping
, is flexible; if only one node is to be loaded, the iterable level is optional; also each node can be specified as anint
, abytes
, astr
, in which case non numeric characters are ignored. So this is a legitimate requirement as well:images_mapping = { 'openair-cn': 12 + 4, 'openair-enodeb': ('fit32',), 'ubuntu': {12, 'reboot1', '004', 'you-get-the-picture-34'} }
2-Dimension grid¶
The R2labMap class is a convenience for mapping node numbers to a 2-dimensional grid coordinates, and backwards.
- class r2lab.r2labmap.R2labMap[source]¶
Inherits
R2labMapGeneric
A map object where coordinates start at 1, and where the Y coordinate goes upwards; so typically in this map node 1 is at (1, 5) and node 37 is at (9, 1).
- class r2lab.r2labmap.R2labMapGeneric(*, offset_x=0, offset_y=0, swap_x=False, swap_y=False, map_x=None, map_y=None)[source]¶
The most general form allows to specifiy mapper functions in both directions. Or to simply specify an integer offset and boolean swap.
- indexes()[source]¶
Object that can be used to create a pandas index on the nodes; essentially this is range(1, 38)
- iterate_holes()[source]¶
An iterator that yields tuples of the form (x, y) for all the possible (x, y) that do not match a node
Dataframes¶
A standard pandas DataFrame
for storing results
on a node by node basis together with their
map coordinates.
- class r2lab.mapdataframe.MapDataFrame(r2labmap, columns=None)[source]¶
A
MapDataFrame
is a dataframe that has one line per node, together with their x and y coordinates as specified in the map object, plus additional columns as specified in the constructor. Is is indexed by node numbers.- Parameters:
map – a R2labMap object that primarily provides nodes coordinates
columns – an dictionary - preferrably an
OrderedDict
if using an older Python - that specifies each column name (key) and corresponding initial value (value).
Probing the testbed status¶
The R2lab sidecar is a websocket service that runs on
wss://r2lab.inria.fr:999/
and that exposes the status of the testbed.
This module implements client classes, for interacting with the sidecar service.
The core of the implementation is asyncio
-friendly and accessible
through the SidecarAsyncClient
class, but for
convenience some features are also available to synchronous code through
the SidecarSyncClient
class.
- class r2lab.sidecar.SidecarAsyncClient(url='wss://r2lab.inria.fr:999/', *args, **kwds)[source]¶
This class behaves as an asynchronous context manager for talking with the R2lab sidecar server.
Optional arguments args and kwds are passed as-is to websockets.client.connect, see https://websockets.readthedocs.io/en/stable/api.html#websockets.client.connect
Example
Set a node as available from some asynchronous code:
async with SidecarAsyncClient() as sidecar: await sidecar.set_node_attribute(1, 'available', 'ok')
In this example, the
sidecar
object is aSidecarProtocol
instance.Note
About SSL server certificate verification: Verifying server certificates relies on a set of “trusted” CAs. Web browsers do come with a maintained set of such trust anchors, however the standard Python installation has no such knowledge; and for that reason attempting to check for the testbed’s certificate will fail unless you’ve taken the time to somehow configure all this.
If you just want to probe the testbed though, this looks like a lot of hassle. In that case you can turn off server verification as foolows:
import ssl ssl_context = ssl.SSLContext() # this is where we ask for no verification ssl_context.verify_mode = ssl.CERT_NONE async with SidecarSyncClient(ssl=ssl_context) as sidecar: await sidecar.set_node_attribute(1, 'available', 'ok')
- class r2lab.sidecar.SidecarProtocol(*, logger=None, origin=None, extensions=None, subprotocols=None, extra_headers=None, user_agent_header='Python/3.11 websockets/12.0', **kwargs)[source]¶
The SidecarProtocol class is an asyncio-compliant implementation of the R2lab sidecar system.
It inherits
websockets.client.WebSocketClientProtocol
as documented here: https://websockets.readthedocs.io/en/stable/api.html#module-websockets.client- async nodes_status()[source]¶
A function call that returns the JSON nodes status for the complete testbed.
- Returns:
A python dictionary indexed by integers 1 to 37, whose values are dictionaries with keys corresponding to each node’s attributes at that time.
Example
Get the complete testbed status:
async with SidecarAsyncClient() as sidecar: nodes_status = await sidecar.nodes_status() print(nodes_status[1]['usrp_type'])
- async set_node_attribute(id, attribute, value)[source]¶
- Parameters:
id – a node_id as an int or str
attribute (str) – the name of the attribute to be written
value (str) – the new value
Example
To mark node 1 as unavailable:
await sidecar.set_node_attribute(1, 'available', 'ko')
- async set_nodes_triples(*triples)[source]¶
- Parameters:
triples – each argument is expected to be a tuple (or list) of the form
id, attribute, value
. The same node id can be used in several triples.
Example
To mark node 1 as unavailable and node 2 as turned off:
await sidecar.set_nodes_triples( (1, 'available', 'ok'), (2, 'cmc_on_off', 'off'), )
- class r2lab.sidecar.SidecarSyncClient(url='wss://r2lab.inria.fr:999/', *args, **kwds)[source]¶
A synchronous wrapper to perform the same operations from sequential code without having to worry about the event loop, asynchronous context manager and coroutine business.
Example
Set a node as available from some synchronous code:
with SidecarSyncClient() as sidecar: sidecar.set_node_attribute(1, 'available', 'ok')
Warning
This is a convenience only, it would be unwise, obviously, to call this from asynchronous code; if it works at all. Use
SidecarAsyncClient
instead in this use case.
Classes for argparse
¶
The classes in this module extend the argparse ecosystem.
Purpose is to enable the creation of CLI options that would behave a bit like
action=append
, but with a check on choices, that is to say:
accumulative : dest holds a list of strings - it’s possible to use the type system as well
restrictive : all elements in the list must be in choices
- optionnally reset-ableit should be possible for add_argument to specify a non-void default
and in this case we need a means for the CLI to explicitly void the value
In practical terms, we want to specify one or several values for a parameter that is itself constrained, like an antenna mask that must be among ‘1’, ‘3’ and ‘7’
As of this writing at least, using ‘append’ as an action won’t work it is possible to write a code that uses action=append, choices=[‘a’, ‘b’, ‘c’] and default=[‘a’, ‘b’] but then defaults are always returned…
Resetting
The actual syntax offered by your CLI for actually resetting the target list may vary from one need to another
As an example, let us consider a use case where we have 2 physical phones, and we want to be able to select any number of them. Let us further imagine that the code expects that selection to be expressed as a list of integers in the 1-2 range.
So we’d like to say e.g.:
parser.add_argument("-p", "--phones", default=[1], choices=(1, 2), type=int, ...)
And with that in place, we’d like to have
no option: results in
phones = [1]
-p 2
:phones = [2]
-p 1 -p 2
:phones = [1, 2]
-p 0
:phones = []
Now, it is not possible to adopt a convention where e.g.
-p none
would meanphones = []
because we have this type = int
setting to add_argument
, which
causes the string "none"
to be rejected as an input.
- class r2lab.argparse_additions.ListOfChoices(*args, **kwds)[source]¶
The generic class assumes there is a means_resetting method that is used to check for special incoming values that mean resetting
Example
Not resettable:
parser.add_argument(choices=('1', '2', '3', '4'), default=['1', '2'], typeaction=ListOfChoices)
General utilities¶
- r2lab.utils.find_local_embedded_script(script, extra_paths=None)[source]¶
This helper is designed to find a script that typically comes with the
r2lab-embedded
repo, specifically in itsshell
subdirectory.It knows of a few heuristics to locate your
r2lab-embedded
repo, relative to your home and current directories. You can specify additional places to search for inextra_paths
- Parameters:
script (str) – the simple name of a script to find
extra_paths (List(str)) – optional, a list of paths (can be
Path
instances too) where to search too
- Returns:
a valid path in the local filesystem, or
None
- Return type:
str
- Raises:
FileNotFoundError(script)` if script can't be foun –
Example
Search for
oai-enb.sh
so as to run it remotely:local_script = find_local_embedded_script("oai-enb.sh") RunScript(localscript, ...)
Note
Should this also look for some env. variable ?
- r2lab.utils.r2lab_data(x)[source]¶
Same as
r2lab_hostname
but returns an interface name of the formdata01
.
- r2lab.utils.r2lab_hostname(x)[source]¶
Return a valid hostname like
fit01
from an input that can be either1
(int),1
(str),01
(str) ,fit1
,fit01
or evenreboot01
.- Parameters:
x (str) – loosely typed input that reflects node number
Examples
Simple use case:
r2lab_hostname(1) == 'fit01'
And:
rl2ab_hostname('reboot1') == 'fit01'
- r2lab.utils.r2lab_id(anything)[source]¶
Returns an integer from an input that can be either
1
(int),1
(str), b``1``(bytes),01
(str) ,fit1
,fit01
or evenreboot01
.
- r2lab.utils.r2lab_parse_slice(slice)[source]¶
returns username and hostname from a slice.
- Parameters:
slice (str) – can be either
username@hostname
or justusername
. In the latter case the hostname defaults to the R2lab gateway i.e.faraday.inria.fr
- Returns:
slice
,hostname
- Return type:
tuple
Example
Typical usage is:
slice, hostname = r2lab_parse_slice("inria_r2lab.tutorial") slice, hostname = r2lab_parse_slice("inria_r2lab.tutorial@faraday.inria.fr")