summaryrefslogtreecommitdiffstats
path: root/build/lib/Bcfg2/Client/Tools/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'build/lib/Bcfg2/Client/Tools/__init__.py')
-rw-r--r--build/lib/Bcfg2/Client/Tools/__init__.py370
1 files changed, 370 insertions, 0 deletions
diff --git a/build/lib/Bcfg2/Client/Tools/__init__.py b/build/lib/Bcfg2/Client/Tools/__init__.py
new file mode 100644
index 000000000..8a90e130c
--- /dev/null
+++ b/build/lib/Bcfg2/Client/Tools/__init__.py
@@ -0,0 +1,370 @@
+"""This contains all Bcfg2 Tool modules"""
+# suppress popen2 warnings for python 2.3
+import warnings
+warnings.filterwarnings("ignore", "The popen2 module is deprecated.*",
+ DeprecationWarning)
+import os
+import popen2
+import stat
+import sys
+import time
+
+import Bcfg2.Client.XML
+__revision__ = '$Revision$'
+
+__all__ = [tool.split('.')[0] \
+ for tool in os.listdir(os.path.dirname(__file__)) \
+ if tool.endswith(".py") and tool != "__init__.py"]
+
+drivers = [item for item in __all__ if item not in ['rpmtools']]
+default = [item for item in drivers if item not in ['RPM', 'Yum']]
+
+
+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 = []
+ try:#macosx doesn't like this
+ runpipe.fromchild.flush()
+ except IOError:
+ pass
+ line = runpipe.fromchild.readline()
+ cmdstat = -1
+ while cmdstat == -1:
+ while line:
+ if len(line) > 0:
+ self.logger.debug('< %s' % line[:-1])
+ output.append(line[:-1])
+ line = runpipe.fromchild.readline()
+ time.sleep(0.1)
+ cmdstat = runpipe.poll()
+ output += [line[:-1] for line in runpipe.fromchild.readlines() \
+ if line]
+ # The exit code from the program is in the upper byte of the
+ # value returned by cmdstat. Shift it down for tools looking at
+ # the value.
+ return ((cmdstat >> 8), output)
+
+
+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):
+ self.__important__ = [entry.get('name') \
+ for struct in config for entry in struct \
+ if entry.tag == 'Path' and \
+ entry.get('important') in ['true', 'True']]
+ self.setup = setup
+ self.logger = logger
+ if not hasattr(self, '__ireq__'):
+ self.__ireq__ = self.__req__
+ self.config = config
+ self.cmd = executor(logger)
+ 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, _, states):
+ """This callback is used when bundle updates occur."""
+ return
+
+ def BundleNotUpdated(self, _, states):
+ """This callback is used when a bundle is not updated."""
+ return
+
+ def Inventory(self, states, structures=[]):
+ """Dispatch verify calls to underlying methods."""
+ if not structures:
+ structures = self.config.getchildren()
+ mods = self.buildModlist()
+ 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))
+ states[entry] = func(entry, mods)
+ 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, states):
+ """Install all entries in sublist."""
+ for entry in entries:
+ try:
+ func = getattr(self, "Install%s" % (entry.tag))
+ 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):
+ '''Build a list of potentially modified POSIX paths for this entry'''
+ return [entry.get('name') for struct in self.config.getchildren() \
+ for entry in struct.getchildren() \
+ if entry.tag in ['Ignore', 'Path']]
+
+ def gatherCurrentData(self, entry):
+ """Default implementation of the information gathering routines."""
+ pass
+
+ def canVerify(self, entry):
+ """Test if entry has enough information to be verified."""
+ if not self.handlesEntry(entry):
+ return False
+
+ if 'failure' in entry.attrib:
+ self.logger.error("Entry %s:%s reports bind failure: %s" % \
+ (entry.tag, entry.get('name'), entry.get('failure')))
+ return False
+
+ missing = [attr for attr in self.__req__[entry.tag] \
+ if attr not in entry.attrib]
+ if missing:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (entry.tag, entry.get('name')))
+ self.logger.error("\t... due to absence of %s attribute(s)" % \
+ (":".join(missing)))
+ try:
+ self.gatherCurrentData(entry)
+ except:
+ self.logger.error("Unexpected error in gatherCurrentData", exc_info=1)
+ 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 'failure' in entry.attrib:
+ self.logger.error("Cannot install entry %s:%s with bind failure" % \
+ (entry.tag, entry.get('name')))
+ return False
+
+ missing = [attr for attr in self.__ireq__[entry.tag] \
+ if attr not in entry.attrib or not entry.attrib[attr]]
+ if missing:
+ self.logger.error("Incomplete information for entry %s:%s; cannot install" \
+ % (entry.tag, entry.get('name')))
+ self.logger.error("\t... due to absence of %s attribute" % \
+ (":".join(missing)))
+ 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):
+ Tool.__init__(self, logger, setup, config)
+ self.installed = {}
+ self.Remove = self.RemovePackages
+ self.FindExtra = self.FindExtraPackages
+ self.RefreshPackages()
+
+ def VerifyPackage(self, dummy, _):
+ """Dummy verification method"""
+ return False
+
+ def Install(self, packages, states):
+ """
+ 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 list(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')))
+ 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')))
+ 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:
+ states[pkg] = True
+ else:
+ self.logger.error("Failed to install package %s" % \
+ (pkg.get('name')))
+ self.RefreshPackages()
+ for entry in [ent for ent in packages if 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 = [data for data in list(self.installed.items()) \
+ if data[0] not in packages]
+ return [Bcfg2.Client.XML.Element('Package', name=name, \
+ type=self.pkgtype, version=version) \
+ for (name, version) in extras]
+
+
+class SvcTool(Tool):
+ """This class defines basic Service behavior"""
+ name = 'SvcTool'
+
+ def get_svc_command(self, service, action):
+ """Return the basename of the command used to start/stop services."""
+ return '/etc/init.d/%s %s' % (service.get('name'), action)
+
+ def start_service(self, service):
+ self.logger.debug('Starting service %s' % service.get('name'))
+ return self.cmd.run(self.get_svc_command(service, 'start'))[0]
+
+ def stop_service(self, service):
+ self.logger.debug('Stopping service %s' % service.get('name'))
+ return self.cmd.run(self.get_svc_command(service, 'stop'))[0]
+
+ def restart_service(self, service):
+ self.logger.debug('Restarting service %s' % service.get('name'))
+ restart_target = 'restart'
+ if service.get('mode', 'default') == 'custom':
+ restart_target = service.get('target', 'restart')
+ return self.cmd.run(self.get_svc_command(service, restart_target))[0]
+
+ def check_service(self, service):
+ # not supported for this driver
+ return 0
+
+ def BundleUpdated(self, bundle, states):
+ """The Bundle has been updated."""
+ if self.setup['servicemode'] == 'disabled':
+ return
+
+ for entry in [ent for ent in bundle if self.handlesEntry(ent)]:
+ if entry.get('mode', 'default') == 'manual':
+ continue
+ # need to handle servicemode = (build|default)
+ # need to handle mode = (default|supervised|custom)
+ if entry.get('status') == 'on':
+ if self.setup['servicemode'] == 'build':
+ rc = self.stop_service(entry)
+ else:
+ if self.setup['interactive']:
+ prompt = 'Restart service %s?: (y/N): ' % entry.get('name')
+ if raw_input(prompt) not in ['y', 'Y']:
+ continue
+ rc = self.restart_service(entry)
+ else:
+ rc = self.stop_service(entry)
+ if rc:
+ self.logger.error("Failed to manipulate service %s" % \
+ (entry.get('name')))