Commit e1f1403c authored by Tomas Hlavacek's avatar Tomas Hlavacek

ULG model implemented. CGI transition finalized. Select items implemeted and...

ULG model implemented. CGI transition finalized. Select items implemeted and Decorator support added.
parent 5a8a1abf
......@@ -25,3 +25,7 @@ pip-log.txt
#Mr Developer
.mr.developer.cfg
# Potentialy sensitive files
src/config.py
ulg
===
Universal Looking Glass for BIRD and others
\ No newline at end of file
......@@ -24,4 +24,3 @@ routers = [
CiscoRouter(host='testrouter2.core.company.com', user='xyz', password='xyz'),
BirdRouterLocal('/var/run/bird.ctl')
]
......@@ -24,7 +24,11 @@ usage_limit = 1 # maximum concurrently processe
# Settings defaults
always_start_thread = True
debug = True
debug = False
persistent_storage_file = '/tmp/ulg.data'
session_dir = '/tmp'
usage_counter_file = '/tmp/ulg.lock'
log_file = '/tmp/ulg.log'
default_bird_sock = '/var/run/bird.ctl'
# Template dir relative to the index.py script
......@@ -36,5 +40,12 @@ display_template_file = 'display.html'
bin_ssh = '/usr/bin/ssh'
# Output (localized) strings
STRING_SESSION_OVERLIMIT = "<em>Limit of maximum concurrently running sessions and/or queries has been reached. The command can not be executed now. Please retry later.</em>"
STRING_ANY='any'
STRING_PARAMETER='Parameter'
STRING_COMMAND='Command'
STRING_ERROR_COMMANDRUN='Error encountered while preparing or running command'
STRING_BAD_PARAMS='Verification of command or parameters failed.'
STRING_SESSION_OVERLIMIT = "<em>Limit of maximum concurrently running sessions and/or queries has been reached. The command can not be executed now. Please try again later.</em>"
STRING_ARBITRARY_ERROR = "Error encountered. Operation aborted. See log for further details."
STRING_IPADDRESS = "IP address"
STRING_IPSUBNET = "IP subnet"
#!/usr/bin/env python
#
# ULG - Universal Looking Glass
# by Tomas Hlavacek (tomas.hlavacek@nic.cz)
# last udate: June 21 2012
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Imports
import os, sys
from genshi.template import TemplateLoader
from genshi.core import Markup
import cgi
import cgitb; cgitb.enable()
import random
import time
import md5
import pickle
import threading
# Apache mod_wsgi hack of import directory
#if __name__.startswith('_mod_wsgi_'):
# sys.path.reverse()
# sys.path.append(os.path.dirname(__file__))
# sys.path.reverse()
import config
import defaults
import ulg
### CGI output handler
class Session(object):
def __init__(self,sessionid=None,routerid=None,commandid=None,parameters=None,result=None,finished=False):
if(sessionid == None):
self.sessionid = self.__genSessionId__()
else:
self.sessionid=sessionid
self.routerid=routerid
self.commandid=commandid
self.parameters=parameters
self.result=result
self.finished=finished
self.save()
def __genSessionId__(self):
return md5.new(str(time.time())+str(random.randint(1,1000000))).hexdigest()
@staticmethod
def getSessionFileName(sessionid):
# TODO
return '/tmp/session.test'
def save(self):
fn = Session.getSessionFileName(self.getSessionId())
f = open(fn,'wb')
pickle.dump(self, f)
f.close()
def getSessionId(self):
return self.sessionid
def setFinished(self):
self.finished=True
self.save()
def isFinished(self):
return self.finished
def setRouterId(self,router):
self.routerid=routerid
self.save()
def getRouterId(self):
return self.routerid
def setCommandId(self,command):
self.commandid = commandid
self.save()
def getCommandId(self):
return self.commandid
def cleanParameters(self):
self.parameters = None
self.save()
def addParameter(self,parameter):
if(not self.parameters):
self.parameters = []
self.parameters.append(parameter)
self.save()
def getParameters(self):
return self.parameters
def setResult(self,result):
self.result = result
self.save()
def getResult(self):
return self.result
def appendResult(self,result_fragment):
self.result = self.result + result_fragment
self.save()
def getRouter(self):
if(self.getRouterId()!=None):
return config.routers[self.getRouterId()]
else:
return None
def getCommand(self):
if(self.getRouterId()!=None)and(self.getCommandId()!=None):
return self.getRouter().listCommands()[self.getCommandId()]
else:
return None
class ULGCron:
def __init__(self):
pass
def run(self):
pass
class ULGCgi:
def __init__(self):
self.loader=TemplateLoader(
os.path.join(os.path.dirname(__file__), defaults.template_dir),
auto_reload=True
)
def rescanRouters(self):
for r in config.routers:
r.rescanHook()
def increaseUsage(self):
# TODO
return True
def decreaseUsage(self):
# TODO
pass
def stopSessionOverlimit(self,session):
session.setResult(defaults.STRING_SESSION_OVERLIMIT)
session.setFinished()
def loadSession(self,sessionid):
if(sessionid == None):
return None
fn = Session.getSessionFileName(sessionid)
if(os.path.isfile(fn)):
f = open(fn, 'rb')
s = pickle.load(f)
f.close()
return s
else:
return None
def HTTPRedirect(self,url):
# TODO
return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Redirect!</title>
<meta http-equiv="REFRESH" content="0;url=%s">
</head>
<body>
</body>
</html>""" % url
def getURL(self,action):
# TODO
return os.path.basename(__file__) + ("?action=%s" % action)
def runCommand(self,session):
# try to increase usage counter
if(self.increaseUsage()):
# start new thread if needed
if(defaults.always_start_thread or session.getRouter().getForkNeeded()):
# define trivial thread function
def commandThreadBody(session,decreaseUsageMethod):
time.sleep(10)
session.setResult(session.getRouter().runCommand(session.getCommand(),session.getParameters()))
session.setFinished()
decreaseUsageMethod()
# fork a daemon process (fork two time to decouple with parent)
sys.stdout.flush()
child_pid = os.fork()
if(child_pid == 0):
# detach process
devnull = open(os.devnull,'w')
os.dup2(devnull.fileno(),sys.stdout.fileno())
os.dup2(devnull.fileno(),sys.stderr.fileno())
# run the command
commandThreadBody(session,self.decreaseUsage)
# exit the child
sys.exit(0)
else:
# directly run the selected action
commandThreadBody(session,self.decreaseUsage)
else:
# stop and report no-op
self.stopSessionOverlimit(session)
def renderULGIndex(self,routerid=0,commandid=0,sessionid=None):
tmplate = self.loader.load(defaults.index_template_file)
# rescan routers - is it a good place for rescan?
self.rescanRouters()
return tmplate.generate(defaults=defaults,
routers=config.routers,
default_routerid=routerid,
default_commandid=commandid,
default_sessionid=sessionid
).render('html', doctype='html')
def renderULGAction(self,routerid=0,commandid=0,sessionid=None,**moreparams):
routerid=int(routerid)
commandid=int(commandid)
# create and register session
session = Session(sessionid=sessionid,routerid=routerid,commandid=commandid)
# extract parameters
session.cleanParameters()
for pidx,ps in enumerate(session.getCommand().getParamSpecs()):
if('param'+str(pidx) in moreparams.keys()):
session.addParameter(moreparams['param'+str(pidx)])
else:
session.addParameter(ps.getDefault())
# run the command (possibly in a separate thread)
self.runCommand(session)
# redirect to the session display
return self.HTTPRedirect(self.getURL('display') + ('&sessionid=%s' % session.getSessionId()))
def renderULGResult(self,sessionid=None):
if(sessionid==None):
return self.HTTPRedirect(self.getURL('error'))
session = self.loadSession(sessionid)
if(session == None):
return self.HTTPRedirect(self.getURL('error'))
if(session.isFinished()):
refresh=None
else:
refresh = defaults.refresh_interval
# rescan routers - is it a good place for this?
self.rescanRouters()
tmplate = self.loader.load(defaults.index_template_file)
return tmplate.generate(defaults=defaults,
routers=config.routers,
default_routerid=session.getRouterId(),
default_commandid=session.getCommandId(),
default_sessionid=sessionid,
result=Markup(session.getResult()),
refresh=refresh
).render('html', doctype='html')
def renderULGError(self,sessionid=None,**params):
tmplate = self.loader.load(defaults.index_template_file)
session = self.loadSession(sessionid)
result_text = defaults.STRING_ARBITRARY_ERROR
session = self.loadSession(sessionid)
if(session!=None):
result_text=self.sessions[sessionid].getResult()
return tmplate.generate(defaults=defaults,
routers=config.routers,
default_routerid=0,
default_commandid=0,
default_sessionid=None,
result=Markup(result_text),
refresh=0
).render('html', doctype='html')
def renderULGDebug(self,**params):
tmplate = self.loader.load(defaults.index_template_file)
result_text = "<h1>DEBUG</h1>\n<pre>\nPARAMS:\n"
for k in params.keys():
result_text = result_text + str(k) + "=" + str(params[k]) + "\n"
result_test = result_text + "\n<pre>"
return tmplate.generate(defaults=defaults,
routers=config.routers,
default_routerid=0,
default_commandid=0,
default_sessionid=None,
result=Markup(result_text),
refresh=0
).render('html', doctype='html')
def index(self, **params):
if('sessionid' in params.keys()):
print self.renderULGIndex(sessionid=params['sessionid'])
else:
print self.renderULGIndex()
def runcommand(self,routerid=0,commandid=0,sessionid=None,**params):
print self.renderULGAction(routerid,commandid,sessionid,**params)
def display(self,sessionid=None,**params):
print self.renderULGResult(sessionid)
def error(self,sessionid=None,**params):
print self.renderULGError(sessionid,**params)
def debug(self,**params):
print self.renderULGDebug(**params)
# main
if __name__=="__main__":
form = cgi.FieldStorage()
handler = ULGCgi()
print "Content-Type: text/html\n"
action = form.getvalue('action',None)
params = dict([(k,form.getvalue(k)) for k in form.keys() if k != 'action'])
if(action):
if(action == 'index'):
handler.index(**params)
if(action == 'runcommand'):
handler.runcommand(**params)
if(action == 'display'):
handler.display(**params)
if(action == 'error'):
handler.display(**params)
if(action == 'debug'):
handler.debug(**params)
else:
handler.index(**params)
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/">
<head>
<title>Universal Looking Glass</title>
<py:if test="defined('refresh')">
<meta http-equiv="refresh" content="$refresh" py:if="defined('refresh') and (refresh>0)" />
</py:if>
<script type="text/javascript">
function updateFormParams()
{
var rs=document.getElementById("routerselect");
var cs=document.getElementById("commandselect");
<py:for each="ridx,router in enumerate(routers)">
<py:for each="cidx,cmd in enumerate(routers[ridx].listCommands())">
<![CDATA[ if((rs.options[rs.selectedIndex].value==$ridx) && (cs.options[cs.selectedIndex].value==$cidx)) { ]]>
<py:choose>
<py:when test="routers[ridx].listCommands()[cidx].getParamSpec() and (routers[ridx].listCommands()[cidx].getParamSpec().getType() == 'text')">
document.getElementById("paramrow").innerHTML=' \
<th align="left"> \
<label for="param">3. ${routers[ridx].listCommands()[cidx].getParamSpec().getName()}:</label> \
</th> \
<td align="left"> \
<input type="text" id="param1" name="param1" value="${routers[ridx].listCommands()[cidx].getParamSpec().getDefault()}" /></td>';
</py:when>
<py:otherwise>
document.getElementById("paramrow").innerHTML='';
</py:otherwise>
</py:choose>
}
</py:for>
</py:for>
}
function updateFormCommands()
{
var rs=document.getElementById("routerselect");
<py:for each="ridx,router in enumerate(routers)">
if(rs.options[rs.selectedIndex].value==$ridx) {
document.getElementById("commandselect").innerHTML='<py:for each="cidx,cmd in enumerate(routers[ridx].listCommands())"><py:choose><py:when test="cidx == default_command"><option value="$cidx" selected="selected">${cmd.getName()}</option></py:when><py:otherwise><option value="$cidx">${cmd.getName()}</option></py:otherwise></py:choose></py:for>';
}
</py:for>
updateFormParams()
}
</script>
</head>
<body class="index">
<script>
window.onload=updateFormCommands;
</script>
<div id="header">
<py:choose>
<py:when test="defaults.header">
<h1>ULG - $defaults.header</h1>
</py:when>
<py:otherwise>
<h1>ULG</h1>
</py:otherwise>
</py:choose>
</div>
<p>Select router to perform operation on, then select a command to perform and add (optional) argument if needed.</p>
<form action="runcommand" method="post">
<input type="hidden" name="session" value="$default_session" py:if="defined('default_session') and (default_session)" />
<table summary="Form settings">
<tbody>
<tr>
<th align="left"><label for="router">1. Router selection:</label></th>
<td>
<select id="routerselect" name="router" onchange="updateFormCommands()">
<py:for each="ridx,rtr in enumerate(routers)">
<py:choose>
<py:when test="ridx == default_router">
<option value="$ridx" selected="selected">
${rtr.getName()}
</option>
</py:when>
<py:otherwise>
<option value="$ridx">
${rtr.getName()}
</option>
</py:otherwise>
</py:choose>
</py:for>
</select>
</td>
</tr>
<tr height="10">
</tr>
<tr>
<th align="left" valign="top">2. Command selection:</th>
<td align="left">
<select id="commandselect" name="command" onchange="updateFormParams()">
<py:for each="cidx,cmd in enumerate(routers[default_router].listCommands())">
<py:choose>
<py:when test="cidx == default_command">
<option value="$cidx" selected="selected">
${cmd.getName()}
</option>
</py:when>
<py:otherwise>
<option value="$cidx">
${cmd.getName()}
</option>
</py:otherwise>
</py:choose>
</py:for>
</select>
</td>
</tr>
<tr height="10"></tr>
<tr id="paramrow">
</tr>
<tr>
<td></td>
<td>
<input type="submit" value="Submit" />
</td>
</tr>
</tbody>
</table>
</form>
<div id="result">
<hr/>
<py:choose>
<py:when test="defined('result')">
${result}
</py:when>
<py:otherwise>
<em>No result.</em>
</py:otherwise>
</py:choose>
<p py:if="defined('refresh') and (refresh>0)">
<em>Refreshing in $refresh seconds.</em>
</p>
</div>
<div id="footer">
<hr/>
<p class="legalese" align="right"><small>© 2012 Tomas Hlavacek</small></p>
</div>
</body>
</html>
<
......@@ -19,21 +19,51 @@
document.getElementById("paramsect").innerHTML=' \
<py:for each="pidx,param in enumerate(routers[ridx].listCommands()[cidx].getParamSpecs())">
<py:choose>
<py:when test="param.getType() == 'select'">
<tr> SELECT TODO!!! </tr> \
</py:when>
<py:otherwise>
<tr> \
<th align="left"> \
<label for="param">${pidx+3}. ${param.getName()}:</label> \
</th> \
<tr height="10"></tr> \
<tr> \
<th align="left"> \
${pidx+3}. ${param.getName()}: \
</th> \
<py:choose>
<py:when test="param.getType() == 'select'">
<td align="left"> \
<input type="text" name="param${pidx}" value="${param.getDefault()}" /> \
<select name="param${pidx}"> \
<py:for each="paridx,parname in enumerate(param.getOptionNames())">
<py:choose>
<py:when test="default_routerid == ridx and default_commandid == cidx and defined('default_params') and pidx &lt; len(default_params) and paridx == int(default_params[pidx])">
<option value="$paridx" selected="selected"> \
${parname} \
</option> \
</py:when>
<py:when test="paridx == param.getDefault()">
<option value="$paridx" selected="selected"> \
${parname} \
</option> \
</py:when>
<py:otherwise>
<option value="$paridx"> \
${parname} \
</option> \
</py:otherwise>
</py:choose>
</py:for>
</select> \
</td> \
</tr> \
</py:otherwise>
</py:choose>
</py:when>
<py:otherwise>
<td align="left"> \
<py:choose>
<py:when test="default_routerid == ridx and default_commandid == cidx and defined('default_params') and pidx &lt; len(default_params)">
<input type="text" name="param${pidx}" value="${default_params[pidx]}" /> \
</py:when>
<py:otherwise>
<input type="text" name="param${pidx}" value="${param.getDefault()}" /> \
</py:otherwise>
</py:choose>
</td> \
</py:otherwise>
</py:choose>
</tr> \
</py:for>';
}
</py:for>
......@@ -45,7 +75,17 @@
var rs=document.getElementById("routerselect");
<py:for each="ridx,router in enumerate(routers)">
if(rs.options[rs.selectedIndex].value==$ridx) {
document.getElementById("commandselect").innerHTML='<py:for each="cidx,cmd in enumerate(routers[ridx].listCommands())"><py:choose><py:when test="cidx == default_commandid"><option value="$cidx" selected="selected">${cmd.getName()}</option></py:when><py:otherwise><option value="$cidx">${cmd.getName()}</option></py:otherwise></py:choose></py:for>';
document.getElementById("commandselect").innerHTML=' \
<py:for each="cidx,cmd in enumerate(routers[ridx].listCommands())">
<py:choose>
<py:when test="default_routerid == ridx and default_commandid == cidx">
<option value="$cidx" selected="selected">${cmd.getName()}</option> \
</py:when>
<py:otherwise>
<option value="$cidx">${cmd.getName()}</option> \
</py:otherwise>