Experiment Model Reference

Reference guide for the Merge Experiment Model Language

Overview

In Merge, the user defines experiment models in the Python language, making use of the mergexp package. At a high level, an experiment model begins by creating a network topology object, creating experiment nodes, defining how the nodes are connected by network links, and finally, executing the model.

For example, consider a simple experiment with two nodes, A and B, which are directly connected via a network link. The Python model could be written as:

from mergexp import *

# create the network topology object
net = Network('example network')

# Create nodes and add them to the network
a = net.node('A')
b = net.node('B')

# Connect A and B
link = net.connect([a, b])

# Set IP addresses for A and B
link[a].socket.addrs = ip4('10.0.0.1/24')
link[b].socket.addrs = ip4('10.0.0.2/24')

# Execute the topology
experiment(net)

In real-world scenarios, the user will want to create more complex topologies, and control some of the properties of the experiment nodes and links. These are called constraints in Merge. The available constraints for each object type are detailed below.

Network

The network object represents the experiment model’s topology.

A network topology is created with the Network() constructor. The constructor takes a string, which defines the name of the topology, and an optional set of constraints.

def Network(name, *constraints):
ParameterTypeDescription
namestringuser description of the model

Network objects can use the following constraints:

ConstraintPossible ValuesDescription
routingstaticautomatically configure static routing on the experiment nodes
addressingipv4automatically assign IPv4 addresses to experiment nodes
experimentnetresolutionTrue,Falsewhether Merge will resolve hostnames to experiment network addresses. If set to False, node names will resolve to infranet IP addresses instead (default: True)

Nodes

A new node is created with the Node() constructor.

def Node(name, *constraints):
ParameterTypeDescription
namestringhostname of the experiment node

If a node in a model needs to meet some criteria, the Node() constructor can be supplied with optional constraints that will be used to control which physical machine in the facility will be selected. Constraints are specified as the constraint name, an operator (<=, ==, >=, etc), and a value.

NameTypeUnitsDescription
imagestringspecify the OS image to boot on the node (default: bullseye)
metalboolnode should be not be virtualized (True) (default: False)
proc.coresinthas the specified number of processor cores (default: 1)
memory.capacityintbytesrequire the memory size (default: 512 MB)

Supported Operating Systems

The image constraint can specify one of the following values:

Image NameOSVersion
1804Ubuntu18.04
2004Ubuntu20.04
bullseyeDebianbullseye
busterDebianbuster

Ingresses

Nodes can host services that are accessible from outside the experiment. When a node ingress is created, the testbed configures a network path from the experiment node to the site’s gateway, allowing external connections to route through the testbed network to the experiment.

Ingresses are specified in the model via the ingress function on a node. See the Ingresses documentation for details.

Groups

Nodes can be categorized into groups, a technique which can be useful when automating experiments using Ansible (see Experiment Automation). To put nodes into groups, use a special properties field of the Node object as follows:

from mergexp import *

net = Network('example topology with groups')

a = [net.node('a%d' % i) for i in range(2)]
b = [net.node('b%d' % i) for i in range(3)]
r = net.node('router')

net.connect(a + [r])
net.connect(b + [r])

for n in a:
    n.properties['group'] = ['group_a']
for n in b:
    n.properties['group'] = ['group_b']

experiment(net)

Once this model has been successfully realized, you can use the CLI to generate an Ansible inventory for the model. Assuming a materialization named r0.groups.murphy:

mrg nodes generate inventory r0.groups.murphy
[all]
a0
a1
b0
b1
b2
router

[group_a]
a0
a1

[group_b]
b0
b1
b2

Network links are represented by the Link object, and are created via the .connect() method on a Network object. Creating the link adds it to the network topology. A Link is used to model both point-to-point links (2 nodes) and LANs (3 or more nodes).

def connect(node_list, *constraint):
ParameterTypeDescription
node_listsequence of Nodetwo or more nodes to be interconnected

The following constraints are defined for Link objects:

NameTypeUnitsRangeDescription
layerintnumber1-3network layer
capacityintbits/sec>0set the max bandwidth
latencyintns>=0set the one-way packet delay
lossfloatpercentage0.0-1.0set the packet loss rate

IP Addresses

IP addresses on experiment nodes are configured via the Link object. The Link object can be dereferenced using a Node object, and setting the .socket.addrs value, which sets the IP address for that node’s interface on the link. For convenience, the mergexp package provides the following utility function:

# The ip4 function takes one or more strings specifying IPv4 addresses in `address/network`
# format (see https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network)
def ip4(*addrs):

Constraints

Constraints are used by Merge to select physical resources that meet criteria required by the user. Constraints are specified as a boolean relationship between the constraint type and the constraint value.

The mergexp package defines many units which can be used as constraint values.

Size

The size constraints are used to specify disk and memory requirements. The base unit for size constraints is bytes. The following convenience constraint values are defined:

kb()
mb()
gb()
tb()
pb()
eb()

Capacity

The capacity constraints are used to specify link bandwidth. The base unit for capacity constraints is bytes/sec. The following convenience constraint values are defined:

bps()
kbps()
mbps()
gbps()
tbps()
pbps()
epps()

Time

The time constraint is used for setting the one-way link latency. The base unit is nanoseconds. The following convenience constraint values are defined:

ns()
us()
ms()
s()