AutoNetkit API tutorial¶
Written for AutoNetkit 0.9
Create Network
import autonetkit
anm = autonetkit.NetworkModel()
g_in = anm.add_overlay("input")
nodes = ['r1', 'r2', 'r3', 'r4', 'r5']
g_in.add_nodes_from(nodes)
g_in.update(device_type = "router", asn=1)
g_in.update("r5", asn = 2)
positions = {'r1': (10, 79),
'r2': (226, 25),
'r3': (172, 295),
'r4': (334, 187),
'r5': (496, 349)}
for n in g_in:
n.x, n.y = positions[n]
autonetkit.update_http(anm)
edges = [("r1", "r2"), ("r2", "r4"), ("r1", "r3"),
("r3", "r4"), ("r3", "r5"), ("r4", "r5")]
g_in.add_edges_from(edges)
autonetkit.update_http(anm)
g_in.allocate_input_interfaces()
autonetkit.update_http(anm)
for node in sorted(g_in):
print node
for index, interface in enumerate(node.physical_interfaces()):
print interface
interface.name = "eth%s" % index
print node
autonetkit.update_http(anm)
g_phy = anm['phy']
g_phy.add_nodes_from(g_in, retain=["asn", "device_type", "x", "y"])
g_phy.update(use_ipv4 = True, host = "localhost", platform = "netkit", syntax = "quagga")
g_phy.add_edges_from(g_in.edges())
autonetkit.update_http(anm)
g_ospf = anm.add_overlay("ospf")
g_ospf.add_nodes_from(g_in.routers())
g_ospf.add_edges_from(e for e in g_in.edges()
if e.src.asn == e.dst.asn)
autonetkit.update_http(anm)
for node in g_ospf:
for interface in node.physical_interfaces():
interface.cost = 10
autonetkit.update_http(anm)
g_ebgp = anm.add_overlay("ebgp_v4", directed = True)
g_ebgp.add_nodes_from(g_in.routers())
edges = [e for e in g_in.edges()
if e.src.asn != e.dst.asn]
# Add in both directions
g_ebgp.add_edges_from(edges, bidirectional = True)
autonetkit.update_http(anm)
g_ibgp = anm.add_overlay("ibgp_v4", directed = True)
g_ibgp.add_nodes_from(g_in.routers())
edges = [(s,t) for s in g_ibgp for t in g_ibgp
# belong to same ASN, but not self-loops
if s != t and s.asn == t.asn]
# Add in both directions
g_ibgp.add_edges_from(edges, bidirectional = True)
autonetkit.update_http(anm)
import autonetkit.ank as ank_utils
g_ipv4 = anm.add_overlay("ipv4")
g_ipv4.add_nodes_from(g_in)
g_ipv4.add_edges_from(g_in.edges())
# Split the point-to-point edges to add a collision domain
edges_to_split = [edge for edge in g_ipv4.edges()
if edge.src.is_l3device() and edge.dst.is_l3device()]
for edge in edges_to_split:
edge.split = True # mark as split for use in building nidb
split_created_nodes = list(ank_utils.split(g_ipv4, edges_to_split,
retain=['split'], id_prepend='bc'))
for node in split_created_nodes:
# Set the co-ordinates using 'x', 'y' of g_in
# based on neighbors in g_ipv4
node.x = ank_utils.neigh_average(g_ipv4, node, 'x', g_in)
node.y = ank_utils.neigh_average(g_ipv4, node, 'y', g_in)
# Set most frequent of asn property in g_phy
# ASN is used to allocate IPs
node.asn = ank_utils.neigh_most_frequent(g_ipv4, node,
'asn', g_phy)
node.broadcast_domain = True
node.device_type = "broadcast_domain"
autonetkit.update_http(anm)
# Now use allocation plugin
import autonetkit.plugins.ipv4 as ipv4
ipv4.allocate_infra(g_ipv4)
ipv4.allocate_loopbacks(g_ipv4)
autonetkit.update_http(anm)
# Now construct NIDB
nidb = autonetkit.DeviceModel()
# NIDB is separate to the ANM -> copy over more properties
retain = ['label', 'host', 'platform', 'x', 'y', 'asn', 'device_type']
nidb.add_nodes_from(g_phy, retain=retain)
# Usually have a base g_ip which has structure
# allocate to g_ipv4, g_ipv6
retain.append("subnet") # also copy across subnet
nidb.add_nodes_from(g_ipv4.nodes("broadcast_domain"), retain=retain)
nidb.add_edges_from(g_ipv4.edges())
# Also need to copy across the collision domains
autonetkit.update_http(anm, nidb)
anm.add_overlay("ipv6")
anm.add_overlay("bgp")
autonetkit.update_http(anm)
import autonetkit.compilers.platform.netkit as pl_netkit
host = "localhost"
platform_compiler = pl_netkit.NetkitCompiler(nidb, anm, host)
platform_compiler.compile()
import autonetkit.render
autonetkit.render.render(nidb)
The output files are put
into rendered/localhost/netkit
For instance:
├── lab.conf
├── r1
│ ├── etc
│ │ ├── hostname
│ │ ├── shadow
│ │ ├── ssh
│ │ │ └── sshd_config
│ │ └── zebra
│ │ ├── bgpd.conf
│ │ ├── daemons
│ │ ├── isisd.conf
│ │ ├── motd.txt
│ │ ├── ospfd.conf
│ │ └── zebra.conf
│ └── root
├── r1.startup
├── r2
│ ├── etc
│ │ ├── hostname
│ │ ├── shadow
│ │ ├── ssh
│ │ │ └── sshd_config
│ │ └── zebra
│ │ ├── bgpd.conf
│ │ ├── daemons
│ │ ├── isisd.conf
│ │ ├── motd.txt
│ │ ├── ospfd.conf
│ │ └── zebra.conf
│ └── root
├── r2.startup
Can also write our own compiler and templates:
# AutoNetkit renderer expects filenames for templates
# uses the Mako template format
router_template_str = """Router |||rendered on ${date} by ${version_banner}
% for interface in node.interfaces:
interface ${interface.id}
description ${interface.description}
ip address ${interface.ipv4_address} netmask ${interface.ipv4_netmask}
% endfor
!
router ospf ${node.ospf.process_id}
% for link in node.ospf.ospf_links:
network ${link.network.cidr} area ${link.area}
% endfor
!
router bgp ${node.asn}
% for neigh in node.bgp.ibgp_neighbors:
! ${neigh.neighbor}
neighbor ${neigh.loopback} remote-as ${neigh.asn}
neighbor ${neigh.loopback} update-source ${node.loopback_zero.ipv4_address}
neighbor ${neigh.loopback} next-hop-self
% endfor
!
% for neigh in node.bgp.ebgp_neighbors:
! ${neigh.neighbor}
neighbor ${neigh.dst_int_ip} remote-as ${neigh.asn}
neighbor ${neigh.dst_int_ip} update-source ${neigh.local_int_ip}
% endfor
!
"""
router_template = "router.mako"
with open(router_template, "w") as fh:
fh.write(router_template_str)
from autonetkit.compilers.device import router_base
from autonetkit.nidb.config_stanza import ConfigStanza as ConfigStanza
class simple_router_compiler(router_base.RouterCompiler):
lo_interface = 'lo:1'
def compile(self, node):
self.interfaces(node)
self.ospf(node)
self.bgp(node)
def interfaces(self, node):
# Append attributes to the interface, rather than add a stanza
ipv4_node = self.anm['ipv4'].node(node)
if node.is_l3device:
node.loopback_zero.id = self.lo_interface
node.loopback_zero.description = 'Loopback'
node.loopback_zero.ipv4_address = ipv4_node.loopback
node.loopback_zero.ipv4_netmask = "255.255.255.255"
#interface_list.append(stanza)
for interface in node.physical_interfaces():
ipv4_int = ipv4_node.interface(interface)
interface.ipv4_address = ipv4_int.ip_address
interface.ipv4_netmask = ipv4_int.subnet.netmask
def ospf(self, node):
node.add_stanza("ospf", process_id = 1)
ospf_links = []
for interface in node.physical_interfaces():
ipv4_int = self.anm['ipv4'].interface(interface)
ospf_links.append(ConfigStanza(network=ipv4_int.subnet,
area=0))
node.ospf.ospf_links = ospf_links
def bgp(self, node):
node.add_stanza("bgp")
g_ebgp = self.anm["ebgp_v4"]
ebgp_neighbors = []
ibgp_neighbors = []
for session in g_ebgp.edges(node):
neighbor = session.dst # remote node
stanza = ConfigStanza(neighbor = neighbor,
asn = neighbor.asn)
# Can obtain the dst int, as created bgp session
# from physical links
stanza.local_int_ip = session.src_int['ipv4'].ip_address
stanza.dst_int_ip = session.dst_int['ipv4'].ip_address
ebgp_neighbors.append(stanza)
for session in g_ibgp.edges(node):
neighbor = session.dst # remote node
stanza = ConfigStanza(neighbor = neighbor,
asn = neighbor.asn)
stanza.loopback = neighbor['ipv4'].loopback
ibgp_neighbors.append(stanza)
node.bgp.ebgp_neighbors = sorted(ebgp_neighbors, key = lambda x: x.neighbor)
node.bgp.ibgp_neighbors = sorted(ibgp_neighbors, key = lambda x: x.neighbor)
topology_template_str = """Topology rendered on ${date} by ${version_banner}
% for host in topology.hosts:
host: ${host}
% endfor
"""
topology_template = "topology.mako"
with open(topology_template, "w") as fh:
fh.write(topology_template_str)
from autonetkit.compilers.platform import platform_base
import netaddr
from autonetkit.nidb import config_stanza
class simple_platform_compiler(platform_base.PlatformCompiler):
def compile(self):
rtr_comp = simple_router_compiler(self.nidb, self.anm)
for node in nidb.routers(host=host):
for index, interface in enumerate(node.physical_interfaces()):
interface.id = "eth%s" % index
# specify router template
node.add_stanza("render")
node.render.template = router_template
node.render.dst_folder = "rendered"
node.render.dst_file = "%s.conf" % node
# enable rendering for node
node.do_render = True
# and compile
rtr_comp.compile(node)
# and the topology
lab_topology = self.nidb.topology(self.host)
# template settings for the renderer
lab_topology.render_template = topology_template
lab_topology.render_dst_folder = "rendered"
lab_topology.render_dst_file = "lab.conf"
lab_topology.hosts = []
for node in nidb.routers(host=host):
lab_topology.hosts.append(node)
# Now construct NIDB
nidb = autonetkit.DeviceModel()
# NIDB is separate to the ANM -> copy over more properties
retain = ['label', 'host', 'platform', 'x', 'y', 'asn', 'device_type']
nidb.add_nodes_from(g_phy, retain=retain)
# Usually have a base g_ip which has structure
# allocate to g_ipv4, g_ipv6
retain.append("subnet") # also copy across subnet
nidb.add_nodes_from(g_ipv4.nodes("broadcast_domain"), retain=retain)
nidb.add_edges_from(g_ipv4.edges())
# Also need to copy across the collision domains
autonetkit.update_http(anm, nidb)
sim_plat = simple_platform_compiler(nidb, anm, "localhost")
sim_plat.compile()
autonetkit.update_http(anm, nidb)
for node in nidb:
print node.dump()
import autonetkit.render
autonetkit.render.render(nidb)
with open("rendered/lab.conf") as fh:
print fh.read()
with open("rendered/r1.conf") as fh:
print fh.read()
with open("rendered/r5.conf") as fh:
print fh.read()