From 3610288cbcbf4d1adedefa03166cd77ee15aad96 Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Sat, 29 Dec 2007 04:57:53 +0000 Subject: Refactor of bcfg2-admin (all modes moved to discrete modules in Bcfg2.Server.Admin git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@4125 ce84e21b-d406-0410-9b95-82705330c041 --- src/lib/Server/Admin/Client.py | 46 +++++++++++ src/lib/Server/Admin/Client.pyc | Bin 0 -> 2173 bytes src/lib/Server/Admin/Compare.py | 131 ++++++++++++++++++++++++++++++++ src/lib/Server/Admin/Compare.pyc | Bin 0 -> 4806 bytes src/lib/Server/Admin/Fingerprint.py | 19 +++++ src/lib/Server/Admin/Fingerprint.pyc | Bin 0 -> 1298 bytes src/lib/Server/Admin/Init.py | 119 +++++++++++++++++++++++++++++ src/lib/Server/Admin/Init.pyc | Bin 0 -> 4346 bytes src/lib/Server/Admin/Minestruct.py | 25 ++++++ src/lib/Server/Admin/Minestruct.pyc | Bin 0 -> 1686 bytes src/lib/Server/Admin/Pull.py | 99 ++++++++++++++++++++++++ src/lib/Server/Admin/Pull.pyc | Bin 0 -> 4122 bytes src/lib/Server/Admin/Tidy.py | 52 +++++++++++++ src/lib/Server/Admin/Tidy.pyc | Bin 0 -> 2067 bytes src/lib/Server/Admin/Viz.py | 143 +++++++++++++++++++++++++++++++++++ src/lib/Server/Admin/Viz.pyc | Bin 0 -> 5610 bytes src/lib/Server/Admin/__init__.py | 45 +++++++++++ src/lib/Server/Admin/__init__.pyc | Bin 0 -> 2485 bytes src/lib/Server/__init__.py | 3 +- 19 files changed, 681 insertions(+), 1 deletion(-) create mode 100644 src/lib/Server/Admin/Client.py create mode 100644 src/lib/Server/Admin/Client.pyc create mode 100644 src/lib/Server/Admin/Compare.py create mode 100644 src/lib/Server/Admin/Compare.pyc create mode 100644 src/lib/Server/Admin/Fingerprint.py create mode 100644 src/lib/Server/Admin/Fingerprint.pyc create mode 100644 src/lib/Server/Admin/Init.py create mode 100644 src/lib/Server/Admin/Init.pyc create mode 100644 src/lib/Server/Admin/Minestruct.py create mode 100644 src/lib/Server/Admin/Minestruct.pyc create mode 100644 src/lib/Server/Admin/Pull.py create mode 100644 src/lib/Server/Admin/Pull.pyc create mode 100644 src/lib/Server/Admin/Tidy.py create mode 100644 src/lib/Server/Admin/Tidy.pyc create mode 100644 src/lib/Server/Admin/Viz.py create mode 100644 src/lib/Server/Admin/Viz.pyc create mode 100644 src/lib/Server/Admin/__init__.py create mode 100644 src/lib/Server/Admin/__init__.pyc (limited to 'src/lib/Server') diff --git a/src/lib/Server/Admin/Client.py b/src/lib/Server/Admin/Client.py new file mode 100644 index 000000000..677e38c2c --- /dev/null +++ b/src/lib/Server/Admin/Client.py @@ -0,0 +1,46 @@ +import lxml.etree + +import Bcfg2.Server.Admin + +class Client(Bcfg2.Server.Admin.Mode): + __shorthelp__ = 'bcfg2-admin client add attr1=val1 attr2=val2\nbcfg2-admin client del ' + __longhelp__ = __shorthelp__ + '\n\tCreate or delete client entries' + def __init__(self, configfile): + Bcfg2.Server.Admin.Mode.__init__(self, configfile) + self.tree = lxml.etree.parse(self.get_repo_path() + \ + '/Metadata/clients.xml') + self.root = self.tree.getroot() + + def __call__(self, args): + Bcfg2.Server.Admin.Mode.__call__(self, args) + repopath = self.get_repo_path() + if args[0] == 'add': + attr_d = {} + for i in args[1:]: + attr, val = i.split('=', 1) + if attr not in ['profile', 'uuid', 'password', 'address', + 'secure', 'location']: + print "Attribute %s unknown" % attr + raise SystemExit(1) + attr_d[attr] = val + self.AddClient(args[1], attr_d) + elif args[0] in ['delete', 'remove', 'del', 'rm']: + self.DelClient(args[1]) + else: + print "No command specified" + raise SystemExit(1) + self.tree.write(repopath + '/Metadata/clients.xml') + + def AddClient(self, client, attrs): + '''add a new client''' + # FIXME add a dup client check + element = lxml.etree.Element("Client", name=client) + for key, val in attrs.iteritems(): + element.set(key, val) + self.root.append(element) + + def DelClient(self, client): + '''delete an existing client''' + # FIXME DelClient not implemented + pass + diff --git a/src/lib/Server/Admin/Client.pyc b/src/lib/Server/Admin/Client.pyc new file mode 100644 index 000000000..262b04f4b Binary files /dev/null and b/src/lib/Server/Admin/Client.pyc differ diff --git a/src/lib/Server/Admin/Compare.py b/src/lib/Server/Admin/Compare.py new file mode 100644 index 000000000..962e54606 --- /dev/null +++ b/src/lib/Server/Admin/Compare.py @@ -0,0 +1,131 @@ + +import lxml.etree, os +import Bcfg2.Server.Admin + +class Compare(Bcfg2.Server.Admin.Mode): + __shorthelp__ = "bcfg2-admin compare \nbcfg2-admin compare -r " + __longhelp__ = __shorthelp__ + '''\n\tCompare mode determines differences between directories of client specification instances''' + + def __init__(self, configfile): + Bcfg2.Server.Admin.Mode.__init__(self, configfile) + self.important = {'Package':['name', 'version'], + 'Service':['name', 'status'], + 'Directory':['name', 'owner', 'group', 'perms'], + 'SymLink':['name', 'to'], + 'ConfigFile':['name', 'owner', 'group', 'perms'], + 'Permissions':['name', 'perms'], + 'PostInstall':['name']} + + def compareStructures(self, new, old): + for child in new.getchildren(): + equiv = old.xpath('%s[@name="%s"]' % (child.tag, child.get('name'))) + if child.tag in self.important: + print "tag type %s not handled" % (child.tag) + continue + if len(equiv) == 0: + print "didn't find matching %s %s" % (child.tag, child.get('name')) + continue + elif len(equiv) >= 1: + if child.tag == 'ConfigFile': + if child.text != equiv[0].text: + print " %s %s contents differ" \ + % (child.tag, child.get('name')) + continue + noattrmatch = [field for field in self.important[child.tag] if \ + child.get(field) != equiv[0].get(field)] + if not noattrmatch: + new.remove(child) + old.remove(equiv[0]) + else: + print " %s %s attributes %s do not match" % \ + (child.tag, child.get('name'), noattrmatch) + if len(old.getchildren()) == 0 and len(new.getchildren()) == 0: + return True + if new.tag == 'Independant': + name = 'Base' + else: + name = new.get('name') + both = [] + oldl = ["%s %s" % (entry.tag, entry.get('name')) for entry in old] + newl = ["%s %s" % (entry.tag, entry.get('name')) for entry in new] + for entry in newl: + if entry in oldl: + both.append(entry) + newl.remove(entry) + oldl.remove(entry) + for entry in both: + print " %s differs (in bundle %s)" % (entry, name) + for entry in oldl: + print " %s only in old configuration (in bundle %s)" % (entry, name) + for entry in newl: + print " %s only in new configuration (in bundle %s)" % (entry, name) + return False + + def compareSpecifications(self, path1, path2): + try: + new = lxml.etree.parse(path1).getroot() + except IOError: + print "Failed to read %s" % (path1) + raise SystemExit(1) + + try: + old = lxml.etree.parse(path2).getroot() + except IOError: + print "Failed to read %s" % (path2) + raise SystemExit(1) + + for src in [new, old]: + for bundle in src.findall('./Bundle'): + if bundle.get('name')[-4:] == '.xml': + bundle.set('name', bundle.get('name')[:-4]) + + rcs = [] + for bundle in new.findall('./Bundle'): + equiv = old.xpath('Bundle[@name="%s"]' % (bundle.get('name'))) + if len(equiv) == 0: + print "couldnt find matching bundle for %s" % bundle.get('name') + continue + if len(equiv) == 1: + if self.compareStructures(bundle, equiv[0]): + new.remove(bundle) + old.remove(equiv[0]) + rcs.append(True) + else: + rcs.append(False) + else: + print "Unmatched bundle %s" % (bundle.get('name')) + rcs.append(False) + i1 = new.find('./Independant') + i2 = old.find('./Independant') + if self.compareStructures(i1, i2): + new.remove(i1) + old.remove(i2) + else: + rcs.append(False) + return False not in rcs + + def __call__(self, args): + Bcfg2.Server.Admin.Mode.__call__(self, args) + if '-r' in args: + args = list(args) + args.remove('-r') + (oldd, newd) = args + (old, new) = [os.listdir(spot) for spot in args] + for item in old: + print "Entry:", item + state = self.__call__([oldd + '/' + item, newd + '/' + item]) + new.remove(item) + if state: + print "Entry:", item, "good" + else: + print "Entry:", item, "bad" + if new: + print "new has extra entries", new + return + try: + (old, new) = args + except IndexError: + print self.__call__.__doc__ + raise SystemExit + + diff --git a/src/lib/Server/Admin/Compare.pyc b/src/lib/Server/Admin/Compare.pyc new file mode 100644 index 000000000..e3c38f3cb Binary files /dev/null and b/src/lib/Server/Admin/Compare.pyc differ diff --git a/src/lib/Server/Admin/Fingerprint.py b/src/lib/Server/Admin/Fingerprint.py new file mode 100644 index 000000000..bf50f0aaa --- /dev/null +++ b/src/lib/Server/Admin/Fingerprint.py @@ -0,0 +1,19 @@ +'''Fingerprint mode for bcfg2-admin''' + +import Bcfg2.tlslite.api +import Bcfg2.Server.Admin + +class Fingerprint(Bcfg2.Server.Admin.Mode): + '''Produce server key fingerprint''' + __shorthelp__ = 'bcfg2-admin fingerprint' + __longhelp__ = __shorthelp__ + '\n\tPrint the server certificate fingerprint' + def __call__(self, args): + Bcfg2.Server.Admin.Mode.__call__(self, args) + print self.getFingerprint() + + def getFingerprint(self): + '''calculate key fingerprint''' + keypath = self.cfp.get('communication', 'key') + x509 = Bcfg2.tlslite.api.X509() + x509.parse(open(keypath).read()) + return x509.getFingerprint() diff --git a/src/lib/Server/Admin/Fingerprint.pyc b/src/lib/Server/Admin/Fingerprint.pyc new file mode 100644 index 000000000..7bbe66d00 Binary files /dev/null and b/src/lib/Server/Admin/Fingerprint.pyc differ diff --git a/src/lib/Server/Admin/Init.py b/src/lib/Server/Admin/Init.py new file mode 100644 index 000000000..01bede88e --- /dev/null +++ b/src/lib/Server/Admin/Init.py @@ -0,0 +1,119 @@ +import os, socket +import Bcfg2.Server.Admin + +config = ''' +[server] +repository = %s +structures = Bundler,Base +generators = SSHbase,Cfg,Pkgmgr,Rules + +[statistics] +sendmailpath = /usr/sbin/sendmail +database_engine = sqlite3 +# 'postgresql', 'mysql', 'mysql_old', 'sqlite3' or 'ado_mssql'. +database_name = +# Or path to database file if using sqlite3. +#/etc/brpt.sqlite is default path if left empty +database_user = +# Not used with sqlite3. +database_password = +# Not used with sqlite3. +database_host = +# Not used with sqlite3. +database_port = +# Set to empty string for default. Not used with sqlite3. +web_debug = True + + +[communication] +protocol = xmlrpc/ssl +password = %s +key = %s/bcfg2.key + +[components] +bcfg2 = %s +''' + +groups = ''' + + + + + + + + + + + + + +''' +clients = ''' + + + +''' + +os_list = [('Redhat/Fedora/RHEL/RHAS/Centos', 'redhat'), + ('SUSE/SLES', 'suse'), + ('Mandrake', 'mandrake'), + ('Debian', 'debian'), + ('Ubuntu', 'ubuntu'), + ('Gentoo', 'gentoo'), + ('FreeBSD', 'freebsd')] + + +class Init(Bcfg2.Server.Admin.Mode): + __shorthelp__ = 'bcfg2-admin init' + __longhelp__ = __shorthelp__ + '\n\tCompare two client specifications or directories of specifications' + def __call__(self, args): + Bcfg2.Server.Admin.Mode.__call__(self, args) + repopath = raw_input( "location of bcfg2 repository [/var/lib/bcfg2]: " ) + if repopath == '': + repopath = '/var/lib/bcfg2' + password = '' + while ( password == '' ): + password = raw_input( + "Input password used for communication verification: " ) + server = "https://%s:6789" % socket.getfqdn() + rs = raw_input( "Input the server location [%s]: " % server) + if rs: + server = rs + #create the groups.xml file + prompt = '''Input base Operating System for clients:\n''' + for entry in os_list: + prompt += "%d: \n" % (os_list.index(entry) + 1, entry[0]) + prompt += ': ' + os_sel = os_list[int(raw_input(prompt))-1][1] + self.initializeRepo(repopath, server, password, os_sel) + print "Repository created successfuly in %s" % (repopath) + + def initializeRepo(self, repo, server_uri, password, os_selection): + '''Setup a new repo''' + keypath = os.path.dirname(os.path.abspath(self.configfile)) + confdata = config % ( repo, password, keypath, server_uri ) + open(self.configfile,"w").write(confdata) + # FIXME automate ssl key generation + os.popen('openssl req -x509 -nodes -days 1000 -newkey rsa:1024 -out %s/bcfg2.key -keyout %s/bcfg2.key' % (keypath, keypath)) + try: + os.chmod('%s/bcfg2.key'% keypath,'0600') + except: + pass + + for subdir in ['SSHbase', 'Cfg', 'Pkgmgr', 'Rules', 'etc', 'Metadata', + 'Base', 'Bundler']: + path = "%s/%s" % (repo, subdir) + newpath = '' + for subdir in path.split('/'): + newpath = newpath + subdir + '/' + try: + os.mkdir(newpath) + except: + continue + + open("%s/Metadata/groups.xml"%repo, "w").write(groups % os_selection) + #now the clients file + open("%s/Metadata/clients.xml"%repo, "w").write(clients % socket.getfqdn()) + + diff --git a/src/lib/Server/Admin/Init.pyc b/src/lib/Server/Admin/Init.pyc new file mode 100644 index 000000000..2cbb030c7 Binary files /dev/null and b/src/lib/Server/Admin/Init.pyc differ diff --git a/src/lib/Server/Admin/Minestruct.py b/src/lib/Server/Admin/Minestruct.py new file mode 100644 index 000000000..8f8be7e77 --- /dev/null +++ b/src/lib/Server/Admin/Minestruct.py @@ -0,0 +1,25 @@ +import Bcfg2.Server.Admin + +class Minestruct(Bcfg2.Server.Admin.Mode): + '''Pull extra entries out of statistics''' + __shorthelp__ = 'bcfg2-admin minestruct ' + __longhelp__ = __shorthelp__ + '\n\tExtract extra entry lists from statistics' + def __call__(self, args): + Bcfg2.Server.Admin.Mode.__call__(self, args) + if len(args) != 1: + self.errExit("minestruct must be called with a client name") + extra = self.MineStruct(args[1]) + self.log.info("Found %d extra entries" % (len(extra))) + self.log.info(["%s: %s" % (entry.tag, entry.get('name')) for entry in extra]) + + def MineStruct(self, client): + '''Pull client entries into structure''' + stats = self.load_stats(client) + if len(stats.getchildren()) == 2: + # FIXME this is busted + # client is dirty + current = [ent for ent in stats.getchildren() if ent.get('state') == 'dirty'][0] + else: + current = stats.getchildren()[0] + return current.find('Extra').getchildren() + diff --git a/src/lib/Server/Admin/Minestruct.pyc b/src/lib/Server/Admin/Minestruct.pyc new file mode 100644 index 000000000..6270f1ed2 Binary files /dev/null and b/src/lib/Server/Admin/Minestruct.pyc differ diff --git a/src/lib/Server/Admin/Pull.py b/src/lib/Server/Admin/Pull.py new file mode 100644 index 000000000..ab334dbf9 --- /dev/null +++ b/src/lib/Server/Admin/Pull.py @@ -0,0 +1,99 @@ + +import binascii, lxml.etree, time +import Bcfg2.Server.Admin + +class Pull(Bcfg2.Server.Admin.Mode): + '''Pull mode retrieves entries from clients and integrates the information into the repository''' + __shorthelp__ = 'bcfg2-admin pull ' + __longhelp__ = __shorthelp__ + '\n\tIntegrate configuration information from clients into the server repository' + def __init__(self, configfile): + Bcfg2.Server.Admin.Mode.__init__(self, configfile) + + def __call__(self, args): + Bcfg2.Server.Admin.Mode.__call__(self, args) + self.PullEntry(args[0], args[1], args[2]) + + def PullEntry(self, client, etype, ename): + '''Make currently recorded client state correct for entry''' + # FIXME Pull.py is _way_ too interactive + sdata = self.load_stats(client) + if sdata.xpath('.//Statistics[@state="dirty"]'): + state = 'dirty' + else: + state = 'clean' + # need to pull entry out of statistics + sxpath = ".//Statistics[@state='%s']/Bad/ConfigFile[@name='%s']/../.." % (state, ename) + sentries = sdata.xpath(sxpath) + if not len(sentries): + self.errExit("Found %d entries for %s:%s:%s" % \ + (len(sentries), client, etype, ename)) + else: + print "Found %d entries for %s:%s:%s" % \ + (len(sentries), client, etype, ename) + maxtime = max([time.strptime(stat.get('time')) for stat in sentries]) + print "Found entry from", time.strftime("%c", maxtime) + statblock = [stat for stat in sentries \ + if time.strptime(stat.get('time')) == maxtime] + entry = statblock[0].xpath('.//Bad/ConfigFile[@name="%s"]' % ename) + if not entry: + self.errExit("Could not find state data for entry; rerun bcfg2 on client system") + cfentry = entry[-1] + + badfields = [field for field in ['perms', 'owner', 'group'] \ + if cfentry.get(field) != cfentry.get('current_' + field) and \ + cfentry.get('current_' + field)] + if badfields: + m_updates = dict([(field, cfentry.get('current_' + field)) \ + for field in badfields]) + print "got metadata_updates", m_updates + else: + m_updates = {} + + if 'current_bdiff' in cfentry.attrib: + data = False + diff = binascii.a2b_base64(cfentry.get('current_bdiff')) + elif 'current_diff' in cfentry.attrib: + data = False + diff = cfentry.get('current_diff') + elif 'current_bfile' in cfentry.attrib: + data = binascii.a2b_base64(cfentry.get('current_bfile')) + diff = False + else: + if not m_updates: + self.errExit("having trouble processing entry. Entry is:\n" \ + + lxml.etree.tostring(cfentry)) + else: + data = False + diff = False + + if diff: + print "Located diff:\n %s" % diff + elif data: + print "Found full (binary) file data" + if m_updates: + print "Found metadata updates" + + if not diff and not data and not m_updates: + self.errExit("Failed to locate diff or full data or metadata updates\nStatistics entry was:\n%s" % lxml.etree.tostring(cfentry)) + + try: + bcore = Bcfg2.Server.Core.Core({}, self.configfile) + except Bcfg2.Server.Core.CoreInitError, msg: + self.errExit("Core load failed because %s" % msg) + [bcore.fam.Service() for _ in range(10)] + while bcore.fam.Service(): + pass + m = bcore.metadata.get_metadata(client) + # find appropriate plugin in bcore + glist = [gen for gen in bcore.generators if + gen.Entries.get(etype, {}).has_key(ename)] + if len(glist) != 1: + self.errExit("Got wrong numbers of matching generators for entry:" \ + + "%s" % ([g.__name__ for g in glist])) + plugin = glist[0] + try: + plugin.AcceptEntry(m, 'ConfigFile', ename, diff, data, m_updates) + except Bcfg2.Server.Plugin.PluginExecutionError: + self.errExit("Configuration upload not supported by plugin %s" \ + % (plugin.__name__)) + # svn commit if running under svn diff --git a/src/lib/Server/Admin/Pull.pyc b/src/lib/Server/Admin/Pull.pyc new file mode 100644 index 000000000..4b76524cc Binary files /dev/null and b/src/lib/Server/Admin/Pull.pyc differ diff --git a/src/lib/Server/Admin/Tidy.py b/src/lib/Server/Admin/Tidy.py new file mode 100644 index 000000000..80307df80 --- /dev/null +++ b/src/lib/Server/Admin/Tidy.py @@ -0,0 +1,52 @@ +import Bcfg2.Server.Admin +import re, os, socket + +class Tidy(Bcfg2.Server.Admin.Mode): + __shorthelp__ = 'bcfg2-admin tidy [-f] [-I]' + __longhelp__ = __shorthelp__ + '\n\tClean up useless files in the repo' + def __call__(self, args): + Bcfg2.Server.Admin.Mode.__call__(self, args) + badfiles = self.buildTidyList() + if '-f' in args or '-I' in args: + if '-I' in args: + for name in badfiles[:]: + answer = raw_input("Unlink file %s? [yN] " % name) + if answer not in ['y', 'Y']: + badfiles.remove(name) + for name in badfiles: + try: + os.unlink(name) + except IOError: + print "Failed to unlink %s" % name + else: + for name in badfiles: + print name + + def buildTidyList(self): + '''Clean up unused or unusable files from the repository''' + hostmatcher = re.compile('.*\.H_(\S+)$') + repo = self.get_repo_path() + to_remove = [] + good = [] + bad = [] + + # clean up unresolvable hosts in SSHbase + for name in os.listdir("%s/SSHbase" % (repo)): + if hostmatcher.match(name): + hostname = hostmatcher.match(name).group(1) + if hostname in good + bad: + continue + try: + socket.gethostbyname(hostname) + good.append(hostname) + except: + bad.append(hostname) + for name in os.listdir("%s/SSHbase" % (repo)): + if not hostmatcher.match(name): + to_remove.append("%s/SSHbase/%s" % (repo, name)) + else: + if hostmatcher.match(name).group(1) in bad: + to_remove.append("%s/SSHbase/%s" % (repo, name)) + # clean up file~ + # clean up files without parsable names in Cfg + return to_remove diff --git a/src/lib/Server/Admin/Tidy.pyc b/src/lib/Server/Admin/Tidy.pyc new file mode 100644 index 000000000..96cf089bd Binary files /dev/null and b/src/lib/Server/Admin/Tidy.pyc differ diff --git a/src/lib/Server/Admin/Viz.py b/src/lib/Server/Admin/Viz.py new file mode 100644 index 000000000..bc2d8665c --- /dev/null +++ b/src/lib/Server/Admin/Viz.py @@ -0,0 +1,143 @@ + +import getopt, popen2, lxml.etree +import Bcfg2.Server.Admin + +class Viz(Bcfg2.Server.Admin.Mode): + __shorthelp__ = '''bcfg2-admin viz [--includehosts] [--includebundles] [--includekey] [-o output.png] [--raw]''' + __longhelp__ = __shorthelp__ + '\n\tProduce graphviz diagrams of metadata structures' + def __init__(self, configfile): + Bcfg2.Server.Admin.Mode.__init__(self, configfile) + self.colors = ['steelblue1', 'chartreuse', 'gold', 'magenta', + 'indianred1', 'limegreen', 'orange1', 'lightblue2', + 'green1', 'blue1', 'yellow1', 'darkturquoise', 'gray66'] + + def __call__(self, args): + Bcfg2.Server.Admin.Mode.__call__(self, args) + # First get options to the 'viz' subcommand + try: + opts, args = getopt.getopt(args, 'rhbko:', + ['raw', 'includehosts', 'includebundles', + 'includekey', 'outfile=']) + except getopt.GetoptError, msg: + print msg + raise SystemExit(1) + + rset = False + hset = False + bset = False + kset = False + outputfile = False + for opt, arg in opts: + if opt in ("-r", "--raw"): + rset = True + elif opt in ("-h", "--includehosts"): + hset = True + elif opt in ("-b", "--includebundles"): + bset = True + elif opt in ("-k", "--includekey"): + kset = True + elif opt in ("-o", "--outfile"): + outputfile = arg + repopath = self.get_repo_path() + + data = self.Visualize(repopath, rset, hset, bset, kset) + if outputfile: + open(outputfile, 'w').write(data) + else: + print data + + def Visualize(self, repopath, raw=False, hosts=False, + bundles=False, key=False): + '''Build visualization of groups file''' + groupdata = lxml.etree.parse(repopath + '/Metadata/groups.xml') + groupdata.xinclude() + groups = groupdata.getroot() + if raw: + dotpipe = popen2.Popen4("dd bs=4M 2>/dev/null") + else: + dotpipe = popen2.Popen4("dot -Tpng") + categories = {'default':'grey83'} + instances = {} + egroups = groups.findall("Group") + groups.findall('.//Groups/Group') + for group in egroups: + if group.get('category', False): + if not categories.has_key(group.get('category')): + categories[group.get('category')] = self.colors.pop() + + try: + dotpipe.tochild.write("digraph groups {\n") + except: + print "write to dot process failed. Is graphviz installed?" + raise SystemExit(1) + dotpipe.tochild.write('\trankdir="LR";\n') + + if hosts: + clients = lxml.etree.parse(repopath + \ + '/Metadata/clients.xml').getroot() + for client in clients.findall('Client'): + if instances.has_key(client.get('profile')): + instances[client.get('profile')].append(client.get('name')) + else: + instances[client.get('profile')] = [client.get('name')] + for profile, clist in instances.iteritems(): + clist.sort() + dotpipe.tochild.write( + '''\t"%s-instances" [ label="%s", shape="record" ];\n''' \ + % (profile, '|'.join(clist))) + dotpipe.tochild.write('''\t"%s-instances" -> "group-%s";\n''' \ + % (profile, profile)) + + if bundles: + bundles = [] + [bundles.append(bund.get('name')) \ + for bund in groups.findall('.//Bundle') + if bund.get('name') not in bundles] + bundles.sort() + for bundle in bundles: + dotpipe.tochild.write( + '''\t"bundle-%s" [ label="%s", shape="septagon"];\n''' \ + % (bundle, bundle)) + gseen = [] + for group in egroups: + color = categories[group.get('category', 'default')] + if group.get('profile', 'false') == 'true': + style = "filled, bold" + else: + style = "filled" + gseen.append(group.get('name')) + dotpipe.tochild.write( + '\t"group-%s" [label="%s", style="%s", fillcolor=%s];\n' % + (group.get('name'), group.get('name'), style, color)) + if bundles: + for bundle in group.findall('Bundle'): + dotpipe.tochild.write('\t"group-%s" -> "bundle-%s";\n' % + (group.get('name'), bundle.get('name'))) + + gfmt = '\t"group-%s" [label="%s", style="filled", fillcolor="grey83"];\n' + for group in egroups: + for parent in group.findall('Group'): + if parent.get('name') not in gseen: + dotpipe.tochild.write(gfmt % (parent.get('name'), + parent.get('name'))) + gseen.append(parent.get("name")) + dotpipe.tochild.write('\t"group-%s" -> "group-%s" ;\n' % + (group.get('name'), parent.get('name'))) + + if key: + dotpipe.tochild.write("\tsubgraph cluster_key {\n") + dotpipe.tochild.write('''\tstyle="filled";\n''') + dotpipe.tochild.write('''\tcolor="lightblue";\n''') + dotpipe.tochild.write('''\tBundle [ shape="septagon" ];\n''') + dotpipe.tochild.write('''\tGroup [shape="ellipse"];\n''') + dotpipe.tochild.write('''\tProfile [style="bold", shape="ellipse"];\n''') + dotpipe.tochild.write('''\tHblock [label="Host1|Host2|Host3", shape="record"];\n''') + for category in categories: + dotpipe.tochild.write( + '''\t''' + category + ''' [label="''' + category + \ + '''", shape="record", style="filled", fillcolor=''' + \ + categories[category] + '''];\n''') + dotpipe.tochild.write('''\tlabel="Key";\n''') + dotpipe.tochild.write("\t}\n") + dotpipe.tochild.write("}\n") + dotpipe.tochild.close() + return dotpipe.fromchild.read() diff --git a/src/lib/Server/Admin/Viz.pyc b/src/lib/Server/Admin/Viz.pyc new file mode 100644 index 000000000..b403fd4e9 Binary files /dev/null and b/src/lib/Server/Admin/Viz.pyc differ diff --git a/src/lib/Server/Admin/__init__.py b/src/lib/Server/Admin/__init__.py new file mode 100644 index 000000000..d059e0a1d --- /dev/null +++ b/src/lib/Server/Admin/__init__.py @@ -0,0 +1,45 @@ +__revision__ = '$Revision: $' + +__all__ = ['Mode', 'Client', 'Compare', 'Fingerprint', 'Init', 'Minestruct', + 'Pull', 'Tidy', 'Viz'] + +import ConfigParser, lxml.etree, logging + +class Mode(object): + '''Help message has not yet been added for mode''' + __shorthelp__ = 'Shorthelp not defined yet' + __longhelp__ = 'Longhelp not defined yet' + __args__ = [] + def __init__(self, configfile): + self.configfile = configfile + self.__cfp = False + self.log = logging.getLogger('Bcfg2.Server.Admin.Mode') + + def getCFP(self): + if not self.__cfp: + self.__cfp = ConfigParser.ConfigParser() + self.__cfp.read(self.configfile) + return self.__cfp + + cfp = property(getCFP) + + def __call__(self, args): + if args[0] == 'help': + print self.__longhelp__ + raise SystemExit(0) + + def errExit(self, emsg): + print emsg + raise SystemExit(1) + + def get_repo_path(self): + '''return repository path''' + return self.cfp.get('server', 'repository') + + def load_stats(self, client): + stats = lxml.etree.parse("%s/etc/statistics.xml" % (self.get_repo_path())) + hostent = stats.xpath('//Node[@name="%s"]' % client) + if not hostent: + self.errExit("Could not find stats for client %s" % (client)) + return hostent[0] + diff --git a/src/lib/Server/Admin/__init__.pyc b/src/lib/Server/Admin/__init__.pyc new file mode 100644 index 000000000..d09de4f96 Binary files /dev/null and b/src/lib/Server/Admin/__init__.pyc differ diff --git a/src/lib/Server/__init__.py b/src/lib/Server/__init__.py index c6743f58b..8f9b7eaf5 100644 --- a/src/lib/Server/__init__.py +++ b/src/lib/Server/__init__.py @@ -2,5 +2,6 @@ '''This is the set of modules for Bcfg2.Server''' __revision__ = '$Revision$' -__all__ = ["Core", "Plugin", "Plugins", "Statistics", "Hostbase", "Reports"] +__all__ = ["Admin", "Core", "Plugin", "Plugins", "Statistics", + "Hostbase", "Reports"] -- cgit v1.2.3-1-g7c22