diff options
author | Daniel Silverstone <daniel.silverstone@codethink.co.uk> | 2012-10-11 17:40:59 +0100 |
---|---|---|
committer | Daniel Silverstone <daniel.silverstone@codethink.co.uk> | 2012-10-11 17:40:59 +0100 |
commit | 4334f9d810ea6e448ad4c452c8fd7fd99487c693 (patch) | |
tree | 1160cf2a3b0cf96e4f706cc62e24585430f1d7a5 /lorrycontroller | |
parent | 4d2520e0ef681dc5cca6f1e03126715b57a2d1ad (diff) | |
download | lorry-controller-4334f9d810ea6e448ad4c452c8fd7fd99487c693.tar.gz |
Support generating HTML status messages
Diffstat (limited to 'lorrycontroller')
-rw-r--r-- | lorrycontroller/confparser.py | 6 | ||||
-rw-r--r-- | lorrycontroller/htmlstatus.py | 258 | ||||
-rw-r--r-- | lorrycontroller/workingstate.py | 2 |
3 files changed, 265 insertions, 1 deletions
diff --git a/lorrycontroller/confparser.py b/lorrycontroller/confparser.py index 00a9fe1..71becd7 100644 --- a/lorrycontroller/confparser.py +++ b/lorrycontroller/confparser.py @@ -30,11 +30,14 @@ class LorryControllerConfig(object): def __init__(self, app, confpath): self.app = app + self.confpath = confpath self.lorries = {} self.configs = {} self.duetimes = {} self.troves = [] - confpath = os.path.join(app.settings['work-area'], confpath) + + def parse_config(self): + confpath = os.path.join(self.app.settings['work-area'], self.confpath) logging.info("Parsing configuration: %s" % confpath) try: with open(confpath, "r") as fh: @@ -260,6 +263,7 @@ class LorryControllerConfig(object): def update_troves(self, statemgr): # Now that we have a state manager we can look at the trove data. for trove in self.troves: + self.app.html.set_processing(trove['uuid']) trove_state = statemgr.get_trove(trove['uuid']) self.update_trove(trove, trove_state) diff --git a/lorrycontroller/htmlstatus.py b/lorrycontroller/htmlstatus.py new file mode 100644 index 0000000..aae75c7 --- /dev/null +++ b/lorrycontroller/htmlstatus.py @@ -0,0 +1,258 @@ +# Copyright (C) 2012 Codethink Limited +# + +import os +import time +from cgi import escape + +state_names = [ + "Initialisation", + "Load Troves", + "Remove old repos", + "Create new repos", + "Process Lorries", + "Finished" + ] + + +class HTMLStatusManager(object): + '''Manage the HTML status page for lorry-controller.''' + + + def __init__(self, app): + self.app = app + self.state = 0 + self.series = None + self.filename = self.app.settings['html-file'] + self.mgr = None + self.processing = None + self.processing_time = None + self.failing = None + self.all_lorries_ever = set() + + def set_failing(self, failmsg): + self.failing = failmsg + self.write_out_status() + + def set_mgr(self, mgr): + self.mgr = mgr + + def set_processing(self, proc): + self.processing = proc + self.processing_time = time.time() + self.write_out_status() + + def bump_state(self): + self.state = self.state + 1 + self.processing = None + self.write_out_status() + + def write_out_status(self): + if self.filename is None: return + try: + with open(self.filename + ".new", "w") as ofh: + ofh.write("<!DOCTYPE html>\n") + ofh.write(self.gen_html()) + ofh.write("\n") + target = self.filename + if self.series is not None: + target += ".%d" % self.series + self.series += 1 + os.rename(self.filename + ".new", target) + except: + os.unlink(self.filename + ".new") + raise + + def gen_html(self): + head = self.gen_head() + body = self.gen_body() + return self.tag("html", content=head+"\n"+body, gap=True) + + def gen_head(self): + title = self.tag("title", content="Lorry Controller") + css = self.tag("link", href="trove.css", rel="stylesheet", + type="text/css") + script = self.tag("script", tyle="text/JavaScript", content=''' +<!-- +function reloadAfter(timeout) { + setTimeout("location.reload(true);", timeout); +} +// --> +''') + return self.tag("head", content=title+css+script) + + def gen_body(self): + # 1. Rough header, branded as trove + curtime = time.asctime(time.gmtime()) + link = "/" + if self.series is not None: + link = self.filename + ".%d" % (self.series + 1) + header = ''' +<table id='header'><tr><td class='logo' rowspan='2'> +<a href='%s'><img src='trove.png' alt='trove logo'/></a></td> +<td class='main'>Status of Lorry Controller</td></tr> +<tr><td class='sub'>Updated at %s</td></tr></table> +''' % (link, curtime) + # 2. List of steps and where we are currently + steps = self.gen_steps() + + # 4. Main content + content = self.gen_content() + + # 5. footer + footer = self.gen_footer() + + # magic args + kwargs = { + "onload": "JavaScript:reloadAfter(5000);", + } + if self.state >= (len(state_names)-1): + kwargs["onload"] = "JavaScript:reloadAfter(10000);", + return self.tag("body", content=self.tag( + "div", content=(header+steps+content+footer), + Class="lorrycontroller"), **kwargs) + + def gen_content(self): + if self.failing is not None: + return self.tag("div", Class="failure", content=self.failing) + # 1. Troves known + troves = self.gen_troves() + # 2. Lorries known + lorries = self.gen_lorries() + + return self.tag("div", Class="content", content= + self.tag("div", id="troves", content=troves) + + self.tag("div", id="lorries", content=lorries)) + + def gen_troves(self): + troves = [] + now = time.time() + for trove in self.app.conf.troves: + troveinfo = {} + if self.mgr is not None: + troveinfo = self.mgr.trove_state.get(trove['uuid'], {}) + uuid = self.tag("td", content=escape(trove['uuid'])) + state = "Up to date" + if self.processing == trove['uuid']: + state = "Processing since " + \ + time.asctime(time.gmtime(self.processing_time)) + elif troveinfo.get('next-vls', now - 1) < now: + state = "Due to be checked" + state = self.tag("td", content=escape(state)) + nextdue = self.tag("td", content=escape(time.asctime( + time.gmtime(troveinfo.get('next-vls', now - 1))))) + lorrycount = len([l for l in self.app.conf.lorries.itervalues() + if l['controller-uuid'] == trove['uuid']]) + lorrycount = self.tag("td", content=str(lorrycount)) + + troves.append(self.tag("tr", content= + uuid+state+nextdue+lorrycount)) + if len(troves) == 0: + content = "No troves detected" + else: + header = self.tag("tr", content= + self.tag("th", content="Trove UUID") + + self.tag("th", content="Status") + + self.tag("th", content="Next due") + + self.tag("th", content="Lorries created")) + content = self.tag("table", content= + header + "".join(troves)) + + return content + + def gen_lorries(self): + lorries = [] + now = time.time() + all_lorry_names = set(self.app.conf.lorries.keys()) + if self.mgr is not None: + all_lorry_names.update(set(self.mgr.lorry_state.keys())) + self.all_lorries_ever.update(all_lorry_names) + all_lorry_names = list(self.all_lorries_ever) + all_lorry_names.sort() + for lorry_name in all_lorry_names: + lorry = self.app.conf.lorries.get(lorry_name, None) + dead_lorry = False + dead_and_gone = False + if lorry is None: + lorrystate = self.mgr.lorry_state.get(lorry_name, None) + if lorrystate is None: + dead_and_gone = True + lorry = {} + else: + lorry = lorrystate['lorry'] + dead_lorry = True + lorryinfo = {} + if self.mgr is not None: + lorryinfo = self.mgr.lorry_state.get(lorry_name, {}) + uuid = self.tag("td", content= + escape(lorry.get('controller-uuid', 'Dead'))) + state = "Waiting until " + \ + time.asctime(time.gmtime(lorryinfo.get('next-due', now - 1))) + if self.processing == lorry_name: + state = "Processing since " + \ + time.asctime(time.gmtime(self.processing_time)) + elif lorryinfo.get('next-due', now - 1) < now: + state = "Due to be checked" + if self.mgr is not None: + if self.mgr.lorry_state.get(lorry_name, None) is None: + state = "Needs creating" + if dead_lorry: + state = "Dead - To be removed" + if dead_and_gone: + state = "Dead" + if self.processing == lorry_name: + state = "Removing since " + \ + time.asctime(time.gmtime(self.processing_time)) + state = self.tag("td", content=escape(state)) + lastresult = self.tag("td", content=self.tag( + "pre", content=escape(lorryinfo.get('result', '-')))) + nextdue = self.tag("td", content=escape(time.asctime( + time.gmtime(lorryinfo.get('next-due', now - 1))))) + lorryname = self.tag("td", content=escape(lorry_name)) + lorries.append(self.tag("tr", content= + lorryname+uuid+state+lastresult+nextdue)) + if len(lorries) == 0: + content = "No lorries detected yet" + else: + header = self.tag("tr", content= + self.tag("th", content="Lorry Name") + + self.tag("th", content="Comes From") + + self.tag("th", content="Status") + + self.tag("th", content="Last result") + + self.tag("th", content="Next due")) + content = self.tag("table", content= + header + "".join(lorries)) + + return content + + + def gen_footer(self): + curtime = time.asctime(time.gmtime()) + return self.tag("div", Class="footer", content= + "Generated by Lorry Controller at " + curtime) + + def gen_steps(self): + steps = [] + for idx in xrange(len(state_names)): + if idx < self.state: + Class = "donestep" + elif idx == self.state: + Class = "activestep" + else: + Class = "pendingstep" + steps.append(self.tag("span", Class=Class, + content=state_names[idx])) + return self.tag("table", Class="steps", content= + self.tag("tr", content= + self.tag("td", content= + "".join(steps)))) + + def tag(self, tagname, content=None, gap=False, **kwargs): + tagval = " ".join([tagname] + + ["%s=%r" % (k.lower(), v) for k, v in kwargs.iteritems()]) + gap = "\n" if gap else "" + if content is None: + return "<%s />" % tagval + else: + return "<%s>%s%s%s</%s>" % (tagval, gap, content, gap, tagname) + diff --git a/lorrycontroller/workingstate.py b/lorrycontroller/workingstate.py index 082647e..8e4b20e 100644 --- a/lorrycontroller/workingstate.py +++ b/lorrycontroller/workingstate.py @@ -38,8 +38,10 @@ class LorryFileRunner(object): exit, out, err = self.mgr.app.maybe_runcmd(cmdargs) if exit == 0: logging.debug("Lorry of %s succeeded: %s" % (self.lorryname, out)) + self.mgr.lorry_state[self.lorryname]['result'] = "OK" else: logging.warn("Lorry of %s failed: %s" % (self.lorryname, err)) + self.mgr.lorry_state[self.lorryname]['result'] = err class WorkingStateManager(object): '''Manage the working state of lorry-controller''' |