summaryrefslogtreecommitdiffstats
path: root/src/lib/Client/Frame.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Client/Frame.py')
-rw-r--r--src/lib/Client/Frame.py213
1 files changed, 213 insertions, 0 deletions
diff --git a/src/lib/Client/Frame.py b/src/lib/Client/Frame.py
new file mode 100644
index 000000000..dd1e98008
--- /dev/null
+++ b/src/lib/Client/Frame.py
@@ -0,0 +1,213 @@
+'''Frame is the Client Framework that verifies and installs entries, and generates statistics'''
+__revision__ = '$Revision$'
+
+import logging, time
+import Bcfg2.Client.Tools
+
+def promptFilter(prompt, entries):
+ '''Filter a supplied list based on user input'''
+ ret = []
+ for entry in [entry for entry in entries]:
+ try:
+ if raw_input(prompt % (entry.tag, entry.get('name'))) in ['y', 'Y']:
+ ret.append(entry)
+ except:
+ continue
+ return ret
+
+class Frame:
+ '''Frame is the container for all Tool objects and state information'''
+ def __init__(self, config, setup, times):
+ self.config = config
+ self.times = times
+ self.times['initialization'] = time.time()
+ self.setup = setup
+ self.tools = []
+ self.states = {}
+ self.whitelist = []
+ self.removal = []
+ self.logger = logging.getLogger("Bcfg2.Client.Frame")
+ for tool in Bcfg2.Client.Tools.__all__[:]:
+ try:
+ tool_class = "Bcfg2.Client.Tools.%s" % tool
+ mod = __import__(tool_class, globals(), locals(), ['*'])
+ except ImportError:
+ print "Failed to import module %s" % (tool)
+ continue
+
+ try:
+ self.tools.append(getattr(mod, tool)(self.logger, setup, config, self.states))
+ except Bcfg2.Client.Tools.toolInstantiationError:
+ continue
+ except:
+ self.logger.error("Failed to instantiate tool %s" % (tool), exc_info=1)
+ self.logger.info("Loaded tool drivers:")
+ self.logger.info([tool.__name__ for tool in self.tools])
+ if not self.setup['dryrun']:
+ for cfile in [cfl for cfl in config.findall(".//ConfigFile") \
+ if cfl.get('name') in self.__important__]:
+ self.VerifyEntry(cfile)
+ if not self.states[cfile]:
+ self.InstallConfigFile(cfile)
+ # find entries not handled by any tools
+ problems = [entry for struct in config for entry in struct if entry not in self.handled]
+ if problems:
+ self.logger.error("The following entries are not handled by any tool:")
+ self.logger.error(["%s:%s:%s" % (entry.tag, entry.get('type'), \
+ entry.get('name')) for entry in problems])
+ self.logger.error("")
+
+ def __getattr__(self, name):
+ if name in ['extra', 'handled', 'modified', '__important__']:
+ ret = []
+ for tool in self.tools:
+ ret += getattr(tool, name)
+ return ret
+ elif self.__dict__.has_key(name):
+ return self.__dict__[name]
+ raise AttributeError, name
+
+ def Inventory(self):
+ '''Verify all entries, find extra entries, and build up workqueues'''
+ # initialize all states
+ for struct in self.config.getchildren():
+ for entry in struct.getchildren():
+ self.states[entry] = False
+ for tool in self.tools:
+ try:
+ tool.Inventory()
+ except:
+ self.logger.error("%s.Inventory() call failed:" % tool.__name__, exc_info=1)
+
+ def Decide(self):
+ '''Set self.whitelist based on user interaction'''
+ prompt = "Would you like to install %s: %s? (y/N): "
+ rprompt = "Would you like to remove %s: %s? (y/N): "
+ if self.setup['dryrun']:
+ self.logger.info("In dryrun mode: suppressing entry installation for:")
+ self.logger.info(["%s:%s" % (entry.tag, entry.get('name')) for entry \
+ in self.states if not self.states[entry]])
+ return
+ elif self.setup['interactive']:
+ self.whitelist = promptFilter(prompt, [entry for entry in self.states \
+ if not self.states[entry]])
+ self.removal = promptFilter(rprompt, self.extra)
+ elif self.setup['bundle']:
+ # only install entries in specified bundle
+ mbs = [bund for bund in self.config.findall('./Bundle') \
+ if bund.get('name') == self.setup['bundle']]
+ if not mbs:
+ self.logger.error("Could not find bundle %s" % (self.setup['bundle']))
+ return
+ self.whitelist = [entry for entry in self.states if not self.states[entry] \
+ and entry in mbs[0].getchildren()]
+ else:
+ # all systems are go
+ self.whitelist = [entry for entry in self.states if not self.states[entry]]
+ if self.setup['remove']:
+ if self.setup['remove'] == 'all':
+ self.removal = self.extra
+ elif self.setup['remove'] == 'services':
+ self.removal = [entry for entry in self.extra if entry.tag == 'Service']
+ elif self.setup['remove'] == 'packages':
+ self.removal = [entry for entry in self.extra if entry.tag == 'Package']
+
+ def DispatchInstallCalls(self, entries):
+ '''Dispatch install calls to underlying tools'''
+ for tool in self.tools:
+ handled = [entry for entry in entries if tool.canInstall(entry)]
+ if not handled:
+ continue
+ try:
+ tool.Install(handled)
+ except:
+ self.logger.error("%s.Install() call failed:" % tool.__name__, exc_info=1)
+
+ def Install(self):
+ '''Install all entries'''
+ self.DispatchInstallCalls(self.whitelist)
+ if self.modified:
+ # Handle Bundle interdeps
+ mods = self.modified
+ mbundles = [struct for struct in self.config if struct.tag == 'Bundle' and \
+ [mod for mod in mods if mod in struct]]
+ self.logger.info("The Following Bundles have been modifed:")
+ self.logger.info([mbun.get('name') for mbun in mbundles])
+ self.logger.info("")
+ tbm = [(t, b) for t in self.tools for b in mbundles]
+ for tool, bundle in tbm:
+ try:
+ tool.Inventory(bundle)
+ except:
+ self.logger.error("%s.Inventory() call failed:" % tool.__name__, exc_info=1)
+ clobbered = [entry for bundle in mbundles for entry in bundle \
+ if not self.states[entry]]
+ if not self.setup['interactive']:
+ self.DispatchInstallCalls(clobbered)
+ for tool, bundle in tbm:
+ try:
+ tool.BundleUpdated(bundle)
+ except:
+ self.logger.error("%s.BundleUpdated() call failed:" % (tool.__name__), exc_info=1)
+
+ def Remove(self):
+ '''Remove extra entries'''
+ for tool in self.tools:
+ extras = [entry for entry in self.removal if tool.canInstall(entry)]
+ if extras:
+ try:
+ tool.Remove(extras)
+ except:
+ self.logger.error("%s.Remove() failed" % tool.__name__, exc_info=1)
+
+ def CondDisplayState(self, phase):
+ '''Conditionally print tracing information'''
+ self.logger.info('\nPhase: %s' % phase)
+ self.logger.info('Correct entries:\t%d' % self.states.values().count(True))
+ self.logger.info('Incorrect entries:\t%d' % self.states.values().count(False))
+ self.logger.info('Total managed entries:\t%d' % len(self.states.values()))
+ self.logger.info('Unmanaged entries:\t%d' % len(self.extra))
+ self.logger.info("")
+
+ if ((self.states.values().count(False) == 0) and not self.extra):
+ self.logger.info('All entries correct.')
+
+ def Execute(self):
+ '''Run all methods'''
+ self.Inventory()
+ self.times['inventory'] = time.time()
+ self.CondDisplayState('initial')
+ self.Decide()
+ self.Install()
+ self.times['install'] = time.time()
+ self.Remove()
+ self.times['finished'] = time.time()
+ self.CondDisplayState('final')
+
+ def GenerateStats(self):
+ '''Generate XML summary of execution statistics'''
+ feedback = Bcfg2.Client.XML.Element("upload-statistics")
+ stats = Bcfg2.Client.XML.SubElement(feedback, \
+ 'Statistics', total=str(len(self.states)),
+ client_version=__revision__, version='2.0',
+ revision=self.config.get('revision', '-1'))
+ good = len([key for key, val in self.states.iteritems() if val])
+ stats.set('good', str(good))
+ if len([key for key, val in self.states.iteritems() if not val]) == 0:
+ stats.set('state', 'clean')
+ else:
+ stats.set('state', 'dirty')
+
+ # List bad elements of the configuration
+ for (data, ename) in [(self.modified, 'Modified'), (self.extra, "Extra"), \
+ ([entry for entry in self.states if not \
+ self.states[entry]], "Bad")]:
+ container = Bcfg2.Client.XML.SubElement(stats, ename)
+ [container.append(item) for item in data]
+
+ timeinfo = Bcfg2.Client.XML.Element("OpStamps")
+ feedback.append(stats)
+ for (event, timestamp) in self.times.iteritems():
+ timeinfo.set(event, str(timestamp))
+ stats.append(timeinfo)
+ return feedback