"""Console script entry point for AutoNetkit"""
from autonetkit.nidb import NIDB
import autonetkit.render as render
import random
import traceback
from datetime import datetime
import os
import time
import autonetkit.compiler as compiler
import pkg_resources
import autonetkit.log as log
import autonetkit.ank_messaging as ank_messaging
import autonetkit.config as config
import autonetkit.ank_json as ank_json
# import autonetkit.bgp_pol as bgp_pol
# raise SystemExit
# TODO: make if measure set, then not compile - or warn if both set, as
# don't want to regen topology when measuring
try:
ANK_VERSION = pkg_resources.get_distribution("autonetkit").version
except pkg_resources.DistributionNotFound:
ANK_VERSION = "dev"
[docs]def file_monitor(filename):
"""Generator based function to check if a file has changed"""
last_timestamp = os.stat(filename).st_mtime
while True:
timestamp = os.stat(filename).st_mtime
if timestamp > last_timestamp:
last_timestamp = timestamp
yield True
yield False
[docs]def manage_network(input_graph_string, timestamp, build_options, reload_build=False, grid = None):
"""Build, compile, render network as appropriate"""
# import build_network_simple as build_network
import autonetkit.build_network as build_network
if reload_build:
# remap?
build_network = reload(build_network)
messaging = ank_messaging.AnkMessaging()
if build_options['build']:
if input_graph_string:
graph = build_network.load(input_graph_string)
elif grid:
graph = build_network.grid_2d(grid)
anm = build_network.build(graph)
if not build_options['compile']:
# publish without nidb
body = ank_json.dumps(anm)
messaging.publish_compressed("www", "client", body)
if build_options['compile']:
if build_options['archive']:
anm.save()
nidb = compile_network(anm)
body = ank_json.dumps(anm, nidb)
messaging.publish_compressed("www", "client", body)
log.debug("Sent ANM to web server")
if build_options['archive']:
nidb.save()
# render.remove_dirs(["rendered"])
if build_options['render']:
render.render(nidb)
if not(build_options['build'] or build_options['compile']):
# Load from last run
import autonetkit.anm
anm = autonetkit.anm.AbstractNetworkModel()
anm.restore_latest()
nidb = NIDB()
nidb.restore_latest()
body = ank_json.dumps(anm, nidb)
messaging.publish_compressed("www", "client", body)
if build_options['diff']:
import autonetkit.diff
nidb_diff = autonetkit.diff.nidb_diff()
import json
data = json.dumps(nidb_diff, cls=ank_json.AnkEncoder, indent=4)
log.info("Wrote diff to diff.json")
with open("diff.json", "w") as fh: # TODO: make file specified in config
fh.write(data)
# Note: this clobbers command line options
# build_options.update(settings['General']) # update in case build has updated, eg for deploy
# build_options.update(settings['General']) # update in case build has
# updated, eg for deploy
if build_options['deploy']:
deploy_network(anm, nidb, input_graph_string)
if build_options['measure']:
measure_network(nidb)
log.info("Finished")
[docs]def parse_options():
"""Parse user-provided options"""
import argparse
usage = "autonetkit -f input.graphml"
version = "%(prog)s " + str(ANK_VERSION)
parser = argparse.ArgumentParser(description=usage, version=version)
input_group = parser.add_mutually_exclusive_group()
input_group.add_argument(
'--file', '-f', default=None, help="Load topology from FILE")
input_group.add_argument('--stdin', action="store_true", default=False,
help="Load topology from STDIN")
# TODO: move from -f to -i for --input
parser.add_argument(
'--monitor', '-m', action="store_true", default=False,
help="Monitor input file for changes")
parser.add_argument('--debug', action="store_true",
default=False, help="Debug mode")
parser.add_argument('--diff', action="store_true", default=False,
help="Diff NIDB")
parser.add_argument('--compile', action="store_true",
default=False, help="Compile")
parser.add_argument(
'--build', action="store_true", default=False, help="Build")
parser.add_argument('--render', action="store_true",
default=False, help="Compile")
parser.add_argument('--deploy', action="store_true",
default=False, help="Deploy")
parser.add_argument('--archive', action="store_true", default=False,
help="Archive ANM, NIDB, and IP allocations")
parser.add_argument('--measure', action="store_true",
default=False, help="Measure")
parser.add_argument('--webserver', action="store_true", default=False, help="Webserver")
parser.add_argument('--grid', type=int, help="Webserver")
arguments = parser.parse_args()
return arguments
def main():
settings = config.settings
options = parse_options()
log.info("AutoNetkit %s" % ANK_VERSION)
# TODO: only allow monitor mode with options.file not options.stdin
if options.debug or settings['General']['debug']:
# TODO: fix this
import logging
logger = logging.getLogger("ANK")
logger.setLevel(logging.DEBUG)
build_options = {
'compile': options.compile or settings['General']['compile'],
'render': options.render or settings['General']['render'],
'build': options.build or settings['General']['build'],
'deploy': options.deploy or settings['General']['deploy'],
'measure': options.measure or settings['General']['measure'],
'monitor': options.monitor or settings['General']['monitor'],
'diff': options.diff or settings['General']['diff'],
'archive': options.archive or settings['General']['archive'],
}
if options.webserver:
log.info("Webserver not yet supported, please run as seperate module")
if options.file:
with open(options.file, "r") as fh:
input_string = fh.read()
timestamp = os.stat(options.file).st_mtime
elif options.stdin:
import sys
input_string = sys.stdin
now = datetime.now()
timestamp = now.strftime("%Y%m%d_%H%M%S_%f")
elif options.grid:
input_string = ""
now = datetime.now()
timestamp = now.strftime("%Y%m%d_%H%M%S_%f")
pass # don't have input file
else:
log.info("No input file specified. Exiting")
raise SystemExit
manage_network(input_string, timestamp, build_options=build_options, grid = options.grid)
# TODO: work out why build_options is being clobbered for monitor mode
build_options['monitor'] = options.monitor or settings['General'][
'monitor']
if build_options['monitor']:
try:
log.info("Monitoring for updates...")
input_filemonitor = file_monitor(options.file)
build_filemonitor = file_monitor("autonetkit/build_network.py")
while True:
time.sleep(1)
rebuild = False
reload_build = False
if input_filemonitor.next():
rebuild = True
if build_filemonitor.next():
reload_build = True
rebuild = True
if rebuild:
try:
log.info("Input graph updated, recompiling network")
with open(options.file, "r") as fh:
input_string = fh.read() # read updates
manage_network(input_string,
timestamp, build_options, reload_build)
log.info("Monitoring for updates...")
except Exception, e:
log.warning("Unable to build network %s" %e)
traceback.print_exc()
except KeyboardInterrupt:
# TODO: need to close filehandles for input and output
log.info("Exiting")
def compile_network(anm):
nidb = NIDB()
g_phy = anm['phy']
g_ipv4 = anm['ipv4']
g_graphics = anm['graphics']
# TODO: build this on a platform by platform basis
nidb.add_nodes_from(
g_phy, retain=['label', 'host', 'platform', 'Network', 'update'])
cd_nodes = [n for n in g_ipv4.nodes(
"collision_domain") if not n.is_switch] # Only add created cds - otherwise overwrite host of switched
nidb.add_nodes_from(
cd_nodes, retain=['label', 'host'], collision_domain=True)
# add edges to switches
edges_to_add = [edge for edge in g_phy.edges(
) if edge.src.is_switch or edge.dst.is_switch]
edges_to_add += [edge for edge in g_ipv4.edges(
) if edge.src.collision_domain or edge.dst.collision_domain]
nidb.add_edges_from(edges_to_add, retain='edge_id')
# TODO: boundaries is still a work in progress...
nidb.copy_graphics(g_graphics)
for target, target_data in config.settings['Compile Targets'].items():
host = target_data['host']
platform = target_data['platform']
if platform == "netkit":
platform_compiler = compiler.NetkitCompiler(nidb, anm, host)
elif platform == "cisco":
platform_compiler = compiler.CiscoCompiler(nidb, anm, host)
elif platform == "dynagen":
platform_compiler = compiler.DynagenCompiler(nidb, anm, host)
elif platform == "junosphere":
platform_compiler = compiler.JunosphereCompiler(nidb, anm, host)
if any(g_phy.nodes(host=host, platform=platform)):
log.info("Compile for %s on %s" % (platform, host))
platform_compiler.compile() # only compile if hosts set
else:
log.debug("No devices set for %s on %s" % (platform, host))
return nidb
def deploy_network(anm, nidb, input_graph_string):
# TODO: make this driven from config file
log.info("Deploying network")
# TODO: pick up platform, host, filenames from nidb (as set in there)
deploy_hosts = config.settings['Deploy Hosts']
for hostname, host_data in deploy_hosts.items():
for platform, platform_data in host_data.items():
if not platform_data['deploy']:
log.debug("Not deploying to %s on %s" % (platform, hostname))
continue
config_path = os.path.join("rendered", hostname, platform)
if hostname == "internal":
try:
from autonetkit_cisco import deploy as cisco_deploy
except ImportError:
pass # development module, may not be available
if platform == "cisco":
if input_graph_string: # input xml file
cisco_deploy.package(nidb, config_path, input_graph_string)
else:
cisco_deploy.create_xml(anm, nidb, input_graph_string)
continue
username = platform_data['username']
key_file = platform_data['key file']
host = platform_data['host']
if platform == "netkit":
import autonetkit.deploy.netkit as netkit_deploy
tar_file = netkit_deploy.package(config_path, "nklab")
netkit_deploy.transfer(
host, username, tar_file, tar_file, key_file)
netkit_deploy.extract(host, username, tar_file,
config_path, timeout=60, key_filename=key_file)
if platform == "cisco":
cisco_deploy.package(config_path, "nklab")
def measure_network(nidb):
import autonetkit.measure as measure
log.info("Measuring network")
remote_hosts = [node.tap.ip for node in nidb.nodes("is_router")]
dest_node = random.choice([n for n in nidb.nodes("is_l3device")])
log.info("Tracing to randomly selected node: %s" % dest_node)
dest_ip = dest_node.interfaces[0].ipv4_address # choose random interface on this node
command = "traceroute -n -a -U -w 0.5 %s" % dest_ip
measure.send(nidb, command, remote_hosts, threads = 10)
# abort after 10 fails, proceed on any success, 0.1 second timeout (quite aggressive)
#command = 'vtysh -c "show ip route"'
#measure.send(nidb, command, remote_hosts, threads = 5)
remote_hosts = [node.tap.ip for node in nidb.nodes(
"is_router") if node.bgp.ebgp_neighbors]
command = "cat /var/log/zebra/bgpd.log"
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass