The goal of Multinet is to provide a fast, controlled and resource-efficient way to boot large-scale SDN topologies. It builds on the Mininet project to emulate SDN networks via multiple isolated topologies, each launched on a separate machine, and all connected to the same controller.
Multinet has been verified with the Lithium release of the OpenDaylight controller, where we managed to boot and connect a topology of 3000+ OVS OF 1.3 switches to a single controller instance in less than 10 minutes. The controller was running on a moderate-sized VM (8 VCPUs, 32GB memory) and the multinet topology over 10 small-sized VMs (1 VCPU, 4GB memory each).
Why isolated topologies?
The main motivation behind Multinet was to be able to stress an SDN controller in terms of its switch scalable limits. In this context, Multinet contents itself to booting topologies that are isolated from each other, without really caring to be interconnected, as we believe this policy is simple and good enough to approximate the behavior of large-scale realistic SDN networks and their interaction with the controller. If creating large-scale interconnected topologies is your primary concern, then you might want to look at other efforts such as Maxinet or the Cluster Edition Prototype of Mininet. Instead, Multinet clearly emphasizes on creating scalable pressure to the controller and provides options to control certain aspects that affect the switches-controller interaction, such as the way these are being connected during start-up.
Why multiple VMs?
The cost to boot a large Mininet topology on a single machine grows exponentially with the number of switches. To amortize this cost, we opted to scale out and utilize multiple VMs to spawn multiple smaller topologies in parallel. Eventually, one of the key questions that we try to answer through Multinet is: what is the best time to boot-up a topology of S Mininet switches with the least amount of resources?
- Large-scale SDN networks emulation, using multiple isolated Mininet topologies distributed across multiple VMs
- Controllable boot-up of switches in groups of configurable size and configurable intermediate delay. This enables studying different policies of connecting large-scale topologies to the controller.
- Centralized and RESTful control of topologies via a master-worker architecture
- Well-known topology types offered out-of-the-box (
disconnected
,linear
,ring
,mesh
) - Smooth integration with custom topologies created via the high-level Mininet API,
provided they have slightly modified their
build
method
To use Multinet you should have a distributed environment of machines configured as follows:
- Software dependencies:
- Python 2.7
bottle
,requests
andparamiko
Python packages- a recent version of Mininet (we support 2.2.1rc)
- Mausezahn, tool for network traffic generation.
- Connectivity:
- the machines should be able to communicate with each other
- the machines should have SSH connectivity
The above software dependencies are installed inside a virtualenv
(isolated Python environment),
which is created from the deploy/provision.sh
script which is responsible
for the environment setup. In the next section we demonstrate how to prepare
such an environment using
Vagrant to provision and boot multiple VMs and
docker to provision multiple containers.
You can use Vagrant to setup a testing environment quickly.
Using the provided Vagrantfile
you can boot a configurable number of
fully provisioned VMs in a private network and specify their IP scheme.
Under the deploy/vagrant
directory we provide scripts and Vagrantfiles to
automatically setup a distributed environment of VMs to run Multinet. The steps
for this are:
-
Provision the base box from which VMs will be instantiated:
[user@machine multinet/]$ cd deploy/vagrant/base/
If you sit behind a proxy, edit the
http_proxy
variable in theVagrantfile
. Then start provisioning:[user@machine multinet/deploy/vagrant/base]$ vagrant up
When the above command finishes, package the base box that has been created:
[user@machine multinet/deploy/vagrant/base]$ vagrant package --output mh-provisioned.box [user@machine multinet/deploy/vagrant/base]$ vagrant box add mh-provisioned mh-provisioned.box [user@machine multinet/deploy/vagrant/base]$ vagrant destroy
For more info on Vagrant box packaging take a look at this guide
-
Configure the VMs:
[user@machine multinet/]$ cd deploy/vagrant/packaged_multi/
Edit the
Vagrantfile
according to your preferences. For example:http_proxy = '' # if you sit behind a corporate proxy, provide it here mh_vm_basebox = 'mh-provisioned' # the name of the Vagrant box we created in step 2 mh_vm_ram_mini = '2048' # RAM size per VM mh_vm_cpus_mini = '2' # number of CPUs per VM num_multinet_vms = 10 # total number of VMs to boot mh_vm_private_network_ip_mini = '10.1.1.70' # the first IP Address in the mininet VMs IP Address range
Optional Configuration If you need port forwarding from the master guest machine to the host machine, edit these variables inside the
Vagrantfile
:forwarded_ports_master = [] # A list of the ports the guest VM needs # to forward forwarded_ports_host = [] # The host ports where the guest ports will be # forwarded to (1 - 1 correspondence) # Example: # port 3300 from master VM will be forwarded to port 3300 of # the host machine # port 6634 from master VM will be forwarded to port 6635 of # the host machine # forwarded_ports_master = [3300, 6634] # forwarded_ports_host = [3300, 6635]
-
Boot the VMs:
[user@machine multinet/deploy/vagrant/packaged_multi]$ vagrant up
You should now have a number of interconnected VMs with all the dependencies installed.
In order to create a docker container we must first create an image which will be used as a base for creating one or more docker containers. For the creation of a docker image we provide a dockerfile
-
Install docker: docker installation guides
-
Creation of docker image (without proxy settings):
[user@machine multinet/]$ cd deploy/docker/no_proxy/ [user@machine multinet/deploy/docker/no_proxy/]$ sudo docker build -t multinet_image .
After this step when you run the command
[user@machine multinet/deploy/docker/no_proxy/]$ sudo docker images
You should see something like the following output
REPOSITORY TAG IMAGE ID CREATED SIZE <repo_name> multinet_image a75c906f03c7 1 minute ago 1.72 GB
-
Create containers from the created image: Open 2 terminals and execute the following command in order to create 2 docker containers
[user@machine]$ sudo docker run -it <repo_name>:multinet_image /bin/bash
After running the above commands on each terminal you should see the command prompt of the container. It should be something like the following
root@cfb6dccfc41d:/#
Multinet, inside a docker container, is under the path /opt/multinet
root@cfb6dccfc41d:/#cd /opt/multinet
The 2 containers are interconnected you can get the ip address information if you run the command
root@cfb6dccfc41d:/opt/multinet# ifconfig
The default docker network for the containers is 172.17.0.0/16. Use the IP
addresses of docker containers in the configuration file for the
"master_ip":
and "worker_ip_list":
. See next in the document in the
configuration
section of multinet. For more information about docker
container networks visit the link
Understand Docker container networks
To start using Multinet, the first step is to clone the Multinet repository on your
local machine. This machine is supposed to act as the client machine to Multinet, and is
denoted in the following examples with the [user@machine]
prompt.
Once you have the repository checked out, you may proceed with some configuration.
Deployment configuration
Edit the configuration file to your IP sceme:
[user@machine multinet/]$ vim config/config.json
{
"master_ip" : "10.1.1.80",
"master_port": 3300,
"worker_ip_list": ["10.1.1.80", "10.1.1.81"],
"worker_port": 3333,
"deploy": {
"multinet_base_dir": "/home/vagrant/multinet",
"ssh_port": 22,
"username": "vagrant",
"password": "vagrant"
}
}
master_ip
is the IP address of the machine where the master will runmaster_port
is the port where the master listens for REST requests from the user or any external client applicationworker_ip_list
is the list with the IPs of all machines where workers will be created to launch topologiesworker_port
is the port where each worker listens for REST requests from the mastermultinet_base_dir
is the location where the Multinet repo was cloned on the client machinessh_port
is the port where the master and worker machines listen for SSH connectionsusername
,password
are the credentials used to access via SSH the master and worker machines
Topology configuration
Edit the configuration file to the desired topology features:
{
"topo": {
"controller_ip_address":"10.1.1.39",
"controller_of_port":6653,
"switch_type":"ovsk",
"topo_type":"linear",
"topo_size":30,
"group_size":3,
"group_delay":100,
"hosts_per_switch":2,
"traffic_generation_duration_ms":60000,
"interpacket_delay_ms":5000
}
}
controller_ip_address
is the IP address of the machine where the SDN controller will runcontroller_of_port
is the port where the controller listens for OpenFlow messagesswitch_type
is the type of soft switch used for the emulation (supported types:ovsk
for OVS OF1.3 switches,user
for CPqD OF1.3 switches)topo_type
is the type of topologies to be booted on every worker node (out of the box supported types:linear
,mesh
,ring
,disconnected
)topo_size
is the size of topologies to be booted on every worker nodegroup_size
,group_delay
are the parameters defining the gradual bootup groups (see section below)hosts_per_switch
is the number of hosts connected to each switch of the topologytraffic_generation_duration_ms
is the amount of time in milliseconds, during whichPACKET_IN
's with ARP payload, will be transmitted.interpacket_delay_ms
is connected to thetraffic_generation_duration_ms
and is the interval between consecutivePACKET_IN
's, with ARP payload, sent from a particular Multinet worker.
The goal of this phase is to deploy the Multinet master and worker nodes on a set of
up and running physical or virtual machines that satisfy the conditions mentioned above.
The provided deploy
script automates the process of copying the required files on
each machine and starting the master
and worker
REST servers. The deployment process
assumes the existence of the /opt/venv_multinet directory created by deploy/provsision.sh
desdribed in Environment setup section.
Run the deploy
script from the client machine to copy the
necessary files and start the master and the workers:
[user@machine /opt/multinet/]$./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/deploy /opt/multinet/config/config.json
This step initializes the distributed topologies without connecting them to the controller. It creates every necessary component of the topology, such as switches, links, hosts, etc.
Run the following command from the client machine:
[user@machine /opt/multinet/]$./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/handlers/init_topos /opt/multinet/config/config.json
The above will send an init
command to every worker node concurrently,
and an identical Mininet topology will be booted on every worker machine.
If all topologies are initialized successfully you should get a 200 OK
output message on the client machine. If any topology fails to initialize
successfully, an error message will be printed.
In this step the initialized topologies are being connected to the SDN controller. Multinet provides control over the way that the topologies are being connected to the controller, allowing gradual connection in a group-wise fashion. The rationale behind this is to prevent the controller from being overwhelmed at cases where a large number of switches are being exposed to it at once. As a result, the gradual start up enables investigating different policies of connecting large-scale topologies to the controller.
The group_size
and group_delay
configuration options control the way that the
distributed topologies are being connected to the controller. The connection
process proceeds in intervals as follows: at every step, group_size
switches from every worker node will be connected to the controller, and
after that, a delay of group_delay
milliseconds will follow before
proceeding to the next interval. Note that each worker node will execute the
gradual booting process independent from the others, without employing any
kind of intermediate synchronization.
To connect a Multinet topology after it has been initialized, run the following command from the client machine:
[user@machine /opt/multinet/]$./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/handlers/start_topos /opt/multinet/config/config.json
The above will send a start
command to every worker node in parallel and
initiate the gradual connections of the topologies.
If all topologies are connected successfully you should get a 200 OK
output message on the client machine. If any topology fails to connect,
an error message will be printed.
To query Multinet for the number of booted switches on each worker node, run the following command from the client machine:
[user@machine multinet/]$ ./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/handlers/get_switches /opt/multinet/config/config.json
If the distributed topologies have been successfully booted, you should
get a 200 OK
message and the number of switches booted on each worker node.
To query Multinet for the number of all installed flows on topology switches on each worker, we can use the following command:
[user@machine /opt/multinet/]$ ./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/handlers/get_flows /opt/multinet/config/config.json
With this command on each switch we get a dump of its flows and we count them. For each worker we add the different counts of switches flows and we get the total installed flows for all the switches on the worker node. We return the per Multinet worker total installed flows.
To perform a "pingall" operation on every worker node in parallel run the following command from the client machine:
[user@machine /opt/multinet/]$ ./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/handlers/pingall /opt/multinet/config/config.json
If the operation runs on a successfully booted topology you should
synchronously get a 200 OK
response code and the pingall output should
be logged. Note that a pingall
operation may take a long time to complete if the
topology has many hosts.
When connecting a Multinet topology to the OpenDaylight controller,
the hosts are not made automatically visible by the L2 switch plugin,
but rather when they generate traffic.
To trigger host visibility, we have opted to perform a dummy ping from each
host to the controller, which fires a PACKET_IN
transmission.
To do this, run the following command from the client machine:
[user@machine /opt/multinet/]$ ./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/handlers/detect_hosts /opt/multinet/config/config.json
If all the topologies are booted successfully you should synchronously
get a 200 OK
response code. Note that a detect_hosts
operation may take a long
time to complete if the topology has many hosts.
To stop a Multinet topology run the following command from the client machine:
[user@machine /opt/multinet/]$ ./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/handlers/stop_topos /opt/multinet/config/config.json
The above will send a stop
command to every worker node in parallel and destroy the
topologies. If all the topologies are destroyed successfully, you should synchronously
get a 200 OK
output message.
A dedicated script exist to revert the Multinet deployment. To clean all the Multinet machines simply run:
[user@machine /opt/multinet/]$ ./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/cleanup /opt/multinet/config/config.json
Multinet has the capability to generate traffic from switches to the controller.
This traffic consists of PACKET_IN
events, which contain in their Data
field,
ARP payload. This operation requires to have defined 2, as a minimum value of
hosts_per_switch
, in the topo
section of the configuration file.
For more information see in the Configuration section above.
If we asume the case that we use OpenDaylight controller with a proper
configuration, to connect our multinet topology, the above mentioned
PACKET_IN
traffic will have as a result the provoke of FLOW_MOD
's,
transmitted from the controller to the switches. Controller must have the
following plugins installed:
odl-restconf-all
odl-openflowplugin-flow-services
odl-l2switch-switch
Aditionally we must change the values of the following configuration keys in the mentioned XML configuration files of the controller:
<controller root directory>/etc/opendaylight/karaf/54-arphandler.xml
- Inside the above mentioned file there is a configuration option, the
is-proactive-flood-mode
. We must set its value tofalse
:<is-proactive-flood-mode>false</is-proactive-flood-mode>
- Inside the above mentioned file there is a configuration option, the
<controller root directory>/etc/opendaylight/karaf/58-l2switchmain.xml
- inside the above mentioned file we have two configuration options, the
reactive-flow-idle-timeout
andreactive-flow-hard-timeout
. We must set the minimum value for these configuration keys, which is equal to 1:<reactive-flow-idle-timeout>1</reactive-flow-idle-timeout> <reactive-flow-hard-timeout>1</reactive-flow-hard-timeout>
- inside the above mentioned file we have two configuration options, the
If the XML configuration files do not exist, we must start and stop the controller once to provoke the generation of these files.
The operational result of the above configuration is presented in the following diagram:
In order to use the PACKET_IN
generation capability, the following command must
be executed:
[user@machine /opt/multinet/]$ ./bin/venv_handler_master.sh /opt/multinet opt/multinet/bin/handlers/traffic_gen /opt/multinet/config/config.json
The end goal of Multinet is to deploy a set of Mininet topologies over multiple machines and have them connected to the same controller simultaneously. To make this possible, every switch must have a unique DPID to avoid naming collisions in the controller's housekeeping mechanism, and to achieve this, Multinet automatically assigns a proper DPID offset to each Mininet topology.
The local Mininet topologies are identical in terms of size, structure and configuration, and they are all being handled in the same fashion simultaneously. For example, during start up, topologies are being created simultaneously on the worker machines and populated in the same way. To relieve the end user from having to manage each topology separately, we have adopted a master-worker model for centralized control.
The master process acts as the Multinet front-end that accepts REST requests from the client machine. At the same time, it orchestrates the pool of workers. On a user request, the master creates a separate command for each local topology which it dispatches simultaneously to the workers. Each worker process controls a local Mininet topology. It accepts commands from the master via REST, applies them to its topology, and responds back with a result status. Every worker is unaware of the topologies being operated from other workers. The master is responsible to collect partial results and statuses from all workers and reply to the user as soon as it has a complete global view.
For resource efficiency and speed, it is preferable to create each worker along with its topology on a separate machine.
Path | Description |
---|---|
.travis.yml |
Travis CI job |
bin/ |
Binaries |
bin/handlers/ |
Command Line Handlers |
bin/cleanuph |
Cleanup script to reset the Multinet machines environment |
bin/deploy |
Automation script to copy and start the master and the workers in the Multinet machines |
config/ |
Configuration file for the handlers, the deployment and the master |
figs/ |
Figures needed for documentation |
multi/ |
Module containing the Master / Worker REST servers |
net/ |
Module containing the Mininet related functionality |
net/multinet.py |
Class inheriting from the core Mininet with added / modified functionality |
net/topologies.py |
example topologies |
test |
basic functionality tests |
travis-jobs |
Travis CI machine provisioning helper scripts |
util/ |
Utility modules |
vagrant/ |
Vagrantfiles for fast provisioning of a running environment |
To make easier the communication between a client application and the master node, we augmented it with a REST API. The client application can issue the POST requests shown below to interact with Multinet programmatically. In essence, the command line handlers presented in the previous sections are wrapper scripts to those POST requests.
-
Initialize Multinet topology
@bottle.route('/init', method='POST')
When making a POST request to the
init
endpoint, you must also send a JSON body with the following format (also see the Initialize Multinet topology section){ "controller_ip_address":"10.1.1.39", "controller_of_port":6653, "switch_type":"ovsk", "topo_type":"linear", "topo_size":30, "group_size":3, "group_delay":100, "hosts_per_switch":2, "traffic_generation_duration_ms":60000, "interpacket_delay_ms":5000 }
-
Start Multinet topology
@bottle.route('/start', method='POST')
-
Get the number of switches
@bottle.route('/get_switches', method='POST')
-
Perform a
pingall
in each topology@bottle.route('/ping_all', method='POST')
-
Make the hosts visible
@bottle.route('/detect_hosts', method='POST')
-
Stop the topologies
@bottle.route('/stop', method='POST')
-
Generate
PACKET_IN
traffic@bottle.route('/generate_traffic', method='POST')
You can also utilize the wrappers from the multinet_requests
module for the
same purpose. Example:
-
For initialization
# Send a POST request to the master 'init' endpoint topo_data= { "controller_ip_address":"10.1.1.39", "controller_of_port":6653, "switch_type":"ovsk", "topo_type":"linear", "topo_size":30, "group_size":3, "group_delay":100, "hosts_per_switch":2, "traffic_generation_duration_ms":60000, "interpacket_delay_ms":5000 } multinet_requests.master_cmd(master_ip, master_port, 'init', data=topo_data)
-
And for any other operation
# Send a POST request to any master endpoint # The endpoint is specified by the 'opcode' parameter opcode = choose_one_of(['start', 'get_switches', 'ping_all', 'detect_hosts', 'stop']) multinet_requests.master_cmd(master_ip, master_port, opcode)
Multinet
class- Extends the
Mininet
class. - Adds a dpid offset during the switch creation phase to distinguish between the switches in different instances.
- Inserts the notion of gradual switch bootup, inserting some idle time
(
group_delay
) between the bootup of groups of switches (group_size
)
- Extends the
worker
- creates its own
Multinet
instance - creates a REST API that wraps the exposed methods of that instance.
- creates its own
master
- exposes a REST API to the end user.
- broadcasts the commands to the workers
- aggregates the responses and returns a summary response to the end user
To be able to plug any topology created with the high level Mininet API to Multinet, modify the build method to conform with the following method signature:
# k is the number of switches
# n is the number of hosts per switch
# dpid is the dpid offset
def build(self, k=2, n=1, dpid=1, **_opts):
- Create a topology with the Mininet high level API, for example
## mytopo.py
class MyTopo(Topo):
"Disconnected topology of k switches, with n hosts per switch."
def build(self, k=2, n=1, dpid=1, **_opts):
"""
k: number of switches
n: number of hosts per switch
dpid: the dpid offset (to enable distributed topology creation)
"""
self.k = k
self.n = n
for i in xrange(k):
# Add switch
switch = self.addSwitch(genSwitchName(i, dpid))
# Add hosts to switch
for j in xrange(n):
host = self.addHost(genHostName(i, j, dpid, n))
self.addLink(host, switch)
-
Add the new topology to the
Multinet.TOPOS
dictionary# worker.py import mytopo ... Multinet.TOPOS['mytopo'] = mytopo.MyTopo MININET_TOPO = Multinet( ... )
# or from inside multinet.py TOPOS = { 'linear': ... 'mytopo': mytopo.MyTopo }