├── .gitignore ├── README.md ├── sample.dot └── create_map.py /.gitignore: -------------------------------------------------------------------------------- 1 | data 2 | *.pyc 3 | create_all.sh 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | zabbix-map-creation 2 | =================== 3 | 4 | A script to create a zabbix map from a graphviz.dot file 5 | 6 | 7 | This is code updated from: 8 | http://blog.zabbix.com/maps-for-the-lazy/2898/ 9 | 10 | But tweaked to allow custom username, password, host, file to parse, etc. It also does an update instead of a delete and create, so that URL's don't change. 11 | -------------------------------------------------------------------------------- /sample.dot: -------------------------------------------------------------------------------- 1 | graph G { 2 | //These are attempts to manipulate the layout. 3 | //Better ways exist, I'm certain. 4 | 5 | //Force straight lines 6 | graph [layout=dot overlap=false sep="+5,5" splines=false] 7 | //Force icons to be considered of the same and constant size 8 | node [shape=box fixedsize=true width=1 height=1] 9 | 10 | //We don't want to type the long hostname when defining edges 11 | server1 [hostname="server1.name", zbximage="Rackmountable_1U_server_3D_(128)"] 12 | databaseserver1 [hostname="databaseserver1.name", zbximage="Rackmountable_1U_server_3D_(128)"] 13 | 14 | 15 | server2 [hostname="server2.name", zbximage="Rackmountable_1U_server_3D_(128)"] 16 | 17 | loadbalancer [hostname="virtualip" zbximage="Rackmountable_1U_server_3D_(128)"] 18 | 19 | //This notation allows for muliple edges from router in one go 20 | loadbalancer -- { server1 server2 } [color="green"] 21 | server1 -- databaseserver1 22 | 23 | //A separate daisy chain of nodes without further details 24 | //host_a -- host_b -- host_c -- host_d [label="Wheee"] 25 | } 26 | -------------------------------------------------------------------------------- /create_map.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # Volker Fröhlich, 2013 4 | # volker27@gmx.at 5 | 6 | """ 7 | Creates a Zabbix map from a Dot file 8 | zbximage and hostname are custom attributes that can be attached to nodes. 9 | Nodes with a hostname attribute are considered Zabbix hosts and looked up 10 | for. Other nodes are treated as images. zbximage and label can be used there. 11 | Edges have their color and label attributes considered. 12 | 13 | This script is meant as an example only! 14 | """ 15 | 16 | # Curly brace edge notation requires a patched networkx module 17 | # https://github.com/networkx/networkx/issues/923 18 | 19 | import networkx as nx 20 | import optparse 21 | import sys 22 | from pyzabbix import ZabbixAPI 23 | parser = optparse.OptionParser() 24 | parser.add_option('-u', '--username', help="Username", default="admin") 25 | parser.add_option('-p', '--password', help="Password", default="zabbix") 26 | parser.add_option('-s', '--host', help="Host To talk to the web api", default="localhost") 27 | parser.add_option('-d', '--path', help="Path", default="/zabbix/") 28 | parser.add_option('-f', '--mapfile', help=".dot graphviz file for imput", default="data.dot") 29 | parser.add_option('-n', '--mapname', help="Map name to put into zabbix") 30 | parser.add_option('-r', '--protocol', help="Protocol to be used", default="http") 31 | (options,args)=parser.parse_args() 32 | 33 | if not options.mapname: 34 | print "Must have a map name!" 35 | parser.print_help() 36 | sys.exit(-1) 37 | 38 | width = 1920 39 | height = 1280 40 | 41 | ELEMENT_TYPE_HOST = 0 42 | ELEMENT_TYPE_MAP = 1 43 | ELEMENT_TYPE_TRIGGER = 2 44 | ELEMENT_TYPE_HOSTGROUP = 3 45 | ELEMENT_TYPE_IMAGE = 4 46 | 47 | ADVANCED_LABELS = 1 48 | LABEL_TYPE_LABEL = 0 49 | 50 | #TODO: Available images should be read via the API instead 51 | #icons = { 52 | #"router": 130, 53 | #"cloud": 26, 54 | #"desktop": 27, 55 | #"laptop": 28, 56 | #"server": 106, 57 | #"database": 20, 58 | #"sat": 30, 59 | #"tux": 31, 60 | #"default": 40, 61 | #"house":34 62 | #} 63 | 64 | colors = { 65 | "purple": "FF00FF", 66 | "green": "00FF00", 67 | "default": "00FF00", 68 | } 69 | 70 | def icons_get(): 71 | icons = {} 72 | iconsData = zapi.image.get(output=["imageid","name"]) 73 | for icon in iconsData: 74 | icons[icon["name"]] = icon["imageid"] 75 | return icons 76 | 77 | def api_connect(): 78 | zapi = ZabbixAPI(options.protocol + "://" + options.host + options.path) 79 | zapi.login(options.username, options.password) 80 | return zapi 81 | 82 | def host_lookup(hostname): 83 | hostid = zapi.host.get(filter={"host": hostname}) 84 | if hostid: 85 | return str(hostid[0]['hostid']) 86 | 87 | def map_lookup(mapname): 88 | mapid = zapi.map.get(filter={"name": mapname}) 89 | if mapid: 90 | return str(mapid[0]['sysmapid']) 91 | 92 | ################################################################ 93 | 94 | # Convert your dot file to a graph 95 | G=nx.drawing.nx_agraph.read_dot(options.mapfile) 96 | 97 | # Use an algorithm of your choice to layout the nodes in x and y 98 | pos = nx.drawing.nx_agraph.graphviz_layout(G) 99 | 100 | # Find maximum coordinate values of the layout to scale it better to the desired output size 101 | #TODO: The scaling could probably be solved within Graphviz 102 | # The origin is different between Zabbix (top left) and Graphviz (bottom left) 103 | # Join the temporary selementid necessary for links and the coordinates to the node data 104 | poslist=list(pos.values()) 105 | maxpos=map(max, zip(*poslist)) 106 | 107 | for host, coordinates in pos.iteritems(): 108 | pos[host] = [int(coordinates[0]*width/maxpos[0]*0.65-coordinates[0]*0.1), int((height-coordinates[1]*height/maxpos[1])*0.65+coordinates[1]*0.1)] 109 | nx.set_node_attributes(G,'coordinates',pos) 110 | 111 | selementids = dict(enumerate(G.nodes_iter(), start=1)) 112 | selementids = dict((v,k) for k,v in selementids.iteritems()) 113 | nx.set_node_attributes(G,'selementid',selementids) 114 | 115 | # Prepare map information 116 | map_params = { 117 | "name": options.mapname, 118 | "label_format": ADVANCED_LABELS, 119 | "label_type_image": LABEL_TYPE_LABEL, 120 | "width": width, 121 | "height": height 122 | } 123 | element_params=[] 124 | link_params=[] 125 | 126 | zapi = api_connect() 127 | icons = icons_get() 128 | 129 | 130 | # Prepare node information 131 | for node, data in G.nodes_iter(data=True): 132 | # Generic part 133 | map_element = {} 134 | map_element.update({ 135 | "selementid": data['selementid'], 136 | "x": data['coordinates'][0], 137 | "y": data['coordinates'][1], 138 | "use_iconmap": 0, 139 | }) 140 | 141 | if "hostname" in data: 142 | map_element.update({ 143 | "elementtype": ELEMENT_TYPE_HOST, 144 | "elementid": host_lookup(data['hostname'].strip('"')), 145 | "iconid_off": icons['Rackmountable_2U_server_3D_(128)'], 146 | }) 147 | elif "map" in data: 148 | map_element.update({ 149 | "elementtype": ELEMENT_TYPE_MAP, 150 | "elementid": map_lookup(data['map'].strip('"')), 151 | "iconid_off": icons['Cloud_(96)'], 152 | }) 153 | 154 | else: 155 | map_element.update({ 156 | "elementtype": ELEMENT_TYPE_IMAGE, 157 | "elementid": 0, 158 | }) 159 | # Labels are only set for images 160 | # elementid is necessary, due to ZBX-6844 161 | # If no image is set, a default image is used 162 | if "label" in data: 163 | map_element.update({ 164 | "label": data['label'].strip('"') 165 | }) 166 | if "zbximage" in data: 167 | map_element.update({ 168 | "iconid_off": icons[data['zbximage'].strip('"')], 169 | }) 170 | elif "hostname" not in data and "zbximage" not in data: 171 | map_element.update({ 172 | "iconid_off": icons['Cloud_(96)'], 173 | }) 174 | 175 | element_params.append(map_element) 176 | 177 | # Prepare edge information -- Iterate through edges to create the Zabbix links, 178 | # based on selementids 179 | nodenum = nx.get_node_attributes(G,'selementid') 180 | for nodea, nodeb, data in G.edges_iter(data=True): 181 | link = {} 182 | link.update({ 183 | "selementid1": nodenum[nodea], 184 | "selementid2": nodenum[nodeb], 185 | }) 186 | 187 | if "color" in data: 188 | color = colors[data['color'].strip('"')] 189 | link.update({ 190 | "color": color 191 | }) 192 | else: 193 | link.update({ 194 | "color": colors['default'] 195 | }) 196 | 197 | if "label" in data: 198 | label = data['label'].strip('"') 199 | link.update({ 200 | "label": label, 201 | }) 202 | 203 | link_params.append(link) 204 | 205 | # Join the prepared information 206 | map_params["selements"] = element_params 207 | map_params["links"] = link_params 208 | 209 | # Get rid of an existing map of that name and create a new one 210 | del_mapid = zapi.map.get(filter={"name": options.mapname}) 211 | if del_mapid: 212 | zapi.map.update({"sysmapid":del_mapid[0]['sysmapid'], "links":[], "selements":[], "urls":[] }) 213 | map_params["sysmapid"] = del_mapid[0]['sysmapid'] 214 | #del map_params["name"] 215 | #del map_params["label_format"] 216 | #del map_params["label_type_image"] 217 | #del map_params["width"] 218 | #del map_params["height"] 219 | map=zapi.map.update(map_params) 220 | else: 221 | map = zapi.map.create(map_params) 222 | --------------------------------------------------------------------------------