Automate discovery of topologies and scenarios

This commit is contained in:
Wojciech Kozlowski 2019-04-07 00:19:11 +02:00
parent 8bbd3d07ad
commit d8b729caac
4 changed files with 59 additions and 168 deletions

View File

@ -7,9 +7,7 @@ from mininet.net import Mininet
from mininet.cli import CLI
from router import Router
from topology.one_node.topo import NetTopo as OneNode
from topology.two_nodes.topo import NetTopo as TwoNodes
from scenario import Basic, Plain, Isis
from scenario import Scenario
def start_daemon(node, daemon, conf_dir):
@ -25,7 +23,7 @@ def start_daemon(node, daemon, conf_dir):
node.waitOutput()
def run(topo, scenario):
def run(scenario):
"""Start a network scenario.
"""
@ -40,15 +38,14 @@ def run(topo, scenario):
os.system("mn -c >/dev/null 2>&1")
os.system("killall -9 {} > /dev/null 2>&1".format(' '.join(daemons)))
net = Mininet(topo=topo, switch=Router)
net = Mininet(topo=scenario.topo(), switch=Router)
net.start()
scenario.setup(net, topo.topo_dir)
# WARNING: FRR can get confused unless all daemons on each node are started
# together.
for node in net.switches:
for daemon in daemons:
if node in getattr(scenario, daemon):
if node.name in getattr(scenario, daemon, set()):
conf_dir = getattr(scenario, "{}_conf".format(daemon))
start_daemon(node, daemon, conf_dir)
@ -67,35 +64,14 @@ def run(topo, scenario):
if __name__ == "__main__":
topology = {
"one_node": OneNode,
"two_nodes": TwoNodes,
}
scenario = {
"plain": Plain,
"basic": Basic,
"isis": Isis,
}
supported = {
"one_node": set(["plain", "basic"]),
"two_nodes": set(["plain", "basic", "isis"]),
}
parser = argparse.ArgumentParser(
description='Launch a network scenario Mininet.')
parser.add_argument('--topology', type=str, required=True,
choices=topology.keys(),
parser.add_argument('--topology', '-t', type=str, required=True,
help='the topology of the network')
parser.add_argument('--scenario', type=str, required=True,
choices=scenario.keys(),
parser.add_argument('--scenario', '-s', type=str, required=True,
help='the scenario to set up in the network')
ARGS = parser.parse_args()
if ARGS.scenario not in supported[ARGS.topology]:
raise ValueError("Scenario \"{}\" is not supported for topology \"{}\""
.format(ARGS.scenario, ARGS.topology))
scenario = Scenario(ARGS.topology, ARGS.scenario)
run(topology[ARGS.topology](),
scenario[ARGS.scenario]())
run(scenario)

View File

@ -1,3 +1,4 @@
import importlib
import os
@ -8,143 +9,61 @@ class Scenario(object):
"""
def __init__(self):
self._routers = None
self._hosts = None
self._zebra = None
self._staticd = None
self._isisd = None
def __init__(self, topology, scenario):
root_dir = os.path.dirname(os.path.realpath(__file__))
self._zebra_conf = None
self._staticd_conf = None
self._isisd_conf = None
# Check if the topology directory exists.
topo_dir = os.path.join(root_dir, "topology/{}".format(topology))
if not os.path.exists(topo_dir):
raise KeyError("Topology \"{}\" not supported".format(topology))
self._reset()
# Extract the topology class.
self._topo = (importlib
.import_module('topology.{}.topo'.format(topology))
.NetTopo)
def _reset(self):
self._routers = set()
self._hosts = set()
self._zebra = set()
self._staticd = set()
self._isisd = set()
# Return now if the scenario is "plain".
if scenario == "plain":
return
self._zebra_conf = None
self._staticd_conf = None
self._isisd_conf = None
# Check if the scenario directory exists. If it does work out which
# daemons are to be used and on which nodes. If the scenario is
# "basic" skip this step.
if scenario != "basic":
scenario_dir = os.path.join(topo_dir,
"scenario/{}".format(scenario))
if not os.path.exists(scenario_dir):
raise KeyError("Scenario \"{}\" not supported"
.format(scenario))
# Work out which daemons to start and their config directories.
for daemon in os.listdir(scenario_dir):
self._get_daemon_nodes(scenario_dir, daemon)
# Zebra and staticd daemons are special. If they don't have an
# override in the scenario directory, take the defaults from the
# topology directory.
for daemon in ["zebra", "staticd"]:
if not hasattr(self, daemon):
self._get_daemon_nodes(topo_dir, daemon)
def _get_daemon_nodes(self, parent_dir, daemon):
# Each daemon entry should be a directory.
daemon_dir = os.path.join(parent_dir, daemon)
if os.path.exists(daemon_dir) and os.path.isdir(daemon_dir):
setattr(self, daemon, set())
setattr(self, "{}_conf".format(daemon), daemon_dir)
# Each node running this daemons must have a conf file in the
# daemon directory called <node_name>.conf.
for conf in os.listdir(daemon_dir):
# Make sure we're only dealing with conf files.
if conf.endswith(".conf"):
getattr(self, daemon).add(conf.split('.')[0])
@property
def routers(self):
"""Set of nodes that are routers.
def topo(self):
"""The topology of this scenario.
"""
return self._routers
@property
def hosts(self):
"""Set of nodes that are hosts.
"""
return self._hosts
@property
def zebra(self):
"""Set of nodes that should run zebra.
"""
return self._zebra
@property
def staticd(self):
"""Set of nodes that should run staticd.
"""
return self._staticd
@property
def isisd(self):
"""Set of nodes that should run isisd.
"""
return self._isisd
@property
def zebra_conf(self):
"""Directory with zebra config files.
"""
return self._zebra_conf
@property
def staticd_conf(self):
"""Directory with staticd config files.
"""
return self._staticd_conf
@property
def isisd_conf(self):
"""Directory with isisd config files.
"""
return self._isisd_conf
def _routers_and_hosts(self, net):
"""Separate nodes into routers and hosts based on their names.
"""
self._routers = set()
self._hosts = set()
for node in net.switches:
if node.name.startswith('R'):
self._routers.add(node)
else:
self._hosts.add(node)
def setup(self, net, _topo_dir):
"""Setup the scenario.
"""
self._reset()
self._routers_and_hosts(net)
class Plain(Scenario):
"""In a Plain scenario no daemons are run.
"""
class Basic(Scenario):
"""In a Basic scenario, all nodes run zebra, and all hosts run staticd to
setup a default route.
"""
def __init__(self):
Scenario.__init__(self)
def setup(self, net, topo_dir):
super(Basic, self).setup(net, topo_dir)
self._zebra = self._routers.union(self._hosts)
self._staticd = self._hosts
self._zebra_conf = os.path.join(topo_dir, "zebra")
self._staticd_conf = os.path.join(topo_dir, "staticd")
class Isis(Basic):
"""Run IS-IS on all routers.
"""
def __init__(self):
Basic.__init__(self)
def setup(self, net, topo_dir):
super(Isis, self).setup(net, topo_dir)
self._isisd = self._routers
self._isisd_conf = os.path.join(topo_dir, "scenario/isis/isisd")
return self._topo

View File

@ -7,8 +7,6 @@ class NetTopo(Topo):
"""The network topology.
"""
topo_dir = os.path.dirname(os.path.realpath(__file__))
def __init__(self):
# Add default members to class.
super(NetTopo, self).__init__()

View File

@ -7,8 +7,6 @@ class NetTopo(Topo):
"""The network topology.
"""
topo_dir = os.path.dirname(os.path.realpath(__file__))
def __init__(self):
# Add default members to class.
super(NetTopo, self).__init__()