diff options
author | Fabian Affolter <fabian@bernewireless.net> | 2010-12-08 23:27:15 +0100 |
---|---|---|
committer | Fabian Affolter <fabian@bernewireless.net> | 2010-12-08 23:27:15 +0100 |
commit | e833a7a76b231cd346f09c9a422ecb855d1cc6b4 (patch) | |
tree | e2762fc31e1162a6859dbd6cd7b5c28208296d9d /build/scripts-2.7 | |
parent | baf5854f414c2a35887e3fbbcfd094e12e7c6ee7 (diff) | |
download | bcfg2-e833a7a76b231cd346f09c9a422ecb855d1cc6b4.tar.gz bcfg2-e833a7a76b231cd346f09c9a422ecb855d1cc6b4.tar.bz2 bcfg2-e833a7a76b231cd346f09c9a422ecb855d1cc6b4.zip |
Merge with upstream
Diffstat (limited to 'build/scripts-2.7')
-rwxr-xr-x | build/scripts-2.7/bcfg2 | 313 | ||||
-rwxr-xr-x | build/scripts-2.7/bcfg2-admin | 80 | ||||
-rwxr-xr-x | build/scripts-2.7/bcfg2-build-reports | 319 | ||||
-rwxr-xr-x | build/scripts-2.7/bcfg2-info | 468 | ||||
-rwxr-xr-x | build/scripts-2.7/bcfg2-ping-sweep | 71 | ||||
-rwxr-xr-x | build/scripts-2.7/bcfg2-repo-validate | 227 | ||||
-rwxr-xr-x | build/scripts-2.7/bcfg2-reports | 282 | ||||
-rwxr-xr-x | build/scripts-2.7/bcfg2-server | 79 |
8 files changed, 1839 insertions, 0 deletions
diff --git a/build/scripts-2.7/bcfg2 b/build/scripts-2.7/bcfg2 new file mode 100755 index 000000000..9f56481bf --- /dev/null +++ b/build/scripts-2.7/bcfg2 @@ -0,0 +1,313 @@ +#!/usr/bin/python + +"""Bcfg2 Client""" +__revision__ = '$Revision$' + +import logging +import os +import signal +import sys +import tempfile +import time +import xmlrpclib +import fcntl +import Bcfg2.Options +import Bcfg2.Client.XML +import Bcfg2.Client.Frame +import Bcfg2.Client.Tools + +import Bcfg2.Proxy +import Bcfg2.Logger + +logger = logging.getLogger('bcfg2') + +def cb_sigint_handler(signum, frame): + """Exit upon CTRL-C.""" + os._exit(1) + +DECISION_LIST = Bcfg2.Options.Option('Decision List', default=False, + cmd="--decision-list", odesc='<file>', + long_arg=True) + + +class Client: + """The main bcfg2 client class""" + + def __init__(self): + self.toolset = None + self.config = None + + optinfo = { + # 'optname': (('-a', argdesc, optdesc), + # env, cfpath, default, boolean)), + 'verbose': Bcfg2.Options.VERBOSE, + 'extra': Bcfg2.Options.CLIENT_EXTRA_DISPLAY, + 'quick': Bcfg2.Options.CLIENT_QUICK, + 'debug': Bcfg2.Options.DEBUG, + 'lockfile': Bcfg2.Options.LOCKFILE, + 'drivers': Bcfg2.Options.CLIENT_DRIVERS, + 'dryrun': Bcfg2.Options.CLIENT_DRYRUN, + 'paranoid': Bcfg2.Options.CLIENT_PARANOID, + 'bundle': Bcfg2.Options.CLIENT_BUNDLE, + 'bundle-quick': Bcfg2.Options.CLIENT_BUNDLEQUICK, + 'indep': Bcfg2.Options.CLIENT_INDEP, + 'file': Bcfg2.Options.CLIENT_FILE, + 'interactive': Bcfg2.Options.INTERACTIVE, + 'cache': Bcfg2.Options.CLIENT_CACHE, + 'profile': Bcfg2.Options.CLIENT_PROFILE, + 'remove': Bcfg2.Options.CLIENT_REMOVE, + 'help': Bcfg2.Options.HELP, + 'setup': Bcfg2.Options.CFILE, + 'server': Bcfg2.Options.SERVER_LOCATION, + 'user': Bcfg2.Options.CLIENT_USER, + 'password': Bcfg2.Options.SERVER_PASSWORD, + 'retries': Bcfg2.Options.CLIENT_RETRIES, + 'kevlar': Bcfg2.Options.CLIENT_KEVLAR, + 'decision-list': DECISION_LIST, + 'encoding': Bcfg2.Options.ENCODING, + 'omit-lock-check': Bcfg2.Options.OMIT_LOCK_CHECK, + 'filelog': Bcfg2.Options.LOGGING_FILE_PATH, + 'decision': Bcfg2.Options.CLIENT_DLIST, + 'servicemode': Bcfg2.Options.CLIENT_SERVICE_MODE, + 'key': Bcfg2.Options.CLIENT_KEY, + 'certificate': Bcfg2.Options.CLIENT_CERT, + 'ca': Bcfg2.Options.CLIENT_CA, + 'serverCN': Bcfg2.Options.CLIENT_SCNS, + } + + self.setup = Bcfg2.Options.OptionParser(optinfo) + self.setup.parse(sys.argv[1:]) + + if self.setup['args']: + print("Bcfg2 takes no arguments, only options") + print(self.setup.buildHelpMessage()) + raise SystemExit(1) + level = 30 + if self.setup['verbose']: + level = 20 + if self.setup['debug']: + level = 0 + Bcfg2.Logger.setup_logging('bcfg2', + to_syslog=False, + level=level, + to_file=self.setup['filelog']) + self.logger = logging.getLogger('bcfg2') + self.logger.debug(self.setup) + if self.setup['bundle-quick']: + if self.setup['bundle'] == []: + self.logger.error("-Q option requires -b") + raise SystemExit(1) + elif self.setup['remove'] != False: + self.logger.error("-Q option incompatible with -r") + raise SystemExit(1) + if 'drivers' in self.setup and self.setup['drivers'] == 'help': + self.logger.info("The following drivers are available:") + self.logger.info(Bcfg2.Client.Tools.drivers) + raise SystemExit(0) + if self.setup['remove'] and 'services' in self.setup['remove']: + self.logger.error("Service removal is nonsensical, disable services to get former behavior") + if self.setup['remove'] not in [False, 'all', 'services', 'packages']: + self.logger.error("Got unknown argument %s for -r" % (self.setup['remove'])) + if (self.setup["file"] != False) and (self.setup["cache"] != False): + print("cannot use -f and -c together") + raise SystemExit(1) + + def run_probe(self, probe): + """Execute probe.""" + name = probe.get('name') + self.logger.info("Running probe %s" % name) + ret = Bcfg2.Client.XML.Element("probe-data", + name=name, + source=probe.get('source')) + try: + scripthandle, scriptname = tempfile.mkstemp() + script = open(scriptname, 'w+') + try: + script.write("#!%s\n" % + (probe.attrib.get('interpreter', '/bin/sh'))) + script.write(probe.text) + script.close() + os.close(scripthandle) + os.chmod(script.name, 0755) + ret.text = os.popen(script.name).read().strip() + self.logger.info("Probe %s has result:\n%s" % (name, ret.text)) + finally: + os.unlink(script.name) + except: + self.logger.error("Failed to execute probe: %s" % (name), exc_info=1) + raise SystemExit(1) + return ret + + def fatal_error(self, message): + """Signal a fatal error.""" + self.logger.error("Fatal error: %s" % (message)) + os._exit(1) + + def run(self): + """Perform client execution phase.""" + times = {} + + # begin configuration + times['start'] = time.time() + + if self.setup['file']: + # read config from file + try: + self.logger.debug("Reading cached configuration from %s" % + (self.setup['file'])) + configfile = open(self.setup['file'], 'r') + rawconfig = configfile.read() + configfile.close() + except IOError: + self.fatal_error("Failed to read cached configuration from: %s" + % (self.setup['file'])) + return(1) + else: + # retrieve config from server + proxy = Bcfg2.Proxy.ComponentProxy(self.setup['server'], + self.setup['user'], + self.setup['password'], + key = self.setup['key'], + cert = self.setup['certificate'], + ca = self.setup['ca'], + allowedServerCNs = self.setup['serverCN']) + + if self.setup['profile']: + try: + proxy.AssertProfile(self.setup['profile']) + except xmlrpclib.Fault: + self.fatal_error("Failed to set client profile") + return(1) + + try: + probe_data = proxy.GetProbes() + except xmlrpclib.Fault, flt: + self.logger.error("Failed to download probes from bcfg2") + self.logger.error(flt.faultString) + raise SystemExit(1) + + times['probe_download'] = time.time() + + try: + probes = Bcfg2.Client.XML.XML(probe_data) + except Bcfg2.Client.XML.ParseError, syntax_error: + self.fatal_error( + "Server returned invalid probe requests: %s" % + (syntax_error)) + return(1) + + # execute probes + try: + probedata = Bcfg2.Client.XML.Element("ProbeData") + [probedata.append(self.run_probe(probe)) + for probe in probes.findall(".//probe")] + except: + self.logger.error("Failed to execute probes") + raise SystemExit(1) + + if len(probes.findall(".//probe")) > 0: + try: + # upload probe responses + proxy.RecvProbeData(Bcfg2.Client.XML.tostring(probedata, encoding='UTF-8', xml_declaration=True)) + except: + self.logger.error("Failed to upload probe data", exc_info=1) + raise SystemExit(1) + + times['probe_upload'] = time.time() + + if self.setup['decision'] in ['whitelist', 'blacklist']: + try: + self.setup['decision_list'] = proxy.GetDecisionList( \ + self.setup['decision']) + self.logger.info("Got decision list from server:") + self.logger.info(self.setup['decision_list']) + except xmlrpclib.Fault, f: + if f.faultCode == 1: + print("GetDecisionList method not supported by server") + else: + self.logger.error("Failed to de", exc_info=1) + raise SystemExit(1) + + try: + rawconfig = proxy.GetConfig() + except xmlrpclib.Fault: + self.logger.error("Failed to download configuration from Bcfg2") + raise SystemExit(2) + + times['config_download'] = time.time() + + if self.setup['cache']: + try: + open(self.setup['cache'], 'w').write(rawconfig.encode(self.setup['encoding'])) + os.chmod(self.setup['cache'], 33152) + except IOError: + self.logger.warning("Failed to write config cache file %s" % + (self.setup['cache'])) + times['caching'] = time.time() + + try: + self.config = Bcfg2.Client.XML.XML(rawconfig) + except Bcfg2.Client.XML.ParseError, syntax_error: + self.fatal_error("The configuration could not be parsed: %s" % + (syntax_error)) + return(1) + + times['config_parse'] = time.time() + + if self.config.tag == 'error': + self.fatal_error("Server error: %s" % (self.config.text)) + return(1) + + if self.setup['bundle-quick']: + newconfig = Bcfg2.Client.XML.XML('<Configuration/>') + [newconfig.append(bundle) for bundle in self.config.getchildren() if \ + bundle.tag == 'Bundle' and bundle.get('name') in self.setup['bundle']] + self.config = newconfig + + self.tools = Bcfg2.Client.Frame.Frame(self.config, + self.setup, + times, self.setup['drivers'], + self.setup['dryrun']) + + if not self.setup['omit-lock-check']: + #check lock here + try: + lockfile = open(self.setup['lockfile'], 'w') + try: + fcntl.lockf(lockfile.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + #otherwise exit and give a warning to the user + self.fatal_error("An other instance of Bcfg2 is running. If you what to bypass the check, run with %s option" % + (Bcfg2.Options.OMIT_LOCK_CHECK.cmd)) + except: + lockfile = None + self.logger.error("Failed to open lockfile") + # execute the said configuration + self.tools.Execute() + + if not self.setup['omit-lock-check']: + #unlock here + if lockfile: + try: + fcntl.lockf(lockfile.fileno(), fcntl.LOCK_UN) + os.remove(self.setup['lockfile']) + except OSError: + self.logger.error("Failed to unlock lockfile %s" % lockfile.name) + + if not self.setup['file'] and not self.setup['bundle-quick']: + # upload statistics + feedback = self.tools.GenerateStats() + + try: + proxy.RecvStats(Bcfg2.Client.XML.tostring(feedback, + encoding='UTF-8', + xml_declaration=True)) + except xmlrpclib.Fault: + self.logger.error("Failed to upload configuration statistics") + raise SystemExit(2) + +if __name__ == '__main__': + signal.signal(signal.SIGINT, cb_sigint_handler) + client = Client() + spid = os.getpid() + client.run() diff --git a/build/scripts-2.7/bcfg2-admin b/build/scripts-2.7/bcfg2-admin new file mode 100755 index 000000000..4d5ab2e5b --- /dev/null +++ b/build/scripts-2.7/bcfg2-admin @@ -0,0 +1,80 @@ +#!/usr/bin/python +"""bcfg2-admin is a script that helps to administrate a Bcfg2 deployment.""" + +from optparse import OptionParser +from StringIO import StringIO +import logging +import Bcfg2.Server.Core +import Bcfg2.Logger +import Bcfg2.Options + +log = logging.getLogger('bcfg2-admin') + +import Bcfg2.Server.Admin + +def mode_import(modename): + """Load Bcfg2.Server.Admin.<mode>.""" + modname = modename.capitalize() + mod = getattr(__import__("Bcfg2.Server.Admin.%s" % + (modname)).Server.Admin, modname) + return getattr(mod, modname) + +def get_modes(): + """Get all available modes, except for the base mode.""" + return [x.lower() for x in Bcfg2.Server.Admin.__all__ if x != 'mode'] + +def create_description(): + """Create the description string from the list of modes.""" + modes = get_modes() + description = StringIO() + description.write("Available modes are:\n\n") + for mode in modes: + try: + description.write((" %-15s %s\n" % + (mode, mode_import(mode).__shorthelp__))) + except ImportError: + continue + return description.getvalue() + +def main(): + Bcfg2.Logger.setup_logging('bcfg2-admin', to_console=True, level=40) + usage = "Usage: %prog [options] MODE [args]" + parser = OptionParser(usage=usage) + parser.set_defaults(configfile=Bcfg2.Options.CFILE.default) + parser.add_option("-C", "--configfile", + dest="configfile", + help="Path to bcfg2.conf", + metavar="FILE") + parser.disable_interspersed_args() + (options, args) = parser.parse_args() + + # Provide help if requested or no args were specified + if len(args) < 1 or args[0] == 'help': + if len(args) > 1: + # Get help for a specific mode by passing it the help argument + args = [args[1], args[0]] + else: + # Print short help for all modes + parser.print_help() + print create_description() + raise SystemExit(0) + + if args[0] in get_modes(): + modname = args[0].capitalize() + try: + mode_cls = mode_import(modname) + except ImportError, e: + log.error("Failed to load admin mode %s: %s" % (modname, e)) + raise SystemExit(1) + mode = mode_cls(options.configfile) + mode(args[1:]) + if hasattr(mode, 'bcore'): + mode.bcore.shutdown() + else: + log.error("Unknown mode %s" % args[0]) + parser.print_help() + print create_description() + raise SystemExit(1) + +if __name__ == '__main__': + main() 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) diff --git a/build/scripts-2.7/bcfg2-info b/build/scripts-2.7/bcfg2-info new file mode 100755 index 000000000..9721122f3 --- /dev/null +++ b/build/scripts-2.7/bcfg2-info @@ -0,0 +1,468 @@ +#!/usr/bin/python + +"""This tool loads the Bcfg2 core into an interactive debugger.""" +__revision__ = '$Revision$' + +from code import InteractiveConsole +import cmd +import errno +import getopt +import logging +import lxml.etree +import os +import sys +import tempfile + +try: + import profile + import pstats + have_profile = True +except: + have_profile = False + +import Bcfg2.Logger +import Bcfg2.Options +import Bcfg2.Server.Core +import Bcfg2.Server.Plugins.Metadata +import Bcfg2.Server.Plugin + +logger = logging.getLogger('bcfg2-info') + +class dummyError(Exception): + pass + +class FileNotBuilt(Exception): + """Thrown when File entry contains no content.""" + def __init__(self, value): + Exception.__init__(self) + self.value = value + def __str__(self): + return repr(self.value) + +def printTabular(rows): + """Print data in tabular format.""" + cmax = tuple([max([len(str(row[index])) for row in rows]) + 1 \ + for index in range(len(rows[0]))]) + fstring = (" %%-%ss |" * len(cmax)) % cmax + fstring = ('|'.join([" %%-%ss "] * len(cmax))) % cmax + print(fstring % rows[0]) + print((sum(cmax) + (len(cmax) * 2) + (len(cmax) - 1)) * '=') + for row in rows[1:]: + print(fstring % row) + +def displayTrace(trace, num=80, sort=('time', 'calls')): + stats = pstats.Stats(trace) + stats.sort_stats('cumulative', 'calls', 'time') + stats.print_stats(200) + +def write_config_file(outputdir, cfg): + """Store file content of an <ConfigFile name='/path/to/file' ...>...</ConfigFile> entry + in the appropriate directory under the output directory. + """ + name = cfg.get('name') + + # directory creation + try: + os.makedirs(os.path.dirname(outputdir + name)) + except OSError, err: + if err.errno != errno.EEXIST: + raise + except: + raise + + # write config file + config_file = open(outputdir + name, "w") + try: + config_file.write(cfg.text) + except: # plugin throw an exception and therefore there is no content => None + raise FileNotBuilt(name) + config_file.close() + +class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): + + def __init__(self, repo, plgs, passwd, encoding, event_debug): + cmd.Cmd.__init__(self) + try: + Bcfg2.Server.Core.Core.__init__(self, repo, plgs, passwd, + encoding) + if event_debug: + self.fam.debug = True + except Bcfg2.Server.Core.CoreInitError, msg: + print("Core load failed because %s" % msg) + raise SystemExit(1) + self.prompt = '> ' + self.cont = True + self.fam.handle_events_in_interval(4) + + def do_loop(self): + self.cont = True + while self.cont: + try: + self.cmdloop('Welcome to bcfg2-info\n' + 'Type "help" for more information') + except SystemExit, val: + raise + except Bcfg2.Server.Plugin.PluginExecutionError: + continue + except KeyboardInterrupt: + print("Ctrl-C pressed exiting...") + self.do_exit([]) + except dummyError: + continue + except: + logger.error("command failure", exc_info=1) + + def do_debug(self, args): + try: + opts, _ = getopt.getopt(args.split(), 'nf:') + except: + print "Usage: debug [-n] [-f <command list>]" + return + self.cont = False + scriptmode = False + interactive = True + for opt in opts: + if opt[0] == '-f': + scriptmode = True + spath = opt[1] + elif opt[0] == '-n': + interactive = False + sh = InteractiveConsole(locals()) + if scriptmode: + for command in [c.strip() for c in open(spath).readlines()]: + if command: + sh.push(command) + if interactive: + print("dropping to python interpreter; press ^D to resume") + try: + import IPython + shell = IPython.Shell.IPShell(argv=[], user_ns=locals()) + shell.mainloop() + except ImportError: + sh.interact() + + def do_quit(self, _): + """ + Exit program. + Usage: [quit|exit] + """ + for plugin in self.plugins.values(): + plugin.shutdown() + os._exit(0) + + do_EOF = do_quit + do_exit = do_quit + + def do_help(self, _): + """Print out usage info.""" + print 'Commands:' + print 'build <hostname> <filename> - build config for hostname, writing to filename' + print 'builddir <hostname> <dirname> - build config for hostname, writing separate files to dirname' + print 'buildall <directory> - build configs for all clients in directory' + print 'buildfile <filename> <hostname> - build config file for hostname (not written to disk)' + print 'bundles - print out group/bundle information' + print 'clients - print out client/profile information' + print 'debug - shell out to native python interpreter' + print 'event_debug - display filesystem events as they are processed' + print 'generators - list current versions of generators' + print 'groups - list groups' + print 'help - print this list of available commands' + print 'mappings <type*> <name*> - print generator mappings for optional type and name' + print 'profile <command> <args> - profile a single bcfg2-info command' + print 'quit - Exit the bcfg2-info command line' + print 'showentries <hostname> <type> - show abstract configuration entries for a given host' + print 'showclient <client1> <client2> - show metadata for given hosts' + print 'update - process pending file events' + print 'version - print version of this tool' + + + def do_update(self, _): + """Process pending fs events.""" + self.fam.handle_events_in_interval(0.1) + + def do_version(self, _): + """Print out code version.""" + print(__revision__) + + def do_build(self, args): + """Build client configuration.""" + alist = args.split() + path_force = False + if '-f' in args: + alist.remove('-f') + path_force = True + if len(alist) == 2: + client, ofile = alist + if not ofile.startswith('/tmp') and not path_force: + print("Refusing to write files outside of /tmp without -f option") + return + output = open(ofile, 'w') + data = lxml.etree.tostring(self.BuildConfiguration(client), + encoding='UTF-8', xml_declaration=True, + pretty_print=True) + output.write(data) + output.close() + else: + print('Usage: build [-f] <hostname> <output file>') + + def help_builddir(self): + """Display help for builddir command.""" + print('Usage: builddir [-f] <hostname> <output dir>') + print('') + print('Generates a config for client <hostname> and writes the') + print('individual configuration files out separately in a tree') + print('under <output dir>. The <output dir> directory must be') + print('rooted under /tmp unless the -f argument is provided, in') + print('which case it can be located anywhere.') + print('') + print('NOTE: Currently only handles ConfigFile entries and writes') + print('all content with the default owner and permissions. These') + print('could be much more permissive than would be created by the') + print('bcfg2 client itself.') + + def do_builddir(self, args): + """Build client configuration as separate files within a dir.""" + alist = args.split() + path_force = False + if '-f' in args: + alist.remove('-f') + path_force = True + if len(alist) == 2: + client, odir = alist + if not odir.startswith('/tmp') and not path_force: + print("Refusing to write files outside of /tmp without -f option") + return + client_config = self.BuildConfiguration(client) + if client_config.tag == 'error': + print("Building client configuration failed.") + return + + # handle <Path type='file'> entries + for configfile in [cfile for cfile in client_config.findall(".//Path[@type = 'file']")]: + try: + write_config_file(odir, configfile) + except FileNotBuilt, ex: + print("Warning: No file content generated for ConfigFile %s!" % ex) + pass + except Exception, ex: + print("unknown error, I give up: %s" %ex) + return + + print("Config for %s written to %s" % (client, odir)) + + else: + print('Error: Incorrect number of parameters') + self.help_builddir() + + def do_buildall(self, args): + if len(args.split()) != 1: + print("Usage: buildall <directory>") + return + try: + os.mkdir(args) + except: + pass + for client in self.metadata.clients: + self.do_build("%s %s/%s.xml" % (client, args, client)) + + def do_buildfile(self, args): + """Build a config file for client.""" + if len(args.split()) == 2: + fname, client = args.split() + entry = lxml.etree.Element('Path', type='file', name=fname) + metadata = self.build_metadata(client) + self.Bind(entry, metadata) + print(lxml.etree.tostring(entry, encoding="UTF-8", xml_declaration=True)) + else: + print('Usage: buildfile filename hostname') + + def do_bundles(self, _): + """Print out group/bundle info.""" + data = [('Group', 'Bundles')] + groups = list(self.metadata.groups.keys()) + groups.sort() + for group in groups: + data.append((group, + ','.join(self.metadata.groups[group][0]))) + printTabular(data) + + def do_clients(self, _): + """Print out client info.""" + data = [('Client', 'Profile')] + clist = list(self.metadata.clients.keys()) + clist.sort() + for client in clist: + data.append((client, self.metadata.clients[client])) + printTabular(data) + + def do_generators(self, _): + """Print out generator info.""" + for generator in self.generators: + print(generator.__version__) + + def do_showentries(self, args): + """Show abstract configuration entries for a given host.""" + arglen = len(args.split()) + if arglen not in [1, 2]: + print("Usage: showentries <hostname> <type>") + return + client = args.split()[0] + try: + meta = self.build_metadata(client) + except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError: + print("Unable to find metadata for host %s" % client) + return + structures = self.GetStructures(meta) + output = [('entrytype', 'name')] + if arglen == 1: + for item in structures: + for child in item.getchildren(): + output.append((child.tag, child.get('name'))) + if arglen == 2: + etype = args.split()[1] + for item in structures: + for child in item.getchildren(): + if child.tag in [etype, "Bound%s" % etype]: + output.append((child.tag, child.get('name'))) + printTabular(output) + + def do_groups(self, _): + """Print out group info.""" + data = [("Groups", "Profile", "Category", "Contains")] + grouplist = list(self.metadata.groups.keys()) + grouplist.sort() + for group in grouplist: + if group in self.metadata.profiles: + prof = 'yes' + else: + prof = 'no' + if group in self.metadata.categories: + cat = self.metadata.categories[group] + else: + cat = '' + gdata = [grp for grp in self.metadata.groups[group][1]] + if group in gdata: + gdata.remove(group) + data.append((group, prof, cat, ','.join(gdata))) + printTabular(data) + + def do_showclient(self, args): + """Print host metadata.""" + data = [('Client', 'Profile', "Groups", "Bundles")] + if not len(args): + print("Usage:\nshowclient <client> ... <clientN>") + return + for client in args.split(): + try: + client_meta = self.build_metadata(client) + except: + print("Client %s not defined" % client) + continue + print "Hostname:\t", client_meta.hostname + print "Profile:\t", client_meta.profile + print "Groups:\t\t", list(client_meta.groups)[0] + for grp in list(client_meta.groups)[1:]: + print '\t\t%s' % grp + if client_meta.bundles: + print "Bundles:\t", list(client_meta.bundles)[0] + for bnd in list(client_meta.bundles)[1:]: + print '\t\t%s' % bnd + if client_meta.connectors: + print "Connector data" + print "=" * 80 + for conn in client_meta.connectors: + if getattr(client_meta, conn): + print "%s:\t" % (conn), getattr(client_meta, conn) + print "=" * 80 + + def do_mappings(self, args): + """Print out mapping info.""" + # dump all mappings unless type specified + data = [('Plugin', 'Type', 'Name')] + arglen = len(args.split()) + for generator in self.generators: + if arglen == 0: + etypes = list(generator.Entries.keys()) + else: + etypes = [args.split()[0]] + if arglen == 2: + interested = [(etype, [args.split()[1]]) \ + for etype in etypes] + else: + interested = [(etype, generator.Entries[etype]) \ + for etype in etypes \ + if etype in generator.Entries] + for etype, names in interested: + for name in [name for name in names if name in \ + generator.Entries.get(etype, {})]: + data.append((generator.name, etype, name)) + printTabular(data) + + def do_event_debug(self, args): + self.fam.debug = True + + def do_cfgdebug(self, args): + try: + meta = self.build_metadata(args) + except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError: + print("Unable to find metadata for host %s" % client) + return + structures = self.GetStructures(meta) + for clist in [struct.findall('Path') for struct in structures]: + for cfile in clist: + if cfile.get('name') in self.plugins['Cfg'].Entries['ConfigFile']: + cset = self.plugins['Cfg'].entries[cfile.get('name')] + cand = cset.get_matching(meta) + fields = ['all', 'group'] + while len(cand) > 1 and fields: + field = fields.pop(0) + [cand.remove(c) for c in cand[:] + if getattr(c.specific, field)] + if len(cand) != 1: + sys.stderr.write("Entry %s failed" % cfile.get('name')) + continue + print(cand[0].name) + + def do_profile(self, arg): + if not have_profile: + print("Profiling functionality not available") + return + tracefname = tempfile.mktemp() + p = profile.Profile() + p.runcall(self.onecmd, arg) + displayTrace(p) + + def Run(self, args): + if args: + self.onecmd(" ".join(args)) + os._exit(0) + else: + self.do_loop() + +if __name__ == '__main__': + Bcfg2.Logger.setup_logging('bcfg2-info', to_syslog=False) + optinfo = { + 'configfile': Bcfg2.Options.CFILE, + 'help': Bcfg2.Options.HELP, + } + optinfo.update({'repo': Bcfg2.Options.SERVER_REPOSITORY, + 'plugins': Bcfg2.Options.SERVER_PLUGINS, + 'password': Bcfg2.Options.SERVER_PASSWORD, + 'event debug': Bcfg2.Options.DEBUG, + 'profile': Bcfg2.Options.CORE_PROFILE, + 'encoding': Bcfg2.Options.ENCODING}) + setup = Bcfg2.Options.OptionParser(optinfo) + setup.parse(sys.argv[1:]) + if setup['profile'] and have_profile: + prof = profile.Profile() + loop = prof.runcall(infoCore, setup['repo'], setup['plugins'], + setup['password'], setup['encoding'], + setup['event debug']) + displayTrace(prof) + else: + if setup['profile']: + print("Profiling functionality not available") + loop = infoCore(setup['repo'], setup['plugins'], setup['password'], + setup['encoding'], setup['event debug']) + + loop.Run(setup['args']) diff --git a/build/scripts-2.7/bcfg2-ping-sweep b/build/scripts-2.7/bcfg2-ping-sweep new file mode 100755 index 000000000..b3905e3a0 --- /dev/null +++ b/build/scripts-2.7/bcfg2-ping-sweep @@ -0,0 +1,71 @@ +#!/usr/bin/python +#GenerateHostInfo - Joey Hagedorn - hagedorn@mcs.anl.gov + +"""Generates hostinfo.xml at a regular interval.""" + +__revision__ = '$Revision$' + +from os import dup2, execl, fork, uname, wait +import sys +import time +import lxml.etree + +import Bcfg2.Options + +if __name__ == '__main__': + opts = {'repo': Bcfg2.Options.SERVER_REPOSITORY, + 'configfile': Bcfg2.Options.CFILE} + setup = Bcfg2.Options.OptionParser(opts) + setup.parse(sys.argv[1:]) + + cfpath = setup['configfile'] + clientdatapath = "%s/Metadata/clients.xml" % setup['repo'] + + clientElement = lxml.etree.parse(clientdatapath) + hostlist = [client.get('name') for client in clientElement.findall("Client")] + + pids = {} + null = open('/dev/null', 'w+') + + #use uname to detect OS and use -t for darwin and -w for linux + #/bin/ping on linux /sbin/ping on os x + osname = uname()[0] + + + while hostlist or pids: + if hostlist and len(pids.keys()) < 15: + host = hostlist.pop() + pid = fork() + if pid == 0: + # in child + dup2(null.fileno(), sys.__stdin__.fileno()) + dup2(null.fileno(), sys.__stdout__.fileno()) + dup2(null.fileno(), sys.__stderr__.fileno()) + if osname == 'Linux': + execl('/bin/ping', 'ping', '-w', '5', '-c', '1', host) + elif osname in ['Darwin', 'FreeBSD']: + execl('/sbin/ping', 'ping', '-t', '5', '-c', '1', host) + elif osname == 'SunOS': + execl('/usr/sbin/ping', 'ping', host, '56', '1') + else: #default + execl('/bin/ping', 'ping', '-w', '5', '-c', '1', host) + else: + pids[pid] = host + else: + try: + (cpid, status) = wait() + except OSError: + continue + chost = pids[cpid] + del pids[cpid] + elm = clientElement.xpath("//Client[@name='%s']"%chost)[0] + if status == 0: + elm.set("pingable",'Y') + elm.set("pingtime", str(time.time())) + else: + elm.set("pingable",'N') + + fout = open(clientdatapath, 'w') + fout.write(lxml.etree.tostring(clientElement.getroot(), encoding='UTF-8', xml_declaration=True)) + fout.close() + diff --git a/build/scripts-2.7/bcfg2-repo-validate b/build/scripts-2.7/bcfg2-repo-validate new file mode 100755 index 000000000..4606ab10e --- /dev/null +++ b/build/scripts-2.7/bcfg2-repo-validate @@ -0,0 +1,227 @@ +#!/usr/bin/python + +""" +bcfg2-repo-validate checks all xml files in Bcfg2 +repos against their respective XML schemas. +""" +__revision__ = '$Revision$' + +import glob +import lxml.etree +import os +import sys +import Bcfg2.Options + +if __name__ == '__main__': + opts = {'repo': Bcfg2.Options.SERVER_REPOSITORY, + 'prefix': Bcfg2.Options.INSTALL_PREFIX, + 'verbose': Bcfg2.Options.VERBOSE, + 'configfile': Bcfg2.Options.CFILE} + setup = Bcfg2.Options.OptionParser(opts) + setup.parse(sys.argv[1:]) + verbose = setup['verbose'] + cpath = setup['configfile'] + prefix = setup['prefix'] + schemadir = "%s/share/bcfg2/schemas" % (prefix) + os.chdir(schemadir) + repo = setup['repo'] + + # Get a list of all info.xml files in the bcfg2 repository + info_list = [] + for infodir in ['Cfg', 'TGenshi', 'TCheetah']: + for root, dirs, files in os.walk('%s/%s' % (repo, infodir)): + for filename in files: + if filename == 'info.xml': + info_list.append(os.path.join(root, filename)) + + # get metadata list (with all included files) + metadata_list = glob.glob("%s/Metadata/groups.xml" % repo) + ref_bundles = set() + xdata = lxml.etree.parse("%s/Metadata/groups.xml" % repo) + included = set([ent.get('href') for ent in \ + xdata.findall('./{http://www.w3.org/2001/XInclude}include')]) + while included: + try: + filename = included.pop() + except KeyError: + continue + metadata_list.append("%s/Metadata/%s" % (repo, filename)) + groupdata = lxml.etree.parse("%s/Metadata/%s" % (repo, filename)) + group_ents = [ent.get('href') for ent in \ + groupdata. + findall('./{http://www.w3.org/2001/XInclude}include')] + for ent in group_ents: + included.add(ent) + included.discard(filename) + + # check for multiple default group definitions + default_groups = [] + for grp in lxml.etree.parse("%s/Metadata/groups.xml" \ + % repo).findall('.//Group'): + if grp.get('default') == 'true': + default_groups.append(grp) + if len(default_groups) > 1: + print("*** Warning: Multiple default groups defined") + for grp in default_groups: + print(" %s" % grp.get('name')) + + # get all XIncluded bundles + xdata.xinclude() + for bundle in xdata.findall("//Bundle"): + ref_bundles.add("%s/Bundler/%s" % (repo, bundle.get('name'))) + + # get lists of all other xml files to validate + clients_list = glob.glob("%s/Metadata/clients.xml" % repo) + bundle_list = glob.glob("%s/Bundler/*.xml" % repo) + genshibundle_list = glob.glob("%s/Bundler/*.genshi" % repo) + pkg_list = glob.glob("%s/Pkgmgr/*.xml" % repo) + base_list = glob.glob("%s/Base/*.xml" % repo) + rules_list = glob.glob("%s/Rules/*.xml" % repo) + imageinfo_list = glob.glob("%s/etc/report-configuration.xml" % repo) + services_list = glob.glob("%s/Svcmgr/*.xml" % repo) + deps_list = glob.glob("%s/Deps/*.xml" % repo) + dec_list = glob.glob("%s/Decisions/*" % repo) + pkgcfg_list = glob.glob("%s/Packages/config.xml" % repo) + gp_list = glob.glob('%s/GroupPatterns/config.xml' % repo) + + # verify attributes for configuration entries + # (as defined in doc/server/configurationentries) + # TODO: See if it is possible to do this in the schema instead + required_configuration_attrs = { + 'device': ['name', 'owner', 'group', 'dev_type'], + 'directory': ['name', 'owner', 'group', 'perms'], + 'file': ['name', 'owner', 'group', 'perms'], + 'hardlink': ['name', 'to'], + 'symlink': ['name', 'to'], + 'ignore': ['name'], + 'nonexist': ['name'], + 'permissions': ['name', 'owner', 'group', 'perms']} + for rfile in rules_list: + try: + xdata = lxml.etree.parse(rfile) + except lxml.etree.XMLSyntaxError, e: + print("Failed to parse %s: %s" % (rfile, e)) + for posixpath in xdata.findall("//Path"): + pathname = posixpath.get('name') + pathtype = posixpath.get('type') + pathset = set(posixpath.attrib.keys()) + try: + required_attrs = set(required_configuration_attrs[pathtype] \ + + ['type']) + except KeyError: + continue + if 'dev_type' in required_attrs: + dev_type = posixpath.get('dev_type') + if dev_type in ['block', 'char']: + # check if major/minor are specified + required_attrs |= set(['major', 'minor']) + if pathset.issuperset(required_attrs): + continue + else: + print("The following required attributes are missing for" + " Path %s in %s: %s" % (pathname, rfile, + [attr for attr in required_attrs.difference(pathset)])) + + # warn on duplicate Pkgmgr entries with the same priority + pset = set() + for plist in pkg_list: + try: + xdata = lxml.etree.parse(plist) + except lxml.etree.XMLSyntaxError, e: + print("Failed to parse %s: %s" % (plist, e)) + # get priority, type, group + priority = xdata.getroot().get('priority') + ptype = xdata.getroot().get('type') + for pkg in xdata.findall("//Package"): + if pkg.getparent().tag == 'Group': + grp = pkg.getparent().get('name') + if type(grp) is not str and grp.getparent().tag == 'Group': + pgrp = grp.getparent().get('name') + else: + pgrp = 'none' + else: + grp = 'none' + pgrp = 'none' + ptuple = (pkg.get('name'), priority, ptype, grp, pgrp) + # check if package is already listed with same priority, + # type, grp + if ptuple in pset: + print("Duplicate Package %s, priority:%s, type:%s"\ + % (pkg.get('name'), priority, ptype)) + else: + pset.add(ptuple) + + filesets = {'metadata': (metadata_list, "%s/metadata.xsd"), + 'clients': (clients_list, "%s/clients.xsd"), + 'info': (info_list, "%s/info.xsd"), + 'bundle': (bundle_list, "%s/bundle.xsd"), + 'pkglist': (pkg_list, "%s/pkglist.xsd"), + 'base': (base_list, "%s/base.xsd"), + 'rules': (rules_list, "%s/rules.xsd"), + 'imageinfo': (imageinfo_list, "%s/report-configuration.xsd"), + 'services': (services_list, "%s/services.xsd"), + 'deps': (deps_list, "%s/deps.xsd"), + 'decisions': (dec_list, "%s/decisions.xsd"), + 'packages': (pkgcfg_list, "%s/packages.xsd"), + 'grouppatterns': (gp_list, "%s/grouppatterns.xsd"), + } + + failures = 0 + for k, (filelist, schemaname) in list(filesets.items()): + try: + schema = lxml.etree.XMLSchema(lxml.etree.parse(open(schemaname%(schemadir)))) + except: + print("Failed to process schema %s" % (schemaname%(schemadir))) + failures = 1 + continue + for filename in filelist: + try: + datafile = lxml.etree.parse(open(filename)) + except SyntaxError: + print("%s ***FAILS*** to parse \t\t<----" % (filename)) + os.system("xmllint %s" % filename) + failures = 1 + continue + except IOError: + print("Failed to open file %s \t\t<---" % (filename)) + failures = 1 + continue + if schema.validate(datafile): + if verbose: + print("%s checks out" % (filename)) + else: + rc = os.system("xmllint --noout --xinclude --schema \ + %s %s > /dev/null 2>/dev/null" % \ + (schemaname % schemadir, filename)) + if rc: + failures = 1 + print("%s ***FAILS*** to verify \t\t<----" % (filename)) + os.system("xmllint --noout --xinclude --schema %s %s" % \ + (schemaname % schemadir, filename)) + elif verbose: + print("%s checks out" % (filename)) + + # print out missing bundle information + if verbose: + print("") + for bundle in ref_bundles: + # check for both regular and genshi bundles + xmlbundle = "%s.xml" % bundle + genshibundle = "%s.genshi" % bundle + allbundles = bundle_list + genshibundle_list + if xmlbundle not in allbundles and \ + genshibundle not in allbundles: + print("*** Warning: Bundle %s referenced, but does not " + "exist." % bundle) + # verify bundle name attribute matches filename + for bundle in (bundle_list + genshibundle_list): + fname = bundle.split('Bundler/')[1].split('.')[0] + xdata = lxml.etree.parse(bundle) + bname = xdata.getroot().get('name') + if fname != bname: + print("The following names are inconsistent:") + print(" Filename is %s" % fname) + print(" Bundle name found in %s is %s" % (fname, bname)) + + + raise SystemExit, failures diff --git a/build/scripts-2.7/bcfg2-reports b/build/scripts-2.7/bcfg2-reports new file mode 100755 index 000000000..85338b0d8 --- /dev/null +++ b/build/scripts-2.7/bcfg2-reports @@ -0,0 +1,282 @@ +#!/usr/bin/python +"""Query reporting system for client status.""" +__revision__ = '$Revision$' + +import os +import sys + +import Bcfg2.Server.Reports.settings + +project_directory = os.path.dirname(Bcfg2.Server.Reports.settings.__file__) +project_name = os.path.basename(project_directory) +sys.path.append(os.path.join(project_directory, '..')) +project_module = __import__(project_name, '', '', ['']) +sys.path.pop() +# Set DJANGO_SETTINGS_MODULE appropriately. +os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name + +from Bcfg2.Server.Reports.reports.models import Client +from getopt import getopt +import datetime +import fileinput + +def timecompare(client1, client2): + """Compares two clients by their timestamps.""" + return cmp(client1.current_interaction.timestamp, \ + client2.current_interaction.timestamp) + +def namecompare(client1, client2): + """Compares two clients by their names.""" + return cmp(client1.name, client2.name) + +def statecompare(client1, client2): + """Compares two clients by their states.""" + clean1 = client1.current_interaction.isclean() + clean2 = client2.current_interaction.isclean() + + if clean1 and not clean2: + return -1 + elif clean2 and not clean1: + return 1 + else: + return 0 + +def crit_compare(criterion, client1, client2): + """Compares two clients by the criteria provided in criterion.""" + for crit in criterion: + comp = 0 + if crit == 'name': + comp = namecompare(client1, client2) + elif crit == 'state': + comp = statecompare(client1, client2) + elif crit == 'time': + comp = timecompare(client1, client2) + + if comp != 0: + return comp + + return 0 + +def print_fields(fields, cli, max_name, entrydict): + """ + Prints the fields specified in fields of cli, max_name + specifies the column width of the name column. + """ + fmt = '' + for field in fields: + if field == 'name': + fmt += ("%%-%ds " % (max_name)) + else: + fmt += "%s " + fdata = [] + for field in fields: + if field == 'time': + fdata.append(str(cli.current_interaction.timestamp)) + elif field == 'state': + if cli.current_interaction.isclean(): + fdata.append("clean") + else: + fdata.append("dirty") + else: + try: + fdata.append(getattr(cli, field)) + except: + fdata.append("N/A") + + display = fmt % tuple(fdata) + if len(entrydict) > 0: + display += " " + display += str(entrydict[cli]) + print display + +def print_entry(item, max_name): + fmt = ("%%-%ds " % (max_name)) + fdata = item.entry.kind + ":" + item.entry.name + display = fmt % (fdata) + print display + +fields = "" +sort = "" +badentry = "" +extraentry = "" +expire = "" +singlehost = "" + +c_list = Client.objects.all() + +result = list() +entrydict = dict() + +args = sys.argv[1:] +opts, pargs = getopt(args, 'ab:cde:hs:x:', + ['stale', 'sort=', 'fields=', 'badentry=', 'extraentry=']) + +for option in opts: + if len(option) > 0: + if option[0] == '--fields': + fields = option[1] + if option[0] == '--sort': + sort = option[1] + if option[0] == '--badentry': + badentry = option[1] + if option[0] == '--extraentry': + extraentry = option[1] + if option[0] == '-x': + expire = option[1] + if option[0] == '-s' or option[0] == '-b' or option[0] == '-e': + singlehost = option[1] + +if expire != "": + for c_inst in c_list: + if expire == c_inst.name: + if c_inst.expiration == None: + c_inst.expiration = datetime.datetime.now() + print "Host expired." + else: + c_inst.expiration = None + print "Host un-expired." + c_inst.save() + +elif '-h' in args: + print """Usage: python bcfg2-reports [option] ... + +Options and arguments (and corresponding environment variables): +-a : shows all hosts, including expired hosts +-b NAME : single-host mode - shows bad entries from the + current interaction of NAME +-c : shows only clean hosts +-d : shows only dirty hosts +-e NAME : single-host mode - shows extra entries from the + current interaction of NAME +-h : shows help and usage info about bcfg2-reports +-s NAME : single-host mode - shows bad and extra entries from + the current interaction of NAME +-x NAME : toggles expired/unexpired state of NAME +--badentry=KIND,NAME : shows only hosts whose current interaction has bad + entries in of KIND kind and NAME name; if a single + argument ARG1 is given, then KIND,NAME pairs will be + read from a file of name ARG1 +--extraentry=KIND,NAME : shows only hosts whose current interaction has extra + entries in of KIND kind and NAME name; if a single + argument ARG1 is given, then KIND,NAME pairs will be + read from a file of name ARG1 +--fields=ARG1,ARG2,... : only displays the fields ARG1,ARG2,... + (name,time,state) +--sort=ARG1,ARG2,... : sorts output on ARG1,ARG2,... (name,time,state) +--stale : shows hosts which haven't run in the last 24 hours +""" +elif singlehost != "": + for c_inst in c_list: + if singlehost == c_inst.name: + baditems = c_inst.current_interaction.bad() + if len(baditems) > 0 and ('-b' in args or '-s' in args): + print "Bad Entries:" + max_name = -1 + for item in baditems: + if len(item.entry.name) > max_name: + max_name = len(item.entry.name) + for item in baditems: + print_entry(item, max_name) + extraitems = c_inst.current_interaction.extra() + if len(extraitems) > 0 and ('-e' in args or '-s' in args): + print "Extra Entries:" + max_name = -1 + for item in extraitems: + if len(item.entry.name) > max_name: + max_name = len(item.entry.name) + for item in extraitems: + print_entry(item, max_name) + + +else: + if fields == "": + fields = ['name', 'time', 'state'] + else: + fields = fields.split(',') + + if sort != "": + sort = sort.split(',') + + if badentry != "": + badentry = badentry.split(',') + + if extraentry != "": + extraentry = extraentry.split(',') + + # stale hosts + if '--stale' in args: + for c_inst in c_list: + if c_inst.current_interaction.isstale(): + result.append(c_inst) + # clean hosts + elif '-c' in args: + for c_inst in c_list: + if c_inst.current_interaction.isclean(): + result.append(c_inst) + # dirty hosts + elif '-d' in args: + for c_inst in c_list: + if not c_inst.current_interaction.isclean(): + result.append(c_inst) + + elif badentry != "": + if len(badentry) == 1: + fileread = fileinput.input(badentry[0]) + for line in fileread: + badentry = line.strip().split(',') + for c_inst in c_list: + baditems = c_inst.current_interaction.bad() + for item in baditems: + if item.name == badentry[1] and item.kind == badentry[0]: + result.append(c_inst) + if c_inst in entrydict: + entrydict.get(c_inst).append(badentry[1]) + else: + entrydict[c_inst] = [badentry[1]] + break + else: + for c_inst in c_list: + baditems = c_inst.current_interaction.bad() + for item in baditems: + if item.name == badentry[1] and item.kind == badentry[0]: + result.append(c_inst) + break + elif extraentry != "": + if len(extraentry) == 1: + fileread = fileinput.input(extraentry[0]) + for line in fileread: + extraentry = line.strip().split(',') + for c_inst in c_list: + extraitems = c_inst.current_interaction.extra() + for item in extraitems: + if item.name == extraentry[1] and item.kind == extraentry[0]: + result.append(c_inst) + if c_inst in entrydict: + entrydict.get(c_inst).append(extraentry[1]) + else: + entrydict[c_inst] = [extraentry[1]] + break + else: + for c_inst in c_list: + extraitems = c_inst.current_interaction.extra() + for item in extraitems: + if item.name == extraentry[1] and item.kind == extraentry[0]: + result.append(c_inst) + break + + else: + for c_inst in c_list: + result.append(c_inst) + max_name = -1 + if 'name' in fields: + for c_inst in result: + if len(c_inst.name) > max_name: + max_name = len(c_inst.name) + + if sort != "": + result.sort(lambda x, y: crit_compare(sort, x, y)) + + if fields != "": + for c_inst in result: + if '-a' in args or c_inst.expiration == None: + print_fields(fields, c_inst, max_name, entrydict) diff --git a/build/scripts-2.7/bcfg2-server b/build/scripts-2.7/bcfg2-server new file mode 100755 index 000000000..115708bf9 --- /dev/null +++ b/build/scripts-2.7/bcfg2-server @@ -0,0 +1,79 @@ +#!/usr/bin/python + +"""The XML-RPC Bcfg2 server.""" +__revision__ = '$Revision$' + +import logging +import os.path +import sys + +import Bcfg2.Logger +import Bcfg2.Options +import Bcfg2.Component +import Bcfg2.Server.Plugins.Metadata +from Bcfg2.Server.Core import CoreInitError + +logger = logging.getLogger('bcfg2-server') + +if __name__ == '__main__': + + OPTINFO = { + 'configfile': Bcfg2.Options.CFILE, + 'daemon' : Bcfg2.Options.DAEMON, + 'debug' : Bcfg2.Options.DEBUG, + 'help' : Bcfg2.Options.HELP, + 'verbose' : Bcfg2.Options.VERBOSE, + 'to_file' : Bcfg2.Options.LOGGING_FILE_PATH, + } + + OPTINFO.update({'repo' : Bcfg2.Options.SERVER_REPOSITORY, + 'plugins' : Bcfg2.Options.SERVER_PLUGINS, + 'password' : Bcfg2.Options.SERVER_PASSWORD, + 'fm' : Bcfg2.Options.SERVER_FILEMONITOR, + }) + + OPTINFO.update({'key' : Bcfg2.Options.SERVER_KEY, + 'cert' : Bcfg2.Options.SERVER_CERT, + 'ca' : Bcfg2.Options.SERVER_CA, + 'location' : Bcfg2.Options.SERVER_LOCATION, + 'passwd' : Bcfg2.Options.SERVER_PASSWORD, + 'static' : Bcfg2.Options.SERVER_STATIC, + 'encoding' : Bcfg2.Options.ENCODING, + 'filelog' : Bcfg2.Options.LOGGING_FILE_PATH, + 'protocol' : Bcfg2.Options.SERVER_PROTOCOL, + }) + + setup = Bcfg2.Options.OptionParser(OPTINFO) + setup.parse(sys.argv[1:]) + try: + # check whether the specified bcfg2.conf exists + if not os.path.exists(setup['configfile']): + print("Could not read %s" % setup['configfile']) + sys.exit(1) + Bcfg2.Component.run_component(Bcfg2.Server.Core.Core, + location=setup['location'], + daemon = setup['daemon'], + pidfile_name = setup['daemon'], + protocol = setup['protocol'], + to_file=setup['to_file'], + cfile=setup['configfile'], + register=False, + cls_kwargs={'repo':setup['repo'], + 'plugins':setup['plugins'], + 'password':setup['password'], + 'encoding':setup['encoding'], + 'ca':setup['ca'], + 'filemonitor':setup['fm'], + 'start_fam_thread':True}, + keyfile=setup['key'], + certfile=setup['cert'], + ca=setup['ca'], + ) + except CoreInitError, msg: + logger.error(msg) + logger.error("exiting") + sys.exit(1) + except KeyboardInterrupt: + sys.exit(1) + sys.exit(0) + |