From 7beb2f08d8dab26d9fe34235c91c1b2a7a2794bd Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Fri, 6 Oct 2006 21:06:16 +0000 Subject: Merge of the client/refactor branch (revisions r2218-r2400) git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@2401 ce84e21b-d406-0410-9b95-82705330c041 --- src/lib/Client/Frame.py | 213 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 src/lib/Client/Frame.py (limited to 'src/lib/Client/Frame.py') 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 -- cgit v1.2.3-1-g7c22