#!/usr/bin/python # -*- coding: utf-8 -*- # Volker Fröhlich, 2013 # volker27@gmx.at """ Creates a map of images from topological data Following up the Zabbix Conference 2012 talk in Riga The source data stems from a Cisco Prime topology export CSV file """ # Requires python-networkx 1.7 or newer for some operations import csv import networkx as nx from zabbix_api import ZabbixAPI server="https://zabbix.example.com" username="zabbix" password="password" width = 800 height = 600 mapname = "ciscotopo" main_loop_ipaddr = "10.110.20.1" main_vlan_ipaddr = "149.148.56.1" zabbix_service_ipaddr = "149.148.20.123" # === CREATE INITIAL GRAPH === G=nx.Graph() csv_reader = csv.DictReader(open('cp_export.csv'), \ delimiter=",", \ fieldnames=("ipaddress", "hostname", "oid", "dontcare", "neighbors")) # Skip header csv_reader.next() for row in csv_reader: neighbor_list = row["neighbors"].split(",") for neighbor in neighbor_list: # Remove spaces neighbor = neighbor.lstrip() # Add neighbors, ignore isolated nodes if neighbor != "": G.add_edge(row["ipaddress"], neighbor) # Add additional information to nodes or edges here G.node[row["ipaddress"]]["hostname"] = row["hostname"] # === CORRECT GRAPH === # Cisco Prime doesn't export all IP addresses of a device; # Only the first for each network # Merge hosts with multiple IP addresses mapping = {main_vlan_ipaddr: main_loop_ipaddr} G = nx.relabel_nodes(G, mapping) # Remove bogus intra-cluster connection, not relevant for operation G.remove_edge("10.110.2.1", "10.110.2.2") # === COMPLETE GRAPH === # Firewalls are not included in the exported file; # neither are non-Cisco devices # Adding connection between Zabbix server and main switch G.add_edge(zabbix_service_ipaddr, main_loop_ipaddr) pos = nx.graphviz_layout(G) # Find maximum coordinate values of the layout to scale it better to the desired output size # The origin is different between Zabbix (top left) and Graphviz (bottom left) # Join the temporary selementid necessary for links and the coordinates to the node data poslist=list(pos.values()) maxpos=map(max, zip(*poslist)) for host, coordinates in pos.iteritems(): pos[host] = [int(coordinates[0]*width/maxpos[0]*0.95-coordinates[0]*0.1), int((height-coordinates[1]*height/maxpos[1])*0.95+coordinates[1]*0.1)] nx.set_node_attributes(G,'coordinates',pos) selementids = dict(enumerate(G.nodes_iter(), start=1)) selementids = dict((v,k) for k,v in selementids.iteritems()) nx.set_node_attributes(G,'selementid',selementids) map_params = { "name": mapname, "label_type": 0, "width": width, "height": height } element_params=[] link_params=[] for node, data in G.nodes_iter(data=True): element_params.append({ "elementtype": 4, "elementid": 0, "selementid": data['selementid'], "iconid_off": "26", "use_iconmap": 0, "label": node, "x": data['coordinates'][0], "y": data['coordinates'][1], }) # Iterate through edges to create the Zabbix links, based on selementids nodenum = nx.get_node_attributes(G,'selementid') for nodea, nodeb in G.edges_iter(): link_params.append({ "selementid1": nodenum[nodea], "selementid2": nodenum[nodeb] }) # Join the map data together map_params["selements"] = element_params map_params["links"] = link_params zapi = ZabbixAPI(server=server, path="", log_level=1) zapi.login(username, password) # Get rid of an existing map of that name and create a new one del_mapid = zapi.map.get({"filter": {"name": mapname}}) if del_mapid: zapi.map.delete([del_mapid[0]['sysmapid']]) map=zapi.map.create(map_params)