summaryrefslogtreecommitdiffstats
path: root/build/scripts-2.7/bcfg2-build-reports
diff options
context:
space:
mode:
Diffstat (limited to 'build/scripts-2.7/bcfg2-build-reports')
-rwxr-xr-xbuild/scripts-2.7/bcfg2-build-reports319
1 files changed, 319 insertions, 0 deletions
diff --git a/build/scripts-2.7/bcfg2-build-reports b/build/scripts-2.7/bcfg2-build-reports
new file mode 100755
index 000000000..575a1b7a4
--- /dev/null
+++ b/build/scripts-2.7/bcfg2-build-reports
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+
+"""
+bcfg2-build-reports generates & distributes reports of statistic
+information for Bcfg2."""
+
+__revision__ = '$Revision$'
+
+import copy
+import getopt
+import re
+import os
+import socket
+import sys
+from time import asctime, strptime
+from ConfigParser import ConfigParser, NoSectionError, NoOptionError
+from lxml.etree import XML, XSLT, parse, Element, ElementTree, SubElement, tostring, XMLSyntaxError
+
+def generatereport(rspec, nrpt):
+ """
+ generatereport creates and returns an ElementTree representation
+ of a report adhering to the XML spec for intermediate reports.
+ """
+ reportspec = copy.deepcopy(rspec)
+ nodereprt = copy.deepcopy(nrpt)
+
+ reportgood = reportspec.get("good", default = 'Y')
+ reportmodified = reportspec.get("modified", default = 'Y')
+ current_date = asctime()[:10]
+
+ """Build regex of all the nodes we are reporting about."""
+ pattern = re.compile( '|'.join([item.get("name") for item in reportspec.findall('Machine')]))
+
+ for node in nodereprt.findall('Node'):
+ if not (node.findall("Statistics") and pattern.match(node.get('name'))):
+ # Don't know enough about node.
+ nodereprt.remove(node)
+ continue
+
+ # Reduce to most recent Statistics entry.
+ statisticslist = node.findall('Statistics')
+ # This line actually sorts from most recent to oldest.
+ statisticslist.sort(lambda y, x: cmp(strptime(x.get("time")), strptime(y.get("time"))))
+ stats = statisticslist[0]
+
+ [node.remove(item) for item in node.findall('Statistics')]
+
+ # Add a good tag if node is good and we wnat to report such.
+ if reportgood == 'Y' and stats.get('state') == 'clean':
+ SubElement(stats,"Good")
+
+ [stats.remove(item) for item in stats.findall("Bad") + stats.findall("Modified") if \
+ item.getchildren() == []]
+ [stats.remove(item) for item in stats.findall("Modified") if reportmodified == 'N']
+
+ # Test for staleness -if stale add Stale tag.
+ if stats.get("time").find(current_date) == -1:
+ SubElement(stats,"Stale")
+ node.append(stats)
+ return nodereprt
+
+def mail(mailbody, confi):
+ """mail mails a previously generated report."""
+
+ try:
+ mailer = confi.get('statistics', 'sendmailpath')
+ except (NoSectionError, NoOptionError):
+ mailer = "/usr/sbin/sendmail"
+ # Open a pipe to the mail program and
+ # write the data to the pipe.
+ pipe = os.popen("%s -t" % mailer, 'w')
+ pipe.write(mailbody)
+ exitcode = pipe.close()
+ if exitcode:
+ print "Exit code: %s" % exitcode
+
+def rss(reportxml, delivery, report):
+ """rss appends a new report to the specified rss file
+ keeping the last 9 articles.
+ """
+ # Check and see if rss file exists.
+ for destination in delivery.findall('Destination'):
+ try:
+ fil = open(destination.attrib['address'], 'r')
+ olddoc = XML(fil.read())
+
+ # Defines the number of recent articles to keep.
+ items = olddoc.find("channel").findall("item")[0:9]
+ fil.close()
+ fil = open(destination.attrib['address'], 'w')
+ except (IOError, XMLSyntaxError):
+ fil = open(destination.attrib['address'], 'w')
+ items = []
+
+ rssdata = Element("rss")
+ channel = SubElement(rssdata, "channel")
+ rssdata.set("version", "2.0")
+ chantitle = SubElement(channel, "title")
+ chantitle.text = report.attrib['name']
+ chanlink = SubElement(channel, "link")
+
+ # This can later link to WWW report if one gets published
+ # simultaneously?
+ chanlink.text = "http://www.mcs.anl.gov/cobalt/bcfg2"
+ chandesc = SubElement(channel, "description")
+ chandesc.text = "Information regarding the 10 most recent bcfg2 runs."
+
+ channel.append(XML(reportxml))
+
+ if items != []:
+ for item in items:
+ channel.append(item)
+
+ tree = tostring(rssdata, encoding='UTF-8', xml_declaration=True)
+ fil.write(tree)
+ fil.close()
+
+def www(reportxml, delivery):
+ """www outputs report to."""
+
+ # This can later link to WWW report if one gets published
+ # simultaneously?
+ for destination in delivery.findall('Destination'):
+ fil = open(destination.attrib['address'], 'w')
+
+ fil.write(reportxml)
+ fil.close()
+
+def fileout(reportxml, delivery):
+ """Outputs to plain text file."""
+ for destination in delivery.findall('Destination'):
+ fil = open(destination.attrib['address'], 'w')
+
+ fil.write(reportxml)
+ fil.close()
+
+def pretty_print(element, level=0):
+ """Produce a pretty-printed text representation of element."""
+ if element.text:
+ fmt = "%s<%%s %%s>%%s</%%s>" % (level*" ")
+ data = (element.tag, (" ".join(["%s='%s'" % keyval for keyval in element.attrib.iteritems()])),
+ element.text, element.tag)
+ if element._children:
+ fmt = "%s<%%s %%s>\n" % (level*" ",) + (len(element._children) * "%s") + "%s</%%s>\n" % (level*" ")
+ data = (element.tag, ) + (" ".join(["%s='%s'" % keyval for keyval in element.attrib.iteritems()]),)
+ data += tuple([pretty_print(entry, level+2) for entry in element._children]) + (element.tag, )
+ else:
+ fmt = "%s<%%s %%s/>\n" % (level * " ")
+ data = (element.tag, " ".join(["%s='%s'" % keyval for keyval in element.attrib.iteritems()]))
+ return fmt % data
+
+
+if __name__ == '__main__':
+ ping=True
+ all=False
+ if '-C' in sys.argv:
+ cfpath = sys.argv[sys.argv.index('-C') + 1]
+ else:
+ cfpath = '/etc/bcfg2.conf'
+ c = ConfigParser()
+ c.read([cfpath])
+ configpath = "%s/etc/report-configuration.xml" % c.get('server', 'repository')
+ statpath = "%s/etc/statistics.xml" % c.get('server', 'repository')
+ clientsdatapath = "%s/Metadata/clients.xml" % c.get('server', 'repository')
+ try:
+ prefix = c.get('server', 'prefix')
+ except (NoSectionError, NoOptionError):
+ prefix = '/usr'
+
+ transformpath = "/%s/share/bcfg2/xsl-transforms/" % (prefix)
+ #websrcspath = "/usr/share/bcfg2/web-rprt-srcs/"
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "C:hAc:Ns:", ["help", "all", "config=","no-ping", "stats="])
+ except getopt.GetoptError, mesg:
+ # Print help information and exit:
+ print "%s\nUsage:\nbcfg2-build-reports [-h][-A (include ALL clients)] [-c <configuration-file>] [-s <statistics-file>][-N (do not ping clients)]" % (mesg)
+ raise SystemExit, 2
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ print "Usage:\nbcfg2-build-reports [-h] [-c <configuration-file>] [-s <statistics-file>]"
+ raise SystemExit
+ if o in ("-A", "--all"):
+ all=True
+ if o in ("-c", "--config"):
+ configpath = a
+ if o in ("-N", "--no-ping"):
+ ping = False
+ if o in ("-s", "--stats"):
+ statpath = a
+
+
+ # See if hostinfo.xml exists, and is less than 23.5 hours old
+ #try:
+ #hostinstat = os.stat(hostinfopath)
+ #if (time() - hostinstat[9])/(60*60) > 23.5:
+ if ping:
+ os.system('bcfg2-ping-sweep -C %s' % cfpath) # bcfg2-ping-sweep needs to be in path
+ #except OSError:
+ # os.system('GenerateHostInfo')#Generate HostInfo needs to be in path
+
+
+ """Reads data & config files."""
+ try:
+ statsdata = XML(open(statpath).read())
+ except (IOError, XMLSyntaxError):
+ print("bcfg2-build-reports: Failed to parse %s"%(statpath))
+ raise SystemExit, 1
+ try:
+ configdata = XML(open(configpath).read())
+ except (IOError, XMLSyntaxError):
+ print("bcfg2-build-reports: Failed to parse %s"%(configpath))
+ raise SystemExit, 1
+ try:
+ clientsdata = XML(open(clientsdatapath).read())
+ except (IOError, XMLSyntaxError):
+ print("bcfg2-build-reports: Failed to parse %s"%(clientsdatapath))
+ raise SystemExit, 1
+
+ # Merge data from three sources.
+ nodereport = Element("Report", attrib={"time" : asctime()})
+ # Should all of the other info in Metadata be appended?
+ # What about all of the package stuff for other types of reports?
+ for client in clientsdata.findall("Client"):
+ nodel = Element("Node", attrib={"name" : client.get("name")})
+ nodel.append(client)
+ for nod in statsdata.findall("Node"):
+ if client.get('name').find(nod.get('name')) == 0:
+ for statel in nod.findall("Statistics"):
+ nodel.append(statel)
+ nodereport.append(nodel)
+
+ if all:
+ for nod in statsdata.findall("Node"):
+ for client in clientsdata.findall("Client"):
+ if client.get('name').find(nod.get('name')) == 0:
+ break
+ else:
+ nodel = Element("Node", attrib={"name" : nod.get("name")})
+ client = Element("Client", attrib={"name" : nod.get("name"), "profile" : "default"})
+ nodel.append(client)
+ for statel in nod.findall("Statistics"):
+ nodel.append(statel)
+ nodereport.append(nodel)
+
+
+ for reprt in configdata.findall('Report'):
+ nodereport.set("name", reprt.get("name", default="BCFG Report"))
+
+ if reprt.get('refresh-time') != None:
+ nodereport.set("refresh-time", reprt.get("refresh-time", default="600"))
+
+ procnodereport = generatereport(reprt, nodereport)
+
+ for deliv in reprt.findall('Delivery'):
+ # Is a deepcopy of procnodereport necessary?
+
+ delivtype = deliv.get('type', default='nodes-digest')
+ deliverymechanism = deliv.get('mechanism', default='www')
+
+ # Apply XSLT, different ones based on report type, and options
+ if deliverymechanism == 'null-operator': # Special Cases
+ fileout(tostring(ElementTree(procnodereport).getroot(), encoding='UTF-8', xml_declaration=True), deliv)
+ break
+ transform = delivtype + '-' + deliverymechanism + '.xsl'
+
+ try: # Make sure valid stylesheet is selected.
+ os.stat(transformpath + transform)
+ except:
+ print("bcfg2-build-reports: Invalid report type or delivery mechanism.\n Can't find: "\
+ + transformpath + transform)
+ raise SystemExit, 1
+
+ try: # Try to parse stylesheet.
+ stylesheet = XSLT(parse(transformpath + transform))
+ except:
+ print("bcfg2-build-reports: invalid XSLT transform file.")
+ raise SystemExit, 1
+
+ if deliverymechanism == 'mail':
+ if delivtype == 'nodes-individual':
+ reportdata = copy.deepcopy(procnodereport)
+ for noden in reportdata.findall("Node"):
+ [reportdata.remove(y) for y in reportdata.findall("Node")]
+ reportdata.append(noden)
+ result = stylesheet.apply(ElementTree(reportdata))
+ outputstring = stylesheet.tostring(result)
+
+ if not outputstring == None:
+ toastring = ''
+ for desti in deliv.findall("Destination"):
+ toastring = "%s%s " % \
+ (toastring, desti.get('address'))
+ # Prepend To: and From:
+ outputstring = "To: %s\nFrom: root@%s\n%s"% \
+ (toastring, socket.getfqdn(), outputstring)
+ mail(outputstring, c) #call function to send
+
+ else:
+ reportdata = copy.deepcopy(procnodereport)
+
+ result = stylesheet.apply(ElementTree(reportdata))
+ outputstring = stylesheet.tostring(result)
+
+ if not outputstring == None:
+ toastring = ''
+ for desti in deliv.findall("Destination"):
+ toastring = "%s%s " % \
+ (toastring, desti.get('address'))
+ # Prepend To: and From:
+ outputstring = "To: %s\nFrom: root@%s\n%s"% \
+ (toastring, socket.getfqdn(), outputstring)
+ mail(outputstring, c) #call function to send
+ else:
+ outputstring = tostring(stylesheet.apply(ElementTree(procnodereport)).getroot(), encoding='UTF-8', xml_declaration=True)
+ if deliverymechanism == 'rss':
+ rss(outputstring, deliv, reprt)
+ else: # Must be deliverymechanism == 'www':
+ www(outputstring, deliv)