From e224ae0359840d54b9bd995a5231e4774321755a Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 28 Sep 2012 12:04:49 -0400 Subject: made client runs abort on probe failure, added option to disable that --- src/sbin/bcfg2 | 247 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 133 insertions(+), 114 deletions(-) (limited to 'src/sbin') diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2 index f41479d77..8d8521b05 100755 --- a/src/sbin/bcfg2 +++ b/src/sbin/bcfg2 @@ -11,31 +11,30 @@ import stat import sys import tempfile import time +import Bcfg2.Proxy +import Bcfg2.Logger import Bcfg2.Options import Bcfg2.Client.XML import Bcfg2.Client.Frame import Bcfg2.Client.Tools -# Compatibility imports from Bcfg2.Compat import xmlrpclib - from Bcfg2.version import __version__ +from subprocess import Popen, PIPE -import Bcfg2.Proxy -import Bcfg2.Logger - -logger = logging.getLogger('bcfg2') def cb_sigint_handler(signum, frame): - """Exit upon CTRL-C.""" - os._exit(1) + """ Exit upon CTRL-C. """ + raise SystemExit(1) -class Client: +class Client(object): """The main bcfg2 client class""" def __init__(self): self.toolset = None + self.tools = None self.config = None + self._proxy = None optinfo = Bcfg2.Options.CLIENT_COMMON_OPTIONS self.setup = Bcfg2.Options.OptionParser(optinfo) @@ -82,6 +81,15 @@ class Client: if not self.setup['server'].startswith('https://'): self.setup['server'] = 'https://' + self.setup['server'] + def _probe_failure(self, probename, msg): + """ handle failure of a probe in the way the user wants us to + (exit or continue) """ + message = "Failed to execute probe %s: %s" % (probename, msg) + if self.setup['probe_exit']: + self.fatal_error(message) + else: + self.logger.error(message) + def run_probe(self, probe): """Execute probe.""" name = probe.get('name') @@ -91,78 +99,116 @@ class Client: source=probe.get('source')) try: scripthandle, scriptname = tempfile.mkstemp() - script = open(scriptname, 'w+') + script = os.fdopen(scripthandle, 'w') try: script.write("#!%s\n" % (probe.attrib.get('interpreter', '/bin/sh'))) script.write(probe.text) script.close() - os.close(scripthandle) - os.chmod(script.name, + os.chmod(scriptname, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH | stat.S_IWUSR) # 0755 - ret.text = os.popen(script.name).read().strip() + proc = Popen(scriptname, stdin=PIPE, stdout=PIPE, stderr=PIPE) + ret.text, err = proc.communicate() + rv = proc.wait() + if err: + self.logger.warning("Probe %s has error output: %s" % + (name, err)) + if rv: + self._probe_failure(name, "Return value %s" % rv) self.logger.info("Probe %s has result:" % name) self.logger.info(ret.text) finally: - os.unlink(script.name) - except: - self.logger.error("Failed to execute probe: %s" % (name), exc_info=1) - raise SystemExit(1) + os.unlink(scriptname) + except: # pylint: disable=W0702 + self._probe_failure(name, sys.exc_info()[1]) return ret def fatal_error(self, message): """Signal a fatal error.""" self.logger.error("Fatal error: %s" % (message)) - os._exit(1) + raise SystemExit(1) + + @property + def proxy(self): + """ get an XML-RPC proxy to the server """ + if self._proxy is None: + self._proxy = Bcfg2.Proxy.ComponentProxy( + self.setup['server'], + self.setup['user'], + self.setup['password'], + key=self.setup['key'], + cert=self.setup['certificate'], + ca=self.setup['ca'], + allowedServerCNs=self.setup['serverCN'], + timeout=self.setup['timeout'], + retries=int(self.setup['retries']), + delay=int(self.setup['retry_delay'])) + return self._proxy + + def run_probes(self, times=None): + """ run probes and upload probe data """ + if times is None: + times = dict() - def run(self): - """Perform client execution phase.""" - times = {} + try: + probes = Bcfg2.Client.XML.XML(str(self.proxy.GetProbes())) + except (Bcfg2.Proxy.ProxyError, + Bcfg2.Proxy.CertificateError, + socket.gaierror, + socket.error): + self.fatal_error("Failed to download probes from bcfg2: %s" % err) + except Bcfg2.Client.XML.ParseError: + err = sys.exc_info()[1] + self.fatal_error("Server returned invalid probe requests: %s" % + err) - # begin configuration - times['start'] = time.time() + times['probe_download'] = time.time() - self.logger.info("Starting Bcfg2 client run at %s" % times['start']) + # execute probes + probedata = Bcfg2.Client.XML.Element("ProbeData") + for probe in probes.findall(".//probe"): + probedata.append(self.run_probe(probe)) + + if len(probes.findall(".//probe")) > 0: + try: + # upload probe responses + self.proxy.RecvProbeData(Bcfg2.Client.XML.tostring( + probedata, + xml_declaration=False).decode('UTF-8')) + except Bcfg2.Proxy.ProxyError: + err = sys.exc_info()[1] + self.fatal_error("Failed to upload probe data: %s" % err) + + times['probe_upload'] = time.time() + + def get_config(self, times=None): + """ load the configuration, either from the cached + configuration file (-f), or from the server """ + if times is None: + times = dict() if self.setup['file']: # read config from file try: self.logger.debug("Reading cached configuration from %s" % - (self.setup['file'])) - configfile = open(self.setup['file'], 'r') - rawconfig = configfile.read() - configfile.close() + self.setup['file']) + return open(self.setup['file'], 'r').read() except IOError: self.fatal_error("Failed to read cached configuration from: %s" % (self.setup['file'])) - return(1) else: # retrieve config from server - proxy = \ - Bcfg2.Proxy.ComponentProxy(self.setup['server'], - self.setup['user'], - self.setup['password'], - key=self.setup['key'], - cert=self.setup['certificate'], - ca=self.setup['ca'], - allowedServerCNs=self.setup['serverCN'], - timeout=self.setup['timeout'], - retries=int(self.setup['retries']), - delay=int(self.setup['retry_delay'])) - if self.setup['profile']: try: - proxy.AssertProfile(self.setup['profile']) + self.proxy.AssertProfile(self.setup['profile']) except Bcfg2.Proxy.ProxyError: err = sys.exc_info()[1] - self.fatal_error("Failed to set client profile") - self.logger.error(str(err)) - raise SystemExit(1) + self.fatal_error("Failed to set client profile: %s" % err) try: - probe_data = proxy.DeclareVersion(__version__) + self.proxy.DeclareVersion(__version__) except xmlrpclib.Fault: err = sys.exc_info()[1] if (err.faultCode == xmlrpclib.METHOD_NOT_FOUND or @@ -179,69 +225,38 @@ class Client: err = sys.exc_info()[1] self.logger.error("Failed to declare version: %s" % err) - try: - probe_data = proxy.GetProbes() - except (Bcfg2.Proxy.ProxyError, - Bcfg2.Proxy.CertificateError, - socket.gaierror, - socket.error): - err = sys.exc_info()[1] - self.logger.error("Failed to download probes from bcfg2: %s" % - err) - raise SystemExit(1) - - times['probe_download'] = time.time() - - try: - probes = Bcfg2.Client.XML.XML(str(probe_data)) - except Bcfg2.Client.XML.ParseError: - syntax_error = sys.exc_info()[1] - self.fatal_error( - "Server returned invalid probe requests: %s" % - (syntax_error)) - return(1) - - # execute probes - try: - probedata = Bcfg2.Client.XML.Element("ProbeData") - [probedata.append(self.run_probe(probe)) - for probe in probes.findall(".//probe")] - except: - self.logger.error("Failed to execute probes") - raise SystemExit(1) - - if len(probes.findall(".//probe")) > 0: - try: - # upload probe responses - proxy.RecvProbeData(Bcfg2.Client.XML.tostring(probedata, - xml_declaration=False).decode('UTF-8')) - except Bcfg2.Proxy.ProxyError: - err = sys.exc_info()[1] - self.logger.error("Failed to upload probe data: %s" % err) - raise SystemExit(1) - - times['probe_upload'] = time.time() + self.run_probes(times=times) if self.setup['decision'] in ['whitelist', 'blacklist']: try: self.setup['decision_list'] = \ - proxy.GetDecisionList(self.setup['decision']) + self.proxy.GetDecisionList(self.setup['decision']) self.logger.info("Got decision list from server:") self.logger.info(self.setup['decision_list']) except Bcfg2.Proxy.ProxyError: err = sys.exc_info()[1] - self.logger.error("Failed to get decision list: %s" % err) - raise SystemExit(1) + self.fatal_error("Failed to get decision list: %s" % err) try: - rawconfig = proxy.GetConfig().encode('UTF-8') + rawconfig = self.proxy.GetConfig().encode('UTF-8') except Bcfg2.Proxy.ProxyError: err = sys.exc_info()[1] - self.logger.error("Failed to download configuration from " - "Bcfg2: %s" % err) - raise SystemExit(2) + self.fatal_error("Failed to download configuration from " + "Bcfg2: %s" % err) times['config_download'] = time.time() + return rawconfig + + def run(self): + """Perform client execution phase.""" + times = {} + + # begin configuration + times['start'] = time.time() + + self.logger.info("Starting Bcfg2 client run at %s" % times['start']) + + rawconfig = self.get_config(times=times) if self.setup['cache']: try: @@ -268,13 +283,13 @@ class Client: if self.setup['bundle_quick']: newconfig = Bcfg2.Client.XML.XML('') - [newconfig.append(bundle) - for bundle in self.config.getchildren() - if (bundle.tag == 'Bundle' and - ((self.setup['bundle'] and - bundle.get('name') in self.setup['bundle']) or - (self.setup['skipbundle'] and - bundle.get('name') not in self.setup['skipbundle'])))] + for bundle in self.config.getchildren(): + if (bundle.tag == 'Bundle' and + ((self.setup['bundle'] and + bundle.get('name') in self.setup['bundle']) or + (self.setup['skipbundle'] and + bundle.get('name') not in self.setup['skipbundle']))): + newconfig.append(bundle) self.config = newconfig self.tools = Bcfg2.Client.Frame.Frame(self.config, @@ -287,33 +302,38 @@ class Client: try: lockfile = open(self.setup['lockfile'], 'w') try: - fcntl.lockf(lockfile.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + fcntl.lockf(lockfile.fileno(), + fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: - #otherwise exit and give a warning to the user - self.fatal_error("An other instance of Bcfg2 is running. If you what to bypass the check, run with %s option" % - (Bcfg2.Options.OMIT_LOCK_CHECK.cmd)) - except: + # otherwise exit and give a warning to the user + self.fatal_error("An other instance of Bcfg2 is running. " + "If you what to bypass the check, run " + "with %s option" % + Bcfg2.Options.OMIT_LOCK_CHECK.cmd) + except: # pylint: disable=W0702 lockfile = None self.logger.error("Failed to open lockfile") # execute the said configuration self.tools.Execute() if not self.setup['omit_lock_check']: - #unlock here + # unlock here if lockfile: try: fcntl.lockf(lockfile.fileno(), fcntl.LOCK_UN) os.remove(self.setup['lockfile']) except OSError: - self.logger.error("Failed to unlock lockfile %s" % lockfile.name) + self.logger.error("Failed to unlock lockfile %s" % + lockfile.name) if not self.setup['file'] and not self.setup['bundle_quick']: # upload statistics feedback = self.tools.GenerateStats() try: - proxy.RecvStats(Bcfg2.Client.XML.tostring(feedback, - xml_declaration=False).decode('UTF-8')) + self.proxy.RecvStats(Bcfg2.Client.XML.tostring( + feedback, + xml_declaration=False).decode('UTF-8')) except Bcfg2.Proxy.ProxyError: err = sys.exc_info()[1] self.logger.error("Failed to upload configuration statistics: " @@ -322,8 +342,7 @@ class Client: self.logger.info("Finished Bcfg2 client run at %s" % time.time()) + if __name__ == '__main__': signal.signal(signal.SIGINT, cb_sigint_handler) - client = Client() - spid = os.getpid() - client.run() + Client().run() -- cgit v1.2.3-1-g7c22