blob: 603835da7f5eeea82d8b58630615aca4b4419dc1 [file] [log] [blame]
Steve McIntyre2454bf02015-09-23 18:33:02 +01001#! /usr/bin/python
2
3# Copyright 2015 Linaro Limited
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18# MA 02110-1301, USA.
19#
20# Visualisation module for VLANd. Fork a trivial webserver
21# implementation on an extra, and generate a simple set of pages and
22# graphics on demand.
23#
24
Steve McIntyrebc06c932018-01-31 17:04:29 +000025import os, sys, logging, time, datetime, re, signal
Steve McIntyre2454bf02015-09-23 18:33:02 +010026from multiprocessing import Process
27from BaseHTTPServer import BaseHTTPRequestHandler
28from BaseHTTPServer import HTTPServer
29import urlparse
Steve McIntyre57a9d0a2015-10-28 18:22:31 +000030import cStringIO
Steve McIntyre2454bf02015-09-23 18:33:02 +010031
Steve McIntyre6c7ca7f2018-03-05 18:15:51 +000032from Vland.errors import InputError
33from Vland.db.db import VlanDB
34from Vland.config.config import VlanConfig
35from Vland.visualisation.graphics import Graphics,Switch
36from Vland.util import VlanUtil
Steve McIntyre2454bf02015-09-23 18:33:02 +010037class VlandHTTPServer(HTTPServer):
38 """ Trivial wrapper for HTTPServer so we can include our own state. """
39 def __init__(self, server_address, handler, state):
40 HTTPServer.__init__(self, server_address, handler)
41 self.state = state
42
Steve McIntyre57a9d0a2015-10-28 18:22:31 +000043class GraphicsCache(object):
44 """ Cache for graphics state, to avoid having to recalculate every
45 query too many times. """
46 last_update = None
Steve McIntyre57a9d0a2015-10-28 18:22:31 +000047 graphics = {}
48
49 def __init__(self):
50 # Pick an epoch older than any sensible use
51 self.last_update = datetime.datetime(2000, 01, 01)
52
Steve McIntyre2454bf02015-09-23 18:33:02 +010053class Visualisation(object):
54 """ Code and config for the visualisation graphics module. """
55
56 state = None
57 p = None
58
59 # Fork a new process for the visualisation webserver
60 def __init__(self, state):
61 self.state = state
62 self.p = Process(target=self.visloop, args=())
63 self.p.start()
64
Steve McIntyrebc06c932018-01-31 17:04:29 +000065 def _receive_signal(self, signum, stack):
66 if signum == signal.SIGUSR1:
67 self.state.db_ok = True
68
Steve McIntyre2454bf02015-09-23 18:33:02 +010069 # The main loop for the visualisation webserver
70 def visloop(self):
Steve McIntyrebc06c932018-01-31 17:04:29 +000071 self.state.db_ok = False
Steve McIntyre57a9d0a2015-10-28 18:22:31 +000072 self.state.cache = GraphicsCache()
Steve McIntyre2454bf02015-09-23 18:33:02 +010073
74 loglevel = VlanUtil().set_logging_level(self.state.config.logging.level)
75
76 # Should we log to stderr?
77 if self.state.config.logging.filename is None:
78 logging.basicConfig(level = loglevel,
79 format = '%(asctime)s %(levelname)-8s %(message)s')
80 else:
81 logging.basicConfig(level = loglevel,
82 format = '%(asctime)s %(levelname)-8s VIS %(message)s',
83 datefmt = '%Y-%m-%d %H:%M:%S %Z',
84 filename = self.state.config.logging.filename,
85 filemode = 'a')
86 logging.info('%s visualisation starting up', self.state.banner)
87
Steve McIntyrebc06c932018-01-31 17:04:29 +000088 # Wait for main process to signal to us that it's finished with any
89 # database upgrades and we can open it without any problems.
90 signal.signal(signal.SIGUSR1, self._receive_signal)
91 while not self.state.db_ok:
92 logging.info('%s visualisation waiting for db_ok signal', self.state.banner)
93 time.sleep(1)
94 logging.info('%s visualisation received db_ok signal', self.state.banner)
95
96 self.state.db = VlanDB(db_name=self.state.config.database.dbname,
97 username=self.state.config.database.username,
98 readonly=True)
99
Steve McIntyre86916e42015-09-28 02:39:32 +0100100 server = VlandHTTPServer(('', self.state.config.visualisation.port),
Steve McIntyre2454bf02015-09-23 18:33:02 +0100101 GetHandler, self.state)
102 server.serve_forever()
103
104 # Kill the webserver
105 def shutdown(self):
106 self.p.terminate()
107
Steve McIntyrebc06c932018-01-31 17:04:29 +0000108 # Kill the webserver
109 def signal_db_ok(self):
110 os.kill(self.p.pid, signal.SIGUSR1)
111
Steve McIntyre2454bf02015-09-23 18:33:02 +0100112class GetHandler(BaseHTTPRequestHandler):
113 """ Methods to generate and serve the pages """
114
115 parsed_path = None
116
117 # Trivial top-level page. Link to images for each of the VLANs we
118 # know about.
119 def send_index(self):
120 self.send_response(200)
121 self.wfile.write('Content-type: text/html\r\n')
122 self.end_headers()
Steve McIntyreb0aa4602015-10-08 15:33:28 +0100123 config = self.server.state.config.visualisation
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000124 cache = self.server.state.cache
125 db = self.server.state.db
126 switches = db.all_switches()
127 vlans = db.all_vlans()
128 vlan_tags = {}
129
130 for vlan in vlans:
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000131 vlan_tags[vlan['vlan_id']] = vlan['tag']
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000132
133 if cache.last_update < self.server.state.db.get_last_modified_time():
134 logging.debug('Cache is out of date')
135 # Fill the cache with all the information we need:
136 # * the graphics themselves
137 # * the data to match each graphic, so we can generate imagemap/tooltips
138 cache.graphics = {}
139 if len(switches) > 0:
140 for vlan in vlans:
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000141 cache.graphics[vlan['vlan_id']] = self.generate_graphic(vlan['vlan_id'])
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000142 cache.last_update = datetime.datetime.utcnow()
143
Steve McIntyre2454bf02015-09-23 18:33:02 +0100144 page = []
Steve McIntyrede1ee972015-10-28 18:23:05 +0000145 page.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">')
Steve McIntyre2454bf02015-09-23 18:33:02 +0100146 page.append('<html>')
147 page.append('<head>')
Steve McIntyrede1ee972015-10-28 18:23:05 +0000148 page.append('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">')
Steve McIntyre2454bf02015-09-23 18:33:02 +0100149 page.append('<TITLE>VLANd visualisation</TITLE>')
Steve McIntyreb74ea6b2015-09-24 20:45:31 +0100150 page.append('<link rel="stylesheet" type="text/css" href="style.css">')
Steve McIntyreb0aa4602015-10-08 15:33:28 +0100151 if config.refresh and config.refresh > 0:
152 page.append('<meta http-equiv="refresh" content="%d">' % config.refresh)
Steve McIntyre2454bf02015-09-23 18:33:02 +0100153 page.append('</HEAD>')
154 page.append('<body>')
Steve McIntyrebfef5062015-10-30 18:28:32 +0000155
156 # Generate left-hand menu with links to each VLAN diagram
Steve McIntyreb74ea6b2015-09-24 20:45:31 +0100157 page.append('<div class="menu">')
158 if len(switches) > 0:
159 page.append('<h2>Menu</h2>')
160 page.append('<p>VLANs: %d</p>' % len(vlans))
161 page.append('<ul>')
162 for vlan in vlans:
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000163 page.append('<li><a href="./#vlan%d">VLAN ID %d, Tag %d<br>(%s)</a>' % (vlan['vlan_id'], vlan['vlan_id'], vlan['tag'], vlan['name']))
Steve McIntyreb74ea6b2015-09-24 20:45:31 +0100164 page.append('</ul>')
165 page.append('<div class="date"><p>Current time: %s</p>' % datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC"))
166 page.append('<p>version %s</p>' % self.server.state.version)
167 page.append('</div>')
168 page.append('</div>')
169
Steve McIntyrebfef5062015-10-30 18:28:32 +0000170 # Now the main content area with the graphics
Steve McIntyreb74ea6b2015-09-24 20:45:31 +0100171 page.append('<div class="content">')
172 page.append('<h1>VLANd visualisation</h1>')
173
Steve McIntyrebfef5062015-10-30 18:28:32 +0000174 # Bail early if we have nothing to show!
Steve McIntyre2454bf02015-09-23 18:33:02 +0100175 if len(switches) == 0:
176 page.append('<p>No switches found in the database, nothing to show...</p>')
Steve McIntyrebfef5062015-10-30 18:28:32 +0000177 page.append('</div>')
178 page.append('</body>')
179 self.wfile.write('\r\n'.join(page))
180 return
Steve McIntyre2454bf02015-09-23 18:33:02 +0100181
Steve McIntyrebfef5062015-10-30 18:28:32 +0000182 # Trivial javascript helpers for tooltip control
183 page.append('<SCRIPT LANGUAGE="javascript">')
184 page.append('function popup(v,p) {')
185 page.append('a=v.toString();')
186 page.append('b=p.toString();')
187 page.append('id="port".concat("",a).concat(".",b);')
188 page.append('document.getElementById(id).style.visibility="visible";')
189 page.append('}')
190 page.append('function popdown(v,p) {')
191 page.append('a=v.toString();')
192 page.append('b=p.toString();')
193 page.append('id="port".concat("",a).concat(".",b);')
194 page.append('document.getElementById(id).style.visibility="hidden";')
195 page.append('}')
196 page.append('</SCRIPT>')
197
198 # For each VLAN, add a graphic
199 for vlan in vlans:
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000200 this_image = cache.graphics[vlan['vlan_id']]
201 page.append('<a name="vlan%d"></a>' % vlan['vlan_id'])
202 page.append('<h3>VLAN ID %d, Tag %d, name %s</h3>' % (vlan['vlan_id'], vlan['tag'], vlan['name']))
Steve McIntyrebfef5062015-10-30 18:28:32 +0000203
204 # Link to an image we generate from our data
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000205 page.append('<p><img src="images/vlan/%d.png" ' % vlan['vlan_id'])
Steve McIntyrebfef5062015-10-30 18:28:32 +0000206 page.append('width="%d" height="%d" ' % ( this_image['image']['width'], this_image['image']['height']))
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000207 page.append('alt="VLAN %d diagram" usemap="#MAPVLAN%d">' % (vlan['vlan_id'],vlan['vlan_id']))
Steve McIntyrebfef5062015-10-30 18:28:32 +0000208
209 # Generate an imagemap describing all the ports, with
210 # javascript hooks to pop up/down a tooltip box based on
211 # later data.
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000212 page.append('<map name="MAPVLAN%d">' % vlan['vlan_id'])
Steve McIntyrebfef5062015-10-30 18:28:32 +0000213 for switch in this_image['ports'].keys():
214 for portnum in this_image['ports'][switch].keys():
215 this_port = this_image['ports'][switch][portnum]
216 port = this_port['db']
217 ((ulx,uly),(lrx,lry),upper) = this_port['location']
218 page.append('<area shape="rect" ')
219 page.append('coords="%d,%d,%d,%d" ' % (ulx,uly,lrx,lry))
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000220 page.append('onMouseOver="popup(%d,%d)" onMouseOut="popdown(%d,%d)">' % (vlan['vlan_id'], port['port_id'], vlan['vlan_id'], port['port_id']))
Steve McIntyrebfef5062015-10-30 18:28:32 +0000221 page.append('</map></p>')
222 page.append('<hr>')
223 page.append('</div>') # End of normal content, all the VLAN graphics shown
224
225 # Now generate the tooltip boxes for the ports. Each is
226 # fully-formed but invisible, ready for our javascript helper
227 # to pop visible on demand.
228 for vlan in vlans:
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000229 this_image = cache.graphics[vlan['vlan_id']]
Steve McIntyrebfef5062015-10-30 18:28:32 +0000230 for switch in this_image['ports'].keys():
231 for portnum in this_image['ports'][switch].keys():
232 this_port = this_image['ports'][switch][portnum]
233 port = this_port['db']
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000234 page.append('<div class="port" id="port%d.%d">' % (vlan['vlan_id'], port['port_id']))
235 page.append('Port ID: %d<br>' % port['port_id'])
236 page.append('Port Number: %d<br>' % port['number'])
237 page.append('Port Name: %s<br>' % port['name'])
238 if port['is_locked']:
Steve McIntyre7d219202018-02-01 16:53:25 +0000239 page.append('Locked - ')
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000240 if (port['lock_reason'] is not None
241 and len(port['lock_reason']) > 1):
242 page.append(port['lock_reason'])
Steve McIntyre7d219202018-02-01 16:53:25 +0000243 else:
244 page.append('unknown reason')
245 page.append('<br>')
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000246 if port['is_trunk']:
Steve McIntyrebfef5062015-10-30 18:28:32 +0000247 page.append('Trunk')
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000248 if port['trunk_id'] != -1:
249 page.append(' (Trunk ID %d)' % port['trunk_id'])
Steve McIntyrebfef5062015-10-30 18:28:32 +0000250 page.append('<br>')
251 else:
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000252 page.append('Current VLAN ID: %d (Tag %d)<br>' % (port['current_vlan_id'], vlan_tags[port['current_vlan_id']]))
253 page.append('Base VLAN ID: %d (Tag %d)<br>' % (port['base_vlan_id'], vlan_tags[port['base_vlan_id']]))
Steve McIntyrebfef5062015-10-30 18:28:32 +0000254 page.append('</div>')
255
Steve McIntyre2454bf02015-09-23 18:33:02 +0100256 page.append('</body>')
257 self.wfile.write('\r\n'.join(page))
258
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000259 # Simple-ish style sheet
Steve McIntyre2454bf02015-09-23 18:33:02 +0100260 def send_style(self):
261 self.send_response(200)
262 self.wfile.write('Content-type: text/css\r\n')
263 self.end_headers()
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000264 cache = self.server.state.cache
Steve McIntyreb74ea6b2015-09-24 20:45:31 +0100265 page = []
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000266 page.append('body {')
267 page.append(' background: white;')
268 page.append(' color: black;')
269 page.append(' font-size: 12pt;')
270 page.append('}')
271 page.append('')
272 page.append('.menu {')
273 page.append(' position:fixed;')
274 page.append(' float:left;')
275 page.append(' font-family: arial, Helvetica, sans-serif;')
276 page.append(' width:20%;')
277 page.append(' height:100%;')
278 page.append(' font-size: 10pt;')
279 page.append(' padding-top: 10px;')
280 page.append('}')
281 page.append('')
282 page.append('.content {')
283 page.append(' position:relative;')
284 page.append(' padding-top: 10px;')
Steve McIntyree8f39df2015-10-28 18:36:39 +0000285 page.append(' width: 80%;')
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000286 page.append(' max-width:80%;')
287 page.append(' margin-left: 21%;')
288 page.append(' margin-top: 50px;')
289 page.append(' height:100%;')
290 page.append('}')
291 page.append('')
Steve McIntyre8179f612015-10-30 18:24:20 +0000292 page.append('h1,h2,h3 {')
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000293 page.append(' font-family: arial, Helvetica, sans-serif;')
294 page.append(' padding-right:3pt;')
295 page.append(' padding-top:2pt;')
296 page.append(' padding-bottom:2pt;')
297 page.append(' margin-top:8pt;')
298 page.append(' margin-bottom:8pt;')
299 page.append(' border-style:none;')
300 page.append(' border-width:thin;')
301 page.append('}')
302 page.append('A:link { text-decoration: none; }')
303 page.append('A:visited { text-decoration: none}')
304 page.append('h1 { font-size: 18pt; }')
305 page.append('h2 { font-size: 14pt; }')
306 page.append('h3 { font-size: 12pt; }')
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000307 page.append('dl,ul { margin-top: 1pt; text-indent: 0 }')
308 page.append('ol { margin-top: 1pt; text-indent: 0 }')
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000309 page.append('div.date { font-size: 8pt; }')
310 page.append('div.sig { font-size: 8pt; }')
Steve McIntyrebfef5062015-10-30 18:28:32 +0000311 page.append('div.port {')
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000312 page.append(' display: block;')
313 page.append(' position: fixed;')
Steve McIntyrebfef5062015-10-30 18:28:32 +0000314 page.append(' left: 0px;')
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000315 page.append(' bottom: 0px;')
316 page.append(' z-index: 99;')
317 page.append(' background: #FFFF00;')
Steve McIntyrebfef5062015-10-30 18:28:32 +0000318 page.append(' border-style: solid;')
319 page.append(' border-width: 3pt;')
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000320 page.append(' border-color: #3B3B3B;')
Steve McIntyrebfef5062015-10-30 18:28:32 +0000321 page.append(' margin: 1;')
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000322 page.append(' padding: 5px;')
323 page.append(' font-size: 10pt;')
324 page.append(' font-family: Courier,monotype;')
Steve McIntyrebfef5062015-10-30 18:28:32 +0000325 page.append(' visibility: hidden;')
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000326 page.append('}')
Steve McIntyreb74ea6b2015-09-24 20:45:31 +0100327 self.wfile.write('\r\n'.join(page))
Steve McIntyre2454bf02015-09-23 18:33:02 +0100328
Steve McIntyree8f39df2015-10-28 18:36:39 +0000329 # Generate a PNG showing the layout of switches/port/trunks for a
330 # specific VLAN
Steve McIntyre2454bf02015-09-23 18:33:02 +0100331 def send_graphic(self):
332 vlan_id = 0
333 vlan_re = re.compile(r'^/images/vlan/(\d+).png$')
334 match = vlan_re.match(self.parsed_path.path)
335 if match:
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000336 vlan_id = int(match.group(1))
337 cache = self.server.state.cache
338
Steve McIntyredef26862015-10-29 17:35:10 +0000339 # Do we have a graphic for this VLAN ID?
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000340 if not vlan_id in cache.graphics.keys():
341 logging.debug('asked for vlan_id %s', vlan_id)
342 logging.debug(cache.graphics.keys())
Steve McIntyre2454bf02015-09-23 18:33:02 +0100343 self.send_response(404)
Steve McIntyre4f584a72015-09-28 02:28:56 +0100344 self.wfile.write('Content-type: text/plain\r\n')
Steve McIntyre2454bf02015-09-23 18:33:02 +0100345 self.end_headers()
Steve McIntyre4f584a72015-09-28 02:28:56 +0100346 self.wfile.write('404 Not Found\r\n')
Steve McIntyre0f561cd2015-10-28 18:05:20 +0000347 self.wfile.write('%s' % self.parsed_path.path)
Steve McIntyre4f584a72015-09-28 02:28:56 +0100348 logging.error('VLAN graphic not found - asked for %s', self.parsed_path.path)
Steve McIntyre2454bf02015-09-23 18:33:02 +0100349 return
350
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000351 # Yes - just send it from the cache
352 self.send_response(200)
353 self.wfile.write('Content-type: image/png\r\n')
354 self.end_headers()
355 self.wfile.write(cache.graphics[vlan_id]['image']['png'].getvalue())
356 return
357
358 # Generate a PNG showing the layout of switches/port/trunks for a
359 # specific VLAN, and return that PNG along with geometry details
360 def generate_graphic(self, vlan_id):
361 db = self.server.state.db
362 vlan = db.get_vlan_by_id(vlan_id)
363 # We've been asked for a VLAN that doesn't exist
364 if vlan is None:
365 return None
366
367 data = {}
368 data['image'] = {}
369 data['ports'] = {}
370
Steve McIntyre2454bf02015-09-23 18:33:02 +0100371 gim = Graphics()
372
Steve McIntyre2454bf02015-09-23 18:33:02 +0100373 # Pick fonts. TODO: Make these configurable?
374 gim.set_font(['/usr/share/fonts/truetype/inconsolata/Inconsolata.otf',
375 '/usr/share/fonts/truetype/freefont/FreeMono.ttf'])
376 try:
377 gim.font
378 # If we can't get the font we need, fail
379 except NameError:
380 self.send_response(500)
Steve McIntyre4f584a72015-09-28 02:28:56 +0100381 self.wfile.write('Content-type: text/plain\r\n')
Steve McIntyre2454bf02015-09-23 18:33:02 +0100382 self.end_headers()
Steve McIntyre4f584a72015-09-28 02:28:56 +0100383 self.wfile.write('500 Internal Server Error\r\n')
384 logging.error('Unable to generate graphic, no fonts found - asked for %s',
385 self.parsed_path.path)
Steve McIntyre2454bf02015-09-23 18:33:02 +0100386 return
387
388 switch = {}
389 size_x = {}
390 size_y = {}
391
392 switches = db.all_switches()
393
394 # Need to set gaps big enough for the number of trunks, at least.
395 trunks = db.all_trunks()
396 y_gap = max(20, 15 * len(trunks))
397 x_gap = max(20, 15 * len(trunks))
398
399 x = 0
400 y = y_gap
401
402 # Work out how much space we need for the switches
403 for i in range(0, len(switches)):
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000404 ports = db.get_ports_by_switch(switches[i]['switch_id'])
405 switch[i] = Switch(gim, len(ports), switches[i]['name'])
Steve McIntyre2454bf02015-09-23 18:33:02 +0100406 (size_x[i], size_y[i]) = switch[i].get_dimensions()
407 x = max(x, size_x[i])
408 y += size_y[i] + y_gap
409
410 # Add space for the legend and the label
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000411 label = "VLAN %d - %s" % (vlan['tag'], vlan['name'])
Steve McIntyre2454bf02015-09-23 18:33:02 +0100412 (legend_width, legend_height) = gim.get_legend_dimensions()
413 (label_width, label_height) = gim.get_label_size(label, gim.label_font_size)
414 x = max(x, legend_width + 2*x_gap + label_width)
415 x = x_gap + x + x_gap
416 y = y + max(legend_height + y_gap, label_height)
417
418 # Create a canvas of the right size
419 gim.create_canvas(x, y)
420
421 # Draw the switches and ports in it
422 curr_y = y_gap
423 for i in range(0, len(switches)):
424 switch[i].draw_switch(gim, x_gap, curr_y)
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000425 ports = db.get_ports_by_switch(switches[i]['switch_id'])
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000426 data['ports'][i] = {}
Steve McIntyre2454bf02015-09-23 18:33:02 +0100427 for port_id in ports:
428 port = db.get_port_by_id(port_id)
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000429 port_location = switch[i].get_port_location(port['number'])
430 data['ports'][i][port['number']] = {}
431 data['ports'][i][port['number']]['db'] = port
432 data['ports'][i][port['number']]['location'] = port_location
433 if port['is_locked']:
434 switch[i].draw_port(gim, port['number'], 'locked')
435 elif port['is_trunk']:
436 switch[i].draw_port(gim, port['number'], 'trunk')
437 elif port['current_vlan_id'] == int(vlan_id):
438 switch[i].draw_port(gim, port['number'], 'VLAN')
Steve McIntyre2454bf02015-09-23 18:33:02 +0100439 else:
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000440 switch[i].draw_port(gim, port['number'], 'normal')
Steve McIntyre2454bf02015-09-23 18:33:02 +0100441 curr_y += size_y[i] + y_gap
442
443 # Now add the trunks
444 for i in range(0, len(trunks)):
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000445 ports = db.get_ports_by_trunk(trunks[i]['trunk_id'])
Steve McIntyre2454bf02015-09-23 18:33:02 +0100446 port1 = db.get_port_by_id(ports[0])
447 port2 = db.get_port_by_id(ports[1])
448 for s in range(0, len(switches)):
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000449 if switches[s]['switch_id'] == port1['switch_id']:
Steve McIntyre2454bf02015-09-23 18:33:02 +0100450 switch1 = s
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000451 if switches[s]['switch_id'] == port2['switch_id']:
Steve McIntyre2454bf02015-09-23 18:33:02 +0100452 switch2 = s
453 gim.draw_trunk(i,
Steve McIntyre3e4ec442018-02-05 18:25:47 +0000454 switch[switch1].get_port_location(port1['number']),
455 switch[switch2].get_port_location(port2['number']),
Steve McIntyre2454bf02015-09-23 18:33:02 +0100456 gim.port_pallette['trunk']['trace'])
457
458 # And the legend and label
459 gim.draw_legend(x_gap, curr_y)
460 gim.draw_label(x - label_width - 2*x_gap, curr_y, label, int(x_gap / 2))
461
Steve McIntyre57a9d0a2015-10-28 18:22:31 +0000462 # All done - push the image file into the cache for this vlan
463 data['image']['png'] = cStringIO.StringIO()
464 gim.im.writePng(data['image']['png'])
465 data['image']['width'] = x
466 data['image']['height'] = y
467 return data
Steve McIntyre2454bf02015-09-23 18:33:02 +0100468
469 # Implement an HTTP GET handler for the HTTPServer instance
470 def do_GET(self):
471 # Compare the URL path to any of the names we recognise and
472 # call the right generator function if we get a match
473 self.parsed_path = urlparse.urlparse(self.path)
474 for url in self.functionMap:
475 match = re.match(url['re'], self.parsed_path.path)
476 if match:
477 return url['fn'](self)
478
479 # Fall-through for any files we don't recognise
480 self.send_response(404)
Steve McIntyre4f584a72015-09-28 02:28:56 +0100481 self.wfile.write('Content-type: text/plain\r\n')
Steve McIntyre2454bf02015-09-23 18:33:02 +0100482 self.end_headers()
483 self.wfile.write('404 Not Found')
Steve McIntyre0f561cd2015-10-28 18:05:20 +0000484 self.wfile.write('%s' % self.parsed_path.path)
Steve McIntyre4f584a72015-09-28 02:28:56 +0100485 logging.error('File not supported - asked for %s', self.parsed_path.path)
Steve McIntyre2454bf02015-09-23 18:33:02 +0100486 return
487
488 # Override the BaseHTTPRequestHandler log_message() method so we
489 # can log requests properly
Steve McIntyre9ff96bf2015-09-23 18:54:53 +0100490 def log_message(self, fmt, *args):
Steve McIntyre2454bf02015-09-23 18:33:02 +0100491 """Log an arbitrary message. """
Steve McIntyre9ff96bf2015-09-23 18:54:53 +0100492 logging.info('%s %s', self.client_address[0], fmt%args)
Steve McIntyre2454bf02015-09-23 18:33:02 +0100493
494 functionMap = (
Steve McIntyre9ff96bf2015-09-23 18:54:53 +0100495 {'re': r'^/$', 'fn': send_index},
496 {'re': r'^/style.css$', 'fn': send_style},
497 {'re': r'^/images/vlan/(\d+).png$', 'fn': send_graphic}
Steve McIntyre2454bf02015-09-23 18:33:02 +0100498 )