From 8e65cdcaaf08982f5f744297c009359ec74d31b5 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Wed, 3 Dec 2008 01:09:32 +0000 Subject: [PATCH] add sorting tables to the pcu view. add support for ipmi, blackbox, and a ManualPCU class that will send email to an local admin. add a --force option to findbadpcu.py, still need a solution for the global increment. added siteid2loginbase mapping to plccache for displaying loginbase in web. --- findbadpcu.py | 5 +- monitor/wrapper/plccache.py | 3 + pcucontrol/reboot.py | 80 ++++++- web/MonitorWeb/monitorweb/controllers.py | 12 +- .../static/javascript/sortable_tables.js | 214 ++++++++++++++++++ .../monitorweb/templates/pculist.kid | 15 +- .../monitorweb/templates/sitemenu.kid | 5 +- 7 files changed, 320 insertions(+), 14 deletions(-) create mode 100644 web/MonitorWeb/monitorweb/static/javascript/sortable_tables.js diff --git a/findbadpcu.py b/findbadpcu.py index 158d772..8ebd891 100755 --- a/findbadpcu.py +++ b/findbadpcu.py @@ -317,7 +317,7 @@ def checkAndRecordState(l_pcus, cohash): fbnodesync.flush() node_round = fbnodesync.round - if node_round < global_round: + if node_round < global_round or config.force: # recreate node stats when refreshed #print "%s" % nodename req = threadpool.WorkRequest(collectPingAndSSH, [pcuname, cohash], {}, @@ -416,6 +416,7 @@ if __name__ == '__main__': dbname="findbadpcus", cachenodes=False, refresh=False, + force=False, ) parser.add_option("-f", "--nodelist", dest="nodelist", metavar="FILE", help="Provide the input file for the node list") @@ -434,6 +435,8 @@ if __name__ == '__main__': help="Refresh the cached values") parser.add_option("-i", "--increment", action="store_true", dest="increment", help="Increment round number to force refresh or retry") + parser.add_option("", "--force", action="store_true", dest="force", + help="Force probe without incrementing global 'round'.") parser = parsermodule.getParser(['defaults'], parser) config = parsermodule.parse_args(parser) try: diff --git a/monitor/wrapper/plccache.py b/monitor/wrapper/plccache.py index d4cfbbc..45f879c 100755 --- a/monitor/wrapper/plccache.py +++ b/monitor/wrapper/plccache.py @@ -74,6 +74,7 @@ l_nodenetworks = None plcdb_hn2lb = None plcdb_lb2hn = None plcdb_netid2ip = None +plcdb_id2lb = None def init(): global l_sites @@ -83,6 +84,7 @@ def init(): global plcdb_hn2lb global plcdb_lb2hn global plcdb_netid2ip + global plcdb_id2lb api = plc.getCachedAuthAPI() l_sites = api.GetSites({'peer_id':None}, @@ -101,6 +103,7 @@ def init(): plcdb_hn2lb = hn2lb plcdb_lb2hn = lb2hn plcdb_netid2ip = netid2ip + plcdb_id2lb = id2lb return l_nodes diff --git a/pcucontrol/reboot.py b/pcucontrol/reboot.py index 5e1d6d7..2e6d2d5 100755 --- a/pcucontrol/reboot.py +++ b/pcucontrol/reboot.py @@ -117,7 +117,7 @@ class Transport: HTTP = 3 IPAL = 4 - TELNET_TIMEOUT = 60 + TELNET_TIMEOUT = 120 def __init__(self, type, verbose): self.type = type @@ -204,6 +204,7 @@ class Transport: if self.transport != None: output = self.transport.read_until(expected, self.TELNET_TIMEOUT) if output.find(expected) == -1: + print "OUTPUT: --%s--" % output raise ErrorClass, "'%s' not found" % expected else: self.transport.write(buffer + "\r\n") @@ -278,7 +279,30 @@ class PCUControl(Transport,PCUModel,PCURecord): import traceback traceback.print_exc() return "EOF connection reset" + str(err) - + +class IPMI(PCUControl): + + supported_ports = [80,443,623] + + # TODO: get exit codes to determine success or failure... + def run(self, node_port, dryrun): + + if not dryrun: + cmd = "ipmitool -I lanplus -H %s -U %s -P '%s' power cycle" + p = os.popen(cmd % ( self.host, self.username, self.password) ) + result = p.read() + print "RESULT: ", result + else: + cmd = "ipmitool -I lanplus -H %s -U %s -P '%s' user list" + p = os.popen(cmd % ( self.host, self.username, self.password) ) + result = p.read() + print "RESULT: ", result + + if "Error" in result: + return result + else: + return 0 + class IPAL(PCUControl): """ This now uses a proprietary format for communicating with the PCU. I @@ -1022,6 +1046,15 @@ class ePowerSwitch(PCUControl): self.close() return 0 +class ManualPCU(PCUControl): + supported_ports = [22,23,80,443,9100,16992] + + def run(self, node_port, dryrun): + if not dryrun: + # TODO: send email message to monitor admin requesting manual + # intervention. This should always be an option for ridiculous, + # custom jobs. + return 0 ### rebooting european BlackBox PSE boxes # Thierry Parmentelat - May 11 2005 @@ -1032,6 +1065,29 @@ class ePowerSwitch(PCUControl): # curl --http1.0 --basic --user : --data P=r \ # http://:/cmd.html && echo OK +# log in: + +## BB PSMaverick +class BlackBoxPSMaverick(PCUControl): + supported_ports = [80] + + def run(self, node_port, dryrun): + if not dryrun: + # send reboot signal. + cmd = "curl -s --data 'P%s=r' --anyauth --user '%s:%s' http://%s/config/home_f.html" % ( node_port, self.username, self.password, self.host) + else: + # else, just try to log in + cmd = "curl -s --anyauth --user '%s:%s' http://%s/config/home_f.html" % ( self.username, self.password, self.host) + + p = os.popen(cmd) + result = p.read() + print "RESULT: ", result + + if len(result.split()) > 3: + return 0 + else: + return result + def bbpse_reboot (pcu_ip,username,password,port_in_pcu,http_port, dryrun): global verbose @@ -1275,6 +1331,10 @@ def model_to_object(modelname): return WTIIPS4 elif "ePowerSwitch" in modelname: return ePowerSwitch + elif "ipmi" in modelname: + return IPMI + elif "bbsemaverick" in modelname: + return BlackBoxPSMaverick else: return Unknown @@ -1303,11 +1363,11 @@ def reboot_test(nodename, values, continue_probe, verbose, dryrun): apc = APCBrazil(values, verbose, ['22', '23']) rb_ret = apc.reboot(values[nodename], dryrun) - elif values['pcu_id'] in [1221,1225,1220]: + elif values['pcu_id'] in [1221,1225,1220,1192]: apc = APCBerlin(values, verbose, ['22', '23']) rb_ret = apc.reboot(values[nodename], dryrun) - elif values['pcu_id'] in [1173,1240,47]: + elif values['pcu_id'] in [1173,1240,47,1363,1405,1401,1372,1371]: apc = APCFolsom(values, verbose, ['22', '23']) rb_ret = apc.reboot(values[nodename], dryrun) @@ -1375,7 +1435,17 @@ def reboot_test(nodename, values, continue_probe, verbose, dryrun): amt = IntelAMT(values, verbose, ['16992']) rb_ret = amt.reboot(values[nodename], dryrun) - # BlackBox PSExxx-xx (e.g. PSE505-FR) + elif continue_probe and values['model'].find("bbsemaverick") >=0: + print "TRYING BlackBoxPSMaverick" + bbe = BlackBoxPSMaverick(values, verbose, ['80']) + rb_ret = bbe.reboot(values[nodename], dryrun) + + elif continue_probe and values['model'].find("ipmi") >=0: + + print "TRYING IPMI" + ipmi = IPMI(values, verbose, ['80', '443', '623']) + rb_ret = ipmi.reboot(values[nodename], dryrun) + elif continue_probe and values['model'].find("ePowerSwitch") >=0: # TODO: allow a different port than http 80. if values['pcu_id'] in [1089, 1071, 1046, 1035, 1118]: diff --git a/web/MonitorWeb/monitorweb/controllers.py b/web/MonitorWeb/monitorweb/controllers.py index d4553dc..2d7f23e 100644 --- a/web/MonitorWeb/monitorweb/controllers.py +++ b/web/MonitorWeb/monitorweb/controllers.py @@ -5,13 +5,18 @@ from turbogears import controllers, expose, flash # log = logging.getLogger("monitorweb.controllers") from monitor.database.info.model import * from pcucontrol import reboot +from monitor.wrapper.plccache import plcdb_id2lb as site_id2lb def format_ports(pcu): retval = [] if pcu.port_status and len(pcu.port_status.keys()) > 0 : obj = reboot.model_to_object(pcu.plc_pcu_stats['model']) for port in obj.supported_ports: - state = pcu.port_status[str(port)] + try: + state = pcu.port_status[str(port)] + except: + state = "unknown" + retval.append( (port, state) ) if retval == []: @@ -106,6 +111,11 @@ class Root(controllers.RootController): else: filtercount['pending'] += 1 + try: + node.loginbase = site_id2lb[node.plc_pcu_stats['site_id']] + except: + node.loginbase = "unknown" + node.ports = format_ports(node) node.status = format_pcu_shortstatus(node) diff --git a/web/MonitorWeb/monitorweb/static/javascript/sortable_tables.js b/web/MonitorWeb/monitorweb/static/javascript/sortable_tables.js new file mode 100644 index 0000000..6ffbe62 --- /dev/null +++ b/web/MonitorWeb/monitorweb/static/javascript/sortable_tables.js @@ -0,0 +1,214 @@ +/* + +On page load, the SortableManager: + +- Finds the table by its id (sortable_table). +- Parses its thead for columns with a "mochi:format" attribute. +- Parses the data out of the tbody based upon information given in the + "mochi:format" attribute, and clones the tr elements for later re-use. +- Clones the column header th elements for use as a template when drawing + sort arrow columns. +- Stores away a reference to the tbody, as it will be replaced on each sort. +- Performs the first sort. + + +On sort request: + +- Sorts the data based on the given key and direction +- Creates a new tbody from the rows in the new ordering +- Replaces the column header th elements with clickable versions, adding an + indicator (↑ or ↓) to the most recently sorted column. + +*/ + +SortableManager = function () { + this.thead = null; + this.tbody = null; + this.columns = []; + this.rows = []; + this.sortState = {}; + this.sortkey = 0; +}; + +mouseOverFunc = function () { + addElementClass(this, "over"); +}; + +mouseOutFunc = function () { + removeElementClass(this, "over"); +}; + +ignoreEvent = function (ev) { + if (ev && ev.preventDefault) { + ev.preventDefault(); + ev.stopPropagation(); + } else if (typeof(event) != 'undefined') { + event.cancelBubble = false; + event.returnValue = false; + } +}; + + +update(SortableManager.prototype, { + + "initWithTable": function (table) { + /*** + + Initialize the SortableManager with a table object + + ***/ + // Ensure that it's a DOM element + table = getElement(table); + // Find the thead + this.thead = table.getElementsByTagName('thead')[0]; + // get the mochi:format key and contents for each column header + var cols = this.thead.getElementsByTagName('th'); + for (var i = 0; i < cols.length; i++) { + var node = cols[i]; + var attr = null; + try { + attr = node.getAttribute("mochi:format"); + } catch (err) { + // pass + } + var o = node.childNodes; + this.columns.push({ + "format": attr, + "element": node, + "proto": node.cloneNode(true) + }); + } + // scrape the tbody for data + this.tbody = table.getElementsByTagName('tbody')[0]; + // every row + var rows = this.tbody.getElementsByTagName('tr'); + for (var i = 0; i < rows.length; i++) { + // every cell + var row = rows[i]; + var cols = row.getElementsByTagName('td'); + var rowData = []; + for (var j = 0; j < cols.length; j++) { + // scrape the text and build the appropriate object out of it + var cell = cols[j]; + var obj = scrapeText(cell); + switch (this.columns[j].format) { + case 'isodate': + obj = isoDate(obj); + break; + case 'int': + obj = parseInt(obj); + break; + case 'str': + break; + case 'istr': + obj = obj.toLowerCase(); + break; + // cases for numbers, etc. could be here + default: + break; + } + rowData.push(obj); + } + // stow away a reference to the TR and save it + rowData.row = row.cloneNode(true); + this.rows.push(rowData); + + } + + // do initial sort on first column + this.drawSortedRows(this.sortkey, true, false); + + }, + + "onSortClick": function (name) { + /*** + + Return a sort function for click events + + ***/ + return method(this, function () { + log('onSortClick', name); + var order = this.sortState[name]; + if (order == null) { + order = true; + } else if (name == this.sortkey) { + order = !order; + } + this.drawSortedRows(name, order, true); + }); + }, + + "drawSortedRows": function (key, forward, clicked) { + /*** + + Draw the new sorted table body, and modify the column headers + if appropriate + + ***/ + log('drawSortedRows', key, forward); + this.sortkey = key; + // sort based on the state given (forward or reverse) + var cmp = (forward ? keyComparator : reverseKeyComparator); + this.rows.sort(cmp(key)); + // save it so we can flip next time + this.sortState[key] = forward; + // get every "row" element from this.rows and make a new tbody + var newBody = TBODY(null, map(itemgetter("row"), this.rows)); + // swap in the new tbody + this.tbody = swapDOM(this.tbody, newBody); + // replace first column with row count. + var rows = this.tbody.getElementsByTagName('tr'); + for (var i=0; i < rows.length; i++) { + var row = rows[i]; + var cols = row.getElementsByTagName('td'); + // var cell = cols[0]; + cols[0].innerHTML = String(i); + } + for (var i = 0; i < this.columns.length; i++) { + var col = this.columns[i]; + var node = col.proto.cloneNode(true); + // remove the existing events to minimize IE leaks + col.element.onclick = null; + col.element.onmousedown = null; + col.element.onmouseover = null; + col.element.onmouseout = null; + // set new events for the new node + node.onclick = this.onSortClick(i); + node.onmousedown = ignoreEvent; + node.onmouseover = mouseOverFunc; + node.onmouseout = mouseOutFunc; + // if this is the sorted column + if (key == i) { + // \u2193 is down arrow, \u2191 is up arrow + // forward sorts mean the rows get bigger going down + var arrow = (forward ? "\u2193" : "\u2191"); + // add the character to the column header + node.appendChild(SPAN(null, arrow)); + if (clicked) { + node.onmouseover(); + } + } + + // swap in the new th + col.element = swapDOM(col.element, node); + } + } +}); + +sortableManager = new SortableManager(); + +addLoadEvent(function () { + sortableManager.initWithTable('sortable_table'); +}); + +// rewrite the view-source links +addLoadEvent(function () { + var elems = getElementsByTagAndClassName("A", "view-source"); + var page = "sortable_tables/"; + for (var i = 0; i < elems.length; i++) { + var elem = elems[i]; + var href = elem.href.split(/\//).pop(); + elem.target = "_blank"; + elem.href = "../view-source/view-source.html#" + page + href; + } +}); diff --git a/web/MonitorWeb/monitorweb/templates/pculist.kid b/web/MonitorWeb/monitorweb/templates/pculist.kid index 1ca6e36..d37be12 100644 --- a/web/MonitorWeb/monitorweb/templates/pculist.kid +++ b/web/MonitorWeb/monitorweb/templates/pculist.kid @@ -5,14 +5,15 @@ from pcucontrol.reboot import pcu_name, model_to_object from monitor import config def plc_site_link(pcu): - return "https://" + config.MONITOR_HOSTNAME + "/db/sites/index.php?id=" + str(pcu['site_id']) + return "https://" + config.PLC_WEB_HOSTNAME + "/db/sites/index.php?id=" + str(pcu['site_id']) def pcu_link(pcu): - return "https://" + config.MONITOR_HOSTNAME + "/db/sites/pcu.php?id=" + str(pcu['pcu_id']) + return "https://" + config.PLC_WEB_HOSTNAME + "/db/sites/pcu.php?id=" + str(pcu['pcu_id']) ?> + xmlns:py="http://purl.org/kid/ns#" + xmlns:mochi="http://www.mochi.org">
@@ -28,10 +29,11 @@ def pcu_link(pcu):
- +
- + + @@ -43,7 +45,8 @@ def pcu_link(pcu): - + + diff --git a/web/MonitorWeb/monitorweb/templates/sitemenu.kid b/web/MonitorWeb/monitorweb/templates/sitemenu.kid index d46e500..3d15356 100644 --- a/web/MonitorWeb/monitorweb/templates/sitemenu.kid +++ b/web/MonitorWeb/monitorweb/templates/sitemenu.kid @@ -2,7 +2,10 @@ App Name - ${page_title} - + + + + -- 2.43.0
SiteSite PCU Name Missing Fields DNS Status
sitename${node.loginbase} ${pcu_name(node.plc_pcu_stats)}