diff options
Diffstat (limited to 'src/lib/Client/Tools/__init__.py')
-rw-r--r-- | src/lib/Client/Tools/__init__.py | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Client/Tools/__init__.py new file mode 100644 index 000000000..da9f47eeb --- /dev/null +++ b/src/lib/Client/Tools/__init__.py @@ -0,0 +1,252 @@ +'''This contains all Bcfg2 Tool modules''' +__revision__ = '$Revision$' + +__all__ = ["APT", "Blast", "Chkconfig", "DebInit", "Launchd", "PostInstall", + "POSIX", "RPM", "SMF", "SYSV"] + +import os, popen2, stat, sys, Bcfg2.Client.XML + +class toolInstantiationError(Exception): + '''This error is called if the toolset cannot be instantiated''' + pass + +class readonlypipe(popen2.Popen4): + '''This pipe sets up stdin --> /dev/null''' + def __init__(self, cmd, bufsize=-1): + popen2._cleanup() + c2pread, c2pwrite = os.pipe() + null = open('/dev/null', 'w+') + self.pid = os.fork() + if self.pid == 0: + # Child + os.dup2(null.fileno(), sys.__stdin__.fileno()) + #os.dup2(p2cread, 0) + os.dup2(c2pwrite, 1) + os.dup2(c2pwrite, 2) + self._run_child(cmd) + os.close(c2pwrite) + self.fromchild = os.fdopen(c2pread, 'r', bufsize) + popen2._active.append(self) + +class executor: + '''this class runs stuff for us''' + def __init__(self, logger): + self.logger = logger + + def run(self, command): + '''Run a command in a pipe dealing with stdout buffer overloads''' + self.logger.debug('> %s' % command) + + runpipe = readonlypipe(command, bufsize=16384) + output = '' + cmdstat = -1 + while cmdstat == -1: + runpipe.fromchild.flush() + moreOutput = runpipe.fromchild.read() + if len(moreOutput) > 0: + self.logger.debug('< %s' % moreOutput) + output += moreOutput + cmdstat = runpipe.poll() + + return (cmdstat, [line for line in output.split('\n') if line]) + +class Tool: + '''All tools subclass this. It defines all interfaces that need to be defined''' + __name__ = 'Tool' + __execs__ = [] + __handles__ = [] + __req__ = {} + __important__ = [] + + def __init__(self, logger, setup, config, states): + self.setup = setup + self.logger = logger + if not hasattr(self, '__ireq__'): + self.__ireq__ = self.__req__ + self.config = config + self.cmd = executor(logger) + self.states = states + self.modified = [] + self.extra = [] + self.handled = [entry for struct in self.config for entry in struct \ + if self.handlesEntry(entry)] + for filename in self.__execs__: + try: + mode = stat.S_IMODE(os.stat(filename)[stat.ST_MODE]) + if mode & stat.S_IEXEC != stat.S_IEXEC: + self.logger.debug("%s: %s not executable" % \ + (self.__name__, filename)) + raise toolInstantiationError + except OSError: + raise toolInstantiationError + except: + self.logger.debug("%s failed" % filename, exc_info=1) + raise toolInstantiationError + + def BundleUpdated(self, _): + '''This callback is used when bundle updates occur''' + pass + + def Inventory(self, structures=[]): + '''Dispatch verify calls to underlying methods''' + if not structures: + structures = self.config.getchildren() + for (struct, entry) in [(struct, entry) for struct in structures \ + for entry in struct.getchildren() \ + if self.canVerify(entry)]: + try: + func = getattr(self, "Verify%s" % (entry.tag)) + self.states[entry] = func(entry, self.buildModlist(entry, struct)) + except: + self.logger.error( + "Unexpected failure of verification method for entry type %s" \ + % (entry.tag), exc_info=1) + self.extra = self.FindExtra() + + def Install(self, entries): + '''Install all entries in sublist''' + for entry in entries: + try: + func = getattr(self, "Install%s" % (entry.tag)) + self.states[entry] = func(entry) + self.modified.append(entry) + except: + self.logger.error("Unexpected failure of install method for entry type %s" \ + % (entry.tag), exc_info=1) + + def Remove(self, entries): + '''Remove specified extra entries''' + pass + + def getSupportedEntries(self): + '''return a list of supported entries''' + return [entry for struct in self.config.getchildren() for entry in struct.getchildren() \ + if self.handlesEntry(entry)] + + def handlesEntry(self, entry): + '''return if entry is handled by this Tool''' + return (entry.tag, entry.get('type')) in self.__handles__ + + def buildModlist(self, entry, struct): + '''Build a list of potentially modified POSIX paths for this entry''' + if entry.tag != 'Package' or struct.tag != 'Bundle': + return [] + return [sentry.get('name') for sentry in struct if sentry.tag in \ + ['ConfigFile', 'SymLink', 'Directory']] + + def canVerify(self, entry): + '''test if entry has enough information to be verified''' + if not self.handlesEntry(entry): + return False + + if [attr for attr in self.__req__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot verify" \ + % (entry.tag, entry.get('name'))) + return False + return True + + + def FindExtra(self): + '''Return a list of extra entries''' + return [] + + def canInstall(self, entry): + '''test if entry has enough information to be installed''' + if not self.handlesEntry(entry): + return False + if [attr for attr in self.__ireq__[entry.tag] if attr not in entry.attrib]: + self.logger.error("Incomplete information for entry %s:%s; cannot install" \ + % (entry.tag, entry.get('name'))) + return False + return True + +class PkgTool(Tool): + '''PkgTool provides a one-pass install with fallback for use with packaging systems''' + pkgtool = ('echo %s', ('%s', ['name'])) + pkgtype = 'echo' + __name__ = 'PkgTool' + + def __init__(self, logger, setup, config, states): + Tool.__init__(self, logger, setup, config, states) + self.installed = {} + self.Remove = self.RemovePackages + self.FindExtra = self.FindExtraPackages + + def VerifyPackage(self, dummy, _): + '''Dummy verification method''' + return False + + def Install(self, packages): + '''Run a one-pass install, followed by single pkg installs in case of failure''' + self.logger.info("Trying single pass package install for pkgtype %s" % \ + self.pkgtype) + + data = [tuple([pkg.get(field) for field in self.pkgtool[1][1]]) for pkg in packages] + pkgargs = " ".join([self.pkgtool[1][0] % datum for datum in data]) + + self.logger.debug("Installing packages: :%s:" % pkgargs) + self.logger.debug("Running command ::%s::" % (self.pkgtool[0] % pkgargs)) + + cmdrc = self.cmd.run(self.pkgtool[0] % pkgargs)[0] + if cmdrc == 0: + self.logger.info("Single Pass Succeded") + # set all package states to true and flush workqueues + pkgnames = [pkg.get('name') for pkg in packages] + for entry in [entry for entry in self.states.keys() + if entry.tag == 'Package' and entry.get('type') == self.pkgtype + and entry.get('name') in pkgnames]: + self.logger.debug('Setting state to true for pkg %s' % (entry.get('name'))) + self.states[entry] = True + self.RefreshPackages() + else: + self.logger.error("Single Pass Failed") + # do single pass installs + self.RefreshPackages() + for pkg in packages: + # handle state tracking updates + if self.VerifyPackage(pkg, []): + self.logger.info("Forcing state to true for pkg %s" % (pkg.get('name'))) + self.states[pkg] = True + else: + self.logger.info("Installing pkg %s version %s" % + (pkg.get('name'), pkg.get('version'))) + cmdrc = self.cmd.run(self.pkgtool[0] % + (self.pkgtool[1][0] % + tuple([pkg.get(field) for field in self.pkgtool[1][1]]))) + if cmdrc[0] == 0: + self.states[pkg] = True + else: + self.logger.error("Failed to install package %s" % (pkg.get('name'))) + for entry in [ent for ent in packages if self.states[ent]]: + self.modified.append(entry) + + def RefreshPackages(self): + '''Dummy state refresh method''' + pass + + def RemovePackages(self, packages): + '''Dummy implementation of package removal method''' + pass + + def FindExtraPackages(self): + '''Find extra packages''' + packages = [entry.get('name') for entry in self.getSupportedEntries()] + extras = [key for key in self.installed if key not in packages] + return [Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype) \ + for name in extras] + +class SvcTool(Tool): + '''This class defines basic Service behavior''' + __name__ = 'SvcTool' + + def BundleUpdated(self, bundle): + '''The Bundle has been updated''' + for entry in bundle: + if self.handlesEntry(entry): + if entry.get('status') == 'on': + self.logger.debug('Restarting service %s' % entry.get('name')) + self.cmd.run('/etc/init.d/%s %s' % \ + (entry.get('name'), entry.get('reload', 'reload'))) + else: + self.logger.debug('Stopping service %s' % entry.get('name')) + self.cmd.run('/etc/init.d/%s stop' % (entry.get('name'))) |