From 89e7afbf74ffbbb54dd892bf2c4245aedee2a832 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Tue, 9 Dec 2014 11:10:24 -0600 Subject: Remove blanket excepts from plugins and lint This removes most blanket except: clauses from all plugins, including the base plugin libraries, and bcfg2-lint. The few that remain should all be necessary. Most of the changes were quite minor, but this did require some restructuring of the CfgPrivateKeyCreator; as a result, the tests for that module were rewritten. --- src/lib/Bcfg2/Server/Lint/GroupPatterns.py | 5 +- src/lib/Bcfg2/Server/Lint/__init__.py | 4 +- src/lib/Bcfg2/Server/Plugin/helpers.py | 10 +-- src/lib/Bcfg2/Server/Plugin/interfaces.py | 10 ++- src/lib/Bcfg2/Server/Plugins/AWSTags.py | 18 ++--- src/lib/Bcfg2/Server/Plugins/Bundler.py | 4 - .../Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py | 11 ++- .../Server/Plugins/Cfg/CfgPrivateKeyCreator.py | 33 ++++---- src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py | 16 ++-- src/lib/Bcfg2/Server/Plugins/FileProbes.py | 4 +- src/lib/Bcfg2/Server/Plugins/Git.py | 27 +++---- src/lib/Bcfg2/Server/Plugins/GroupPatterns.py | 72 +++++++++-------- src/lib/Bcfg2/Server/Plugins/Guppy.py | 41 ++++------ src/lib/Bcfg2/Server/Plugins/Hg.py | 11 +-- src/lib/Bcfg2/Server/Plugins/Metadata.py | 25 ++---- src/lib/Bcfg2/Server/Plugins/Ohai.py | 4 +- src/lib/Bcfg2/Server/Plugins/Packages/Apt.py | 2 +- src/lib/Bcfg2/Server/Plugins/Packages/Pac.py | 2 +- src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py | 2 +- src/lib/Bcfg2/Server/Plugins/Packages/Source.py | 18 ++++- src/lib/Bcfg2/Server/Plugins/Packages/Yum.py | 8 -- src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py | 4 +- src/lib/Bcfg2/Server/Plugins/Packages/__init__.py | 4 - src/lib/Bcfg2/Server/Plugins/Pkgmgr.py | 8 +- src/lib/Bcfg2/Server/Plugins/Probes.py | 9 +-- src/lib/Bcfg2/Server/Plugins/Properties.py | 8 +- src/lib/Bcfg2/Server/Plugins/Reporting.py | 9 +-- src/lib/Bcfg2/Server/Plugins/SSHbase.py | 17 ++-- src/lib/Bcfg2/Server/Plugins/TemplateHelper.py | 4 + .../TestPlugins/TestCfg/TestCfgGenshiGenerator.py | 2 +- .../TestCfg/TestCfgPrivateKeyCreator.py | 94 ++++++++++++---------- .../TestServer/TestPlugins/TestCfg/Test_init.py | 2 +- .../Testlib/TestServer/TestPlugins/TestMetadata.py | 7 -- .../TestServer/TestPlugins/TestProperties.py | 2 +- testsuite/Testsrc/test_code_checks.py | 5 +- testsuite/common.py | 27 +++++++ testsuite/install.sh | 2 +- 37 files changed, 260 insertions(+), 271 deletions(-) diff --git a/src/lib/Bcfg2/Server/Lint/GroupPatterns.py b/src/lib/Bcfg2/Server/Lint/GroupPatterns.py index deb91020d..e9813b7e9 100644 --- a/src/lib/Bcfg2/Server/Lint/GroupPatterns.py +++ b/src/lib/Bcfg2/Server/Lint/GroupPatterns.py @@ -4,7 +4,8 @@ import sys from Bcfg2.Server.Lint import ServerPlugin -from Bcfg2.Server.Plugins.GroupPatterns import PatternMap +from Bcfg2.Server.Plugins.GroupPatterns import PatternMap, \ + PatternInitializationError class GroupPatterns(ServerPlugin): @@ -36,7 +37,7 @@ class GroupPatterns(ServerPlugin): PatternMap(pat, None, groups) else: PatternMap(None, pat, groups) - except: # pylint: disable=W0702 + except PatternInitializationError: err = sys.exc_info()[1] self.LintError("pattern-fails-to-initialize", "Failed to initialize %s %s for %s: %s" % diff --git a/src/lib/Bcfg2/Server/Lint/__init__.py b/src/lib/Bcfg2/Server/Lint/__init__.py index 903ee6326..61f704206 100644 --- a/src/lib/Bcfg2/Server/Lint/__init__.py +++ b/src/lib/Bcfg2/Server/Lint/__init__.py @@ -26,7 +26,7 @@ def _ioctl_GWINSZ(fd): # pylint: disable=C0103 from the given file descriptor """ try: return struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) - except: # pylint: disable=W0702 + except (IOError, struct.error): return None @@ -40,7 +40,7 @@ def get_termsize(): fd = os.open(os.ctermid(), os.O_RDONLY) dims = _ioctl_GWINSZ(fd) os.close(fd) - except: # pylint: disable=W0702 + except IOError: pass if not dims: try: diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 2aab231c6..5cfc8998c 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -266,7 +266,7 @@ class FileBacked(Debuggable): self.fam = Bcfg2.Server.FileMonitor.get_fam() def HandleEvent(self, event=None): - """ HandleEvent is called whenever the FAM registers an event. + """HandleEvent is called whenever the FAM registers an event. :param event: The event object :type event: Bcfg2.Server.FileMonitor.Event @@ -276,13 +276,11 @@ class FileBacked(Debuggable): return try: self.data = open(self.name).read() - self.Index() except IOError: err = sys.exc_info()[1] self.logger.error("Failed to read file %s: %s" % (self.name, err)) - except: - err = sys.exc_info()[1] - self.logger.error("Failed to parse file %s: %s" % (self.name, err)) + + self.Index() def Index(self): """ Index() is called by :func:`HandleEvent` every time the @@ -1196,7 +1194,7 @@ class SpecificData(Debuggable): self.data = open(self.name).read() except UnicodeDecodeError: self.data = open(self.name, mode='rb').read() - except: # pylint: disable=W0201 + except IOError: self.logger.error("Failed to read file %s" % self.name) diff --git a/src/lib/Bcfg2/Server/Plugin/interfaces.py b/src/lib/Bcfg2/Server/Plugin/interfaces.py index c45d6fa84..da39ac77a 100644 --- a/src/lib/Bcfg2/Server/Plugin/interfaces.py +++ b/src/lib/Bcfg2/Server/Plugin/interfaces.py @@ -445,8 +445,14 @@ class ThreadedStatistics(Statistics, Threaded, threading.Thread): except Empty: continue except: - err = sys.exc_info()[1] - self.logger.error("ThreadedStatistics: %s" % err) + # we want to catch all exceptions here so that a stray + # error doesn't kill the entire statistics thread. For + # instance, if a bad value gets pushed onto the queue + # and the assignment above raises TypeError, we want + # to report the error, ignore the bad value, and + # continue processing statistics. + self.logger.error("Unknown error processing statistics: %s" % + sys.exc_info()[1]) continue self.handle_statistic(client, xdata) if self.work_queue is not None and not self.work_queue.empty(): diff --git a/src/lib/Bcfg2/Server/Plugins/AWSTags.py b/src/lib/Bcfg2/Server/Plugins/AWSTags.py index 3f92542e7..0d6eefaaa 100644 --- a/src/lib/Bcfg2/Server/Plugins/AWSTags.py +++ b/src/lib/Bcfg2/Server/Plugins/AWSTags.py @@ -1,4 +1,4 @@ -""" Query tags from AWS via boto, optionally setting group membership """ +"""Query tags from AWS via boto, optionally setting group membership.""" import os import re @@ -82,22 +82,17 @@ class PatternFile(Bcfg2.Server.Plugin.XMLFileBacked): self.tags.append(AWSTagPattern(entry.get("name"), entry.get("value"), groups)) - except: # pylint: disable=W0702 + except re.error: self.logger.error("AWSTags: Failed to initialize pattern %s: " "%s" % (entry.get("name"), sys.exc_info()[1])) - def get_groups(self, hostname, tags): + def get_groups(self, tags): """ return a list of groups that should be added to the given - client based on patterns that match the hostname """ + client based on patterns that match the tags """ ret = [] for pattern in self.tags: - try: - ret.extend(pattern.get_groups(tags)) - except: # pylint: disable=W0702 - self.logger.error("AWSTags: Failed to process pattern %s for " - "%s" % (pattern, hostname), - exc_info=1) + ret.extend(pattern.get_groups(tags)) return ret @@ -182,5 +177,4 @@ class AWSTags(Bcfg2.Server.Plugin.Plugin, return self.get_tags(metadata) def get_additional_groups(self, metadata): - return self.config.get_groups(metadata.hostname, - self.get_tags(metadata)) + return self.config.get_groups(self.get_tags(metadata)) diff --git a/src/lib/Bcfg2/Server/Plugins/Bundler.py b/src/lib/Bcfg2/Server/Plugins/Bundler.py index 6c35ada59..e38eeea89 100644 --- a/src/lib/Bcfg2/Server/Plugins/Bundler.py +++ b/src/lib/Bcfg2/Server/Plugins/Bundler.py @@ -92,10 +92,6 @@ class Bundler(Plugin, self.logger.error("Bundler: Failed to render templated bundle " "%s: %s" % (bundlename, err)) continue - except: - self.logger.error("Bundler: Unexpected bundler error for %s" % - bundlename, exc_info=1) - continue if data.get("independent", "false").lower() == "true": data.tag = "Independent" diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py index ef4e6a656..c3bf9569b 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py @@ -10,6 +10,7 @@ from Bcfg2.Server.Plugin import PluginExecutionError, removecomment, \ DefaultTemplateDataProvider, get_template_data from Bcfg2.Server.Plugins.Cfg import CfgGenerator from genshi.template import TemplateLoader, NewTextTemplate +from genshi.template.base import TemplateError from genshi.template.eval import UndefinedError, Suite @@ -117,6 +118,8 @@ class CfgGenshiGenerator(CfgGenerator): quad[2])) raise except: + # this needs to be a blanket except, since it can catch + # any error raised by the genshi template. self._handle_genshi_exception(sys.exc_info()) get_data.__doc__ = CfgGenerator.get_data.__doc__ @@ -184,10 +187,10 @@ class CfgGenshiGenerator(CfgGenerator): def handle_event(self, event): CfgGenerator.handle_event(self, event) try: - self.template = \ - self.loader.load(self.name, cls=NewTextTemplate, - encoding=Bcfg2.Options.setup.encoding) - except: + self.template = self.loader.load( + self.name, cls=NewTextTemplate, + encoding=Bcfg2.Options.setup.encoding) + except TemplateError: raise PluginExecutionError("Failed to load template: %s" % sys.exc_info()[1]) handle_event.__doc__ = CfgGenerator.handle_event.__doc__ diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py index 8cc3f7b21..43035b410 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py @@ -47,7 +47,7 @@ class CfgPrivateKeyCreator(XMLCfgCreator): the given client metadata, and may be obtained by doing ``self.XMLMatch(metadata)`` :type spec: lxml.etree._Element - :returns: string - The filename of the private key + :returns: tuple - (private key data, public key data) """ if spec is None: spec = self.XMLMatch(metadata) @@ -91,10 +91,9 @@ class CfgPrivateKeyCreator(XMLCfgCreator): "with errors: %s" % (filename, metadata.hostname, result.stderr)) - return filename - except: + return (open(filename).read(), open(filename + ".pub").read()) + finally: shutil.rmtree(tempdir) - raise # pylint: disable=W0221 def create_data(self, entry, metadata): @@ -109,21 +108,17 @@ class CfgPrivateKeyCreator(XMLCfgCreator): """ spec = self.XMLMatch(metadata) specificity = self.get_specificity(metadata) - filename = self._gen_keypair(metadata, spec) + privkey, pubkey = self._gen_keypair(metadata, spec) - try: - # write the public key, stripping the comment and - # replacing it with a comment that specifies the filename. - kdata = open(filename + ".pub").read().split()[:2] - kdata.append(self.pubkey_creator.get_filename(**specificity)) - pubkey = " ".join(kdata) + "\n" - self.pubkey_creator.write_data(pubkey, **specificity) + # write the public key, stripping the comment and + # replacing it with a comment that specifies the filename. + kdata = pubkey.split()[:2] + kdata.append(self.pubkey_creator.get_filename(**specificity)) + pubkey = " ".join(kdata) + "\n" + self.pubkey_creator.write_data(pubkey, **specificity) - # encrypt the private key, write to the proper place, and - # return it - privkey = open(filename).read() - self.write_data(privkey, **specificity) - return privkey - finally: - shutil.rmtree(os.path.dirname(filename)) + # encrypt the private key, write to the proper place, and + # return it + self.write_data(privkey, **specificity) + return privkey # pylint: enable=W0221 diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py index 5dc3d98eb..355e53588 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py @@ -553,12 +553,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet): % (action, event.filename)) self.debug_log("%s handling %s event on %s" % (hdlr.__name__, action, event.filename)) - try: - self.entry_init(event, hdlr) - except: # pylint: disable=W0702 - err = sys.exc_info()[1] - self.logger.error("Cfg: Failed to parse %s: %s" % - (event.filename, err)) + self.entry_init(event, hdlr) return elif hdlr.ignore(event, basename=self.path): return @@ -732,7 +727,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet): try: return creator.create_data(entry, metadata) - except: + except CfgCreationError: raise PluginExecutionError("Cfg: Error creating data for %s: %s" % (entry.get("name"), sys.exc_info()[1])) @@ -760,6 +755,9 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet): try: return (generator.get_data(entry, metadata), generator) except: + # TODO: the exceptions raised by ``get_data`` are not + # constrained in any way, so for now this needs to be a + # blanket except. msg = "Cfg: Error rendering %s: %s" % (entry.get("name"), sys.exc_info()[1]) self.logger.error(msg) @@ -803,7 +801,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet): try: best = self.best_matching(metadata, generators) rv.append(best.specific) - except: # pylint: disable=W0702 + except PluginExecutionError: pass if not rv or not rv[0].hostname: @@ -835,7 +833,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet): raise PluginExecutionError(msg) try: etext = new_entry['text'].encode(Bcfg2.Options.setup.encoding) - except: + except UnicodeDecodeError: msg = "Cfg: Cannot encode content of %s as %s" % \ (name, Bcfg2.Options.setup.encoding) self.logger.error(msg) diff --git a/src/lib/Bcfg2/Server/Plugins/FileProbes.py b/src/lib/Bcfg2/Server/Plugins/FileProbes.py index 38f9403f5..b7d78bf75 100644 --- a/src/lib/Bcfg2/Server/Plugins/FileProbes.py +++ b/src/lib/Bcfg2/Server/Plugins/FileProbes.py @@ -38,7 +38,7 @@ if not os.path.exists(path): try: stat = os.stat(path) -except: +except OSError: sys.stderr.write("Could not stat %%s: %%s" %% (path, sys.exc_info()[1])) raise SystemExit(0) data = Bcfg2.Client.XML.Element("ProbedFileData", @@ -48,7 +48,7 @@ data = Bcfg2.Client.XML.Element("ProbedFileData", mode=oct_mode(stat[0] & 4095)) try: data.text = b64encode(open(path).read()) -except: +except IOError: sys.stderr.write("Could not read %%s: %%s" %% (path, sys.exc_info()[1])) raise SystemExit(0) print(Bcfg2.Client.XML.tostring(data, xml_declaration=False).decode('UTF-8')) diff --git a/src/lib/Bcfg2/Server/Plugins/Git.py b/src/lib/Bcfg2/Server/Plugins/Git.py index 9012fceb0..d0ae010fa 100644 --- a/src/lib/Bcfg2/Server/Plugins/Git.py +++ b/src/lib/Bcfg2/Server/Plugins/Git.py @@ -41,22 +41,17 @@ class Git(Version): def get_revision(self): """Read git revision information for the Bcfg2 repository.""" - try: - if HAS_GITPYTHON: - return self.repo.head.commit.hexsha - else: - cmd = ["git", "--git-dir", self.vcs_path, - "--work-tree", Bcfg2.Options.setup.vcs_root, - "rev-parse", "HEAD"] - self.debug_log("Git: Running %s" % cmd) - result = self.cmd.run(cmd) - if not result.success: - raise Exception(result.stderr) - return result.stdout - except: - raise PluginExecutionError("Git: Error getting revision from %s: " - "%s" % (Bcfg2.Options.setup.vcs_root, - sys.exc_info()[1])) + if HAS_GITPYTHON: + return self.repo.head.commit.hexsha + else: + cmd = ["git", "--git-dir", self.vcs_path, + "--work-tree", Bcfg2.Options.setup.vcs_root, + "rev-parse", "HEAD"] + self.debug_log("Git: Running %s" % cmd) + result = self.cmd.run(cmd) + if not result.success: + raise PluginExecutionError(result.stderr) + return result.stdout def Update(self, ref=None): """ Git.Update() => True|False diff --git a/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py b/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py index 7fa95fd05..9874977df 100644 --- a/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py +++ b/src/lib/Bcfg2/Server/Plugins/GroupPatterns.py @@ -8,34 +8,43 @@ import Bcfg2.Server.Plugin from Bcfg2.Utils import PackedDigitRange +class PatternInitializationError(Exception): + """Raised when creating a PatternMap object fails.""" + + class PatternMap(object): - """ Handler for a single pattern or range """ + """Handler for a single pattern or range.""" def __init__(self, pattern, rangestr, groups): self.pattern = pattern self.rangestr = rangestr self.groups = groups - if pattern is not None: - self.re = re.compile(pattern) - self.process = self.process_re - elif rangestr is not None: - if '\\' in rangestr: - raise Exception("Backslashes are not allowed in NameRanges") - range_finder = r'\[\[[\d\-,]+\]\]' - self.process = self.process_range - self.re = re.compile(r'^' + re.sub(range_finder, r'(\d+)', - rangestr)) - dmatcher = re.compile(re.sub(range_finder, - r'\[\[([\d\-,]+)\]\]', - rangestr)) - self.dranges = [PackedDigitRange(x) - for x in dmatcher.match(rangestr).groups()] - else: - raise Exception("No pattern or range given") + try: + if pattern is not None: + self._re = re.compile(pattern) + self.process = self.process_re + elif rangestr is not None: + if '\\' in rangestr: + raise PatternInitializationError( + "Backslashes are not allowed in NameRanges") + range_finder = r'\[\[[\d\-,]+\]\]' + self.process = self.process_range + self._re = re.compile(r'^' + re.sub(range_finder, r'(\d+)', + rangestr)) + dmatcher = re.compile(re.sub(range_finder, + r'\[\[([\d\-,]+)\]\]', + rangestr)) + self.dranges = [PackedDigitRange(x) + for x in dmatcher.match(rangestr).groups()] + else: + raise PatternInitializationError("No pattern or range given") + except re.error: + raise PatternInitializationError( + "Could not compile pattern regex: %s" % sys.exc_info()[1]) def process_range(self, name): - """ match the given hostname against a range-based NameRange """ - match = self.re.match(name) + """match the given hostname against a range-based NameRange.""" + match = self._re.match(name) if not match: return None digits = match.groups() @@ -45,11 +54,11 @@ class PatternMap(object): return self.groups def process_re(self, name): - """ match the given hostname against a regex-based NamePattern """ - match = self.re.search(name) + """match the given hostname against a regex-based NamePattern.""" + match = self._re.search(name) if not match: return None - ret = list() + ret = [] sub = match.groups() for group in self.groups: newg = group @@ -64,7 +73,7 @@ class PatternMap(object): class PatternFile(Bcfg2.Server.Plugin.XMLFileBacked): - """ representation of GroupPatterns config.xml """ + """representation of GroupPatterns config.xml.""" __identifier__ = None create = 'GroupPatterns' @@ -89,7 +98,7 @@ class PatternFile(Bcfg2.Server.Plugin.XMLFileBacked): for range_ent in entry.findall('NameRange'): rng = range_ent.text self.patterns.append(PatternMap(None, rng, groups)) - except: # pylint: disable=W0702 + except PatternInitializationError: self.logger.error("GroupPatterns: Failed to initialize " "pattern %s: %s" % (entry.text, sys.exc_info()[1])) @@ -99,20 +108,15 @@ class PatternFile(Bcfg2.Server.Plugin.XMLFileBacked): client based on patterns that match the hostname """ ret = [] for pattern in self.patterns: - try: - grps = pattern.process(hostname) - if grps is not None: - ret.extend(grps) - except: # pylint: disable=W0702 - self.logger.error("GroupPatterns: Failed to process pattern " - "%s for %s" % (pattern.pattern, hostname), - exc_info=1) + grps = pattern.process(hostname) + if grps is not None: + ret.extend(grps) return ret class GroupPatterns(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector): - """ set group membership based on client hostnames """ + """set group membership based on client hostnames.""" def __init__(self, core): Bcfg2.Server.Plugin.Plugin.__init__(self, core) diff --git a/src/lib/Bcfg2/Server/Plugins/Guppy.py b/src/lib/Bcfg2/Server/Plugins/Guppy.py index 8427a56c3..de994a2db 100644 --- a/src/lib/Bcfg2/Server/Plugins/Guppy.py +++ b/src/lib/Bcfg2/Server/Plugins/Guppy.py @@ -1,9 +1,9 @@ -""" -This plugin is used to trace memory leaks within the bcfg2-server -process using Guppy. By default the remote debugger is started -when this plugin is enabled. The debugger can be shutoff in a running -process using "bcfg2-admin xcmd Guppy.Disable" and reenabled using -"bcfg2-admin xcmd Guppy.Enable". +"""Debugging plugin to trace memory leaks within the bcfg2-server process. + +By default the remote debugger is started when this plugin is enabled. +The debugger can be shutoff in a running process using "bcfg2-admin +xcmd Guppy.Disable" and reenabled using "bcfg2-admin xcmd +Guppy.Enable". To attach the console run: @@ -23,36 +23,27 @@ Remote connection 1. To return to Monitor, type or . Remote interactive console. To return to Annex, type '-'. >>> hp.heap() ... - - """ import Bcfg2.Server.Plugin from guppy.heapy import Remote class Guppy(Bcfg2.Server.Plugin.Plugin): - """Guppy is a debugging plugin to help trace memory leaks""" + """Guppy is a debugging plugin to help trace memory leaks.""" __author__ = 'bcfg-dev@mcs.anl.gov' __rmi__ = Bcfg2.Server.Plugin.Plugin.__rmi__ + ['Enable', 'Disable'] __child_rmi__ = __rmi__[:] def __init__(self, core): Bcfg2.Server.Plugin.Plugin.__init__(self, core) - self.Enable() - def Enable(self): - """Enable remote debugging""" - try: - Remote.on() - except: - self.logger.error("Failed to create Heapy context") - raise Bcfg2.Server.Plugin.PluginInitError - - def Disable(self): - """Disable remote debugging""" - try: - Remote.off() - except: - self.logger.error("Failed to disable Heapy") - raise Bcfg2.Server.Plugin.PluginInitError + @staticmethod + def Enable(): + """Enable remote debugging.""" + Remote.on() + + @staticmethod + def Disable(): + """Disable remote debugging.""" + Remote.off() diff --git a/src/lib/Bcfg2/Server/Plugins/Hg.py b/src/lib/Bcfg2/Server/Plugins/Hg.py index 7554b4d52..3260908d8 100644 --- a/src/lib/Bcfg2/Server/Plugins/Hg.py +++ b/src/lib/Bcfg2/Server/Plugins/Hg.py @@ -1,5 +1,5 @@ -""" The Hg plugin provides a revision interface for Bcfg2 repos using -mercurial. """ +"""Revision interface for Bcfg2 repos using mercurial. +""" import sys from mercurial import ui, hg @@ -7,8 +7,9 @@ import Bcfg2.Server.Plugin class Hg(Bcfg2.Server.Plugin.Version): - """ The Hg plugin provides a revision interface for Bcfg2 repos - using mercurial. """ + """Revision interface for Bcfg2 repos using mercurial. + """ + __author__ = 'bcfg-dev@mcs.anl.gov' __vcs_metadata_path__ = ".hg" @@ -24,7 +25,7 @@ class Hg(Bcfg2.Server.Plugin.Version): repo = hg.repository(ui.ui(), repo_path) tip = repo.changelog.tip() return repo.changelog.rev(tip) - except: + except hg.error.RepoError: err = sys.exc_info()[1] msg = "Failed to read hg repository: %s" % err self.logger.error(msg) diff --git a/src/lib/Bcfg2/Server/Plugins/Metadata.py b/src/lib/Bcfg2/Server/Plugins/Metadata.py index 26f39e50d..b850c1870 100644 --- a/src/lib/Bcfg2/Server/Plugins/Metadata.py +++ b/src/lib/Bcfg2/Server/Plugins/Metadata.py @@ -7,7 +7,6 @@ import sys import time import copy import errno -import fcntl import socket import logging import lxml.etree @@ -201,15 +200,7 @@ class XMLMetadataConfig(Bcfg2.Server.Plugin.XMLFileBacked): while locked(fd): pass - try: - datafile.write(newcontents) - except: - fcntl.lockf(fd, fcntl.LOCK_UN) - msg = "Metadata: Failed to write new xml data to %s: %s" % \ - (tmpfile, sys.exc_info()[1]) - self.logger.error(msg, exc_info=1) - os.unlink(tmpfile) - raise Bcfg2.Server.Plugin.MetadataRuntimeError(msg) + datafile.write(newcontents) datafile.close() # check if clients.xml is a symlink if os.path.islink(fname): @@ -217,10 +208,10 @@ class XMLMetadataConfig(Bcfg2.Server.Plugin.XMLFileBacked): try: os.rename(tmpfile, fname) - except: # pylint: disable=W0702 + except OSError: try: os.unlink(tmpfile) - except: # pylint: disable=W0702 + except OSError: pass msg = "Metadata: Failed to rename %s: %s" % (tmpfile, sys.exc_info()[1]) @@ -594,14 +585,8 @@ class Metadata(Bcfg2.Server.Plugin.Metadata, def _handle_file(self, fname): """ set up the necessary magic for handling a metadata file (clients.xml or groups.xml, e.g.) """ - try: - Bcfg2.Server.FileMonitor.get_fam().AddMonitor( - os.path.join(self.data, fname), self) - except: - err = sys.exc_info()[1] - msg = "Unable to add file monitor for %s: %s" % (fname, err) - self.logger.error(msg) - raise Bcfg2.Server.Plugin.PluginInitError(msg) + Bcfg2.Server.FileMonitor.get_fam().AddMonitor( + os.path.join(self.data, fname), self) self.states[fname] = False xmlcfg = XMLMetadataConfig(self, fname) aname = re.sub(r'[^A-z0-9_]', '_', os.path.basename(fname)) diff --git a/src/lib/Bcfg2/Server/Plugins/Ohai.py b/src/lib/Bcfg2/Server/Plugins/Ohai.py index c5fb46c97..461be9ba8 100644 --- a/src/lib/Bcfg2/Server/Plugins/Ohai.py +++ b/src/lib/Bcfg2/Server/Plugins/Ohai.py @@ -51,7 +51,7 @@ class OhaiCache(object): if item not in self.cache: try: data = open(self.hostpath(item)).read() - except: + except IOError: raise KeyError(item) self.cache[item] = json.loads(data) return self.cache[item] @@ -61,7 +61,7 @@ class OhaiCache(object): del self.cache[item] try: os.unlink(self.hostpath(item)) - except: + except OSError: raise IndexError("Could not unlink %s: %s" % (self.hostpath(item), sys.exc_info()[1])) diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py index cfabd8457..7de79e2f3 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Apt.py @@ -89,7 +89,7 @@ class AptSource(Source): bprov[barch] = dict() try: reader = gzip.GzipFile(fname) - except: + except IOError: self.logger.error("Packages: Failed to read file %s" % fname) raise for line in reader.readlines(): diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py b/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py index 5f4d2ea41..0e15d2e15 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Pac.py @@ -67,7 +67,7 @@ class PacSource(Source): try: self.debug_log("Packages: try to read %s" % fname) tar = tarfile.open(fname, "r") - except: + except (IOError, tarfile.TarError): self.logger.error("Packages: Failed to read file %s" % fname) raise diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py b/src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py index e393cabfe..736cdcdd4 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Pkgng.py @@ -71,7 +71,7 @@ class PkgngSource(Source): try: tar = tarfile.open(fileobj=lzma.LZMAFile(fname)) reader = tar.extractfile('packagesite.yaml') - except: + except (IOError, tarfile.TarError): self.logger.error("Packages: Failed to read file %s" % fname) raise for line in reader.readlines(): diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py index 67ada2399..c9f6ea14a 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/Source.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Source.py @@ -331,10 +331,11 @@ class Source(Debuggable): # pylint: disable=R0902 @track_statistics() def setup_data(self, force_update=False): - """ Perform all data fetching and setup tasks. For most - backends, this involves downloading all metadata from the - repository, parsing it, and caching the parsed data locally. - The order of operations is: + """Perform all data fetching and setup tasks. + + For most backends, this involves downloading all metadata from + the repository, parsing it, and caching the parsed data + locally. The order of operations is: #. Call :func:`load_state` to try to load data from the local cache. @@ -352,6 +353,15 @@ class Source(Debuggable): # pylint: disable=R0902 upstream repository. :type force_update: bool """ + # there are a bunch of wildcard except statements here, + # because the various functions called herein (``load_state``, + # ``read_files``, ``update``) are defined entirely by the + # Packages plugins that implement them. + # + # TODO: we should define an exception subclass that each of + # these functions can raise when an *expected* error condition + # is encountered. + # # pylint: disable=W0702 if not force_update: if os.path.exists(self.cachefile): diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py index b6e9f13eb..dbe3f9ce5 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py @@ -631,10 +631,6 @@ class YumCollection(Collection): err = sys.exc_info()[1] self.logger.error("Packages: Could not contact Pulp server: %s" % err) - except: - err = sys.exc_info()[1] - self.logger.error("Packages: Unknown error querying Pulp server: " - "%s" % err) return consumer @track_statistics() @@ -1034,10 +1030,6 @@ class YumSource(Source): err = sys.exc_info()[1] raise SourceInitError("Could not contact Pulp server: %s" % err) - except: - err = sys.exc_info()[1] - raise SourceInitError("Unknown error querying Pulp server: %s" - % err) self.rawurl = "%s/%s" % (PULPCONFIG.cds['baseurl'], self.repo['relative_path']) self.arches = [self.repo['arch']] diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py b/src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py index f26d6ba18..b2e43bde7 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/YumHelper.py @@ -285,8 +285,8 @@ class HelperSubcommand(Bcfg2.Options.Subcommand): def run(self, setup): try: data = json.loads(sys.stdin.read()) - except: # pylint: disable=W0702 - self.logger.error("Unexpected error decoding JSON input: %s" % + except ValueError: + self.logger.error("Error decoding JSON input: %s" % sys.exc_info()[1]) print(json.dumps(self.fallback)) return 2 diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py index 87d42fd1c..3aa5c415f 100644 --- a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py +++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py @@ -515,10 +515,6 @@ class Packages(Bcfg2.Server.Plugin.Plugin, err = sys.exc_info()[1] self.logger.error("Packages: Error writing %s to %s: " "%s" % (key, localfile, err)) - except: - err = sys.exc_info()[1] - self.logger.error("Packages: Unknown error fetching " - "%s: %s" % (key, err)) for kfile in glob.glob(os.path.join(self.keypath, "*")): if kfile not in keyfiles: diff --git a/src/lib/Bcfg2/Server/Plugins/Pkgmgr.py b/src/lib/Bcfg2/Server/Plugins/Pkgmgr.py index c7d8986ed..7c6ab0ed7 100644 --- a/src/lib/Bcfg2/Server/Plugins/Pkgmgr.py +++ b/src/lib/Bcfg2/Server/Plugins/Pkgmgr.py @@ -35,7 +35,7 @@ class FuzzyDict(dict): def get(self, key, default=None): try: return self.__getitem__(key) - except: + except KeyError: if default: return default raise @@ -182,11 +182,7 @@ class PNode(object): """Return a dictionary of package mappings.""" if self.predicate(metadata, entry): for key in self.contents: - try: - data[key].update(self.contents[key]) - except: # pylint: disable=W0702 - data[key] = FuzzyDict() - data[key].update(self.contents[key]) + data.setdefault(key, FuzzyDict).update(self.contents[key]) for child in self.children: child.Match(metadata, data) diff --git a/src/lib/Bcfg2/Server/Plugins/Probes.py b/src/lib/Bcfg2/Server/Plugins/Probes.py index 21d50ace6..76aab69b5 100644 --- a/src/lib/Bcfg2/Server/Plugins/Probes.py +++ b/src/lib/Bcfg2/Server/Plugins/Probes.py @@ -405,7 +405,7 @@ class ProbeSet(Bcfg2.Server.Plugin.EntrySet): else: try: probe.text = entry.data - except: # pylint: disable=W0702 + except ValueError: self.logger.error("Client unable to handle unicode " "probes. Skipping %s" % probe.get('name')) @@ -447,12 +447,7 @@ class Probes(Bcfg2.Server.Plugin.Probing, Bcfg2.Server.Plugin.Connector.__init__(self) Bcfg2.Server.Plugin.DatabaseBacked.__init__(self, core) - try: - self.probes = ProbeSet(self.data, self.name) - except: - err = sys.exc_info()[1] - raise Bcfg2.Server.Plugin.PluginInitError(err) - + self.probes = ProbeSet(self.data, self.name) if self._use_db: self.probestore = DBProbeStore(core, self.data) else: diff --git a/src/lib/Bcfg2/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py index 28400f6d2..c4dd75e60 100644 --- a/src/lib/Bcfg2/Server/Plugins/Properties.py +++ b/src/lib/Bcfg2/Server/Plugins/Properties.py @@ -79,13 +79,12 @@ class PropertyFile(object): class JSONPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile): - """ Handle JSON Properties files. """ + """Handle JSON Properties files.""" def __init__(self, name): Bcfg2.Server.Plugin.FileBacked.__init__(self, name) PropertyFile.__init__(self, name) self.json = None - __init__.__doc__ = Bcfg2.Server.Plugin.FileBacked.__init__.__doc__ def Index(self): try: @@ -94,21 +93,18 @@ class JSONPropertyFile(Bcfg2.Server.Plugin.FileBacked, PropertyFile): err = sys.exc_info()[1] raise PluginExecutionError("Could not load JSON data from %s: %s" % (self.name, err)) - Index.__doc__ = Bcfg2.Server.Plugin.FileBacked.Index.__doc__ def _write(self): json.dump(self.json, open(self.name, 'wb')) return True - _write.__doc__ = PropertyFile._write.__doc__ def validate_data(self): try: json.dumps(self.json) - except: + except TypeError: err = sys.exc_info()[1] raise PluginExecutionError("Data for %s cannot be dumped to JSON: " "%s" % (self.name, err)) - validate_data.__doc__ = PropertyFile.validate_data.__doc__ def __str__(self): return str(self.json) diff --git a/src/lib/Bcfg2/Server/Plugins/Reporting.py b/src/lib/Bcfg2/Server/Plugins/Reporting.py index 282de8247..5c73546b4 100644 --- a/src/lib/Bcfg2/Server/Plugins/Reporting.py +++ b/src/lib/Bcfg2/Server/Plugins/Reporting.py @@ -3,7 +3,6 @@ import sys import time import platform -import traceback import lxml.etree import Bcfg2.Options from Bcfg2.Reporting.Transport.base import TransportError @@ -102,11 +101,11 @@ class Reporting(Statistics, Threaded, PullSource): except TransportError: continue except: - self.logger.error("%s: Attempt %s: Failed to add statistic %s" + self.logger.error("%s: Attempt %s: Failed to add statistic: %s" % (self.__class__.__name__, i, - traceback.format_exc().splitlines()[-1])) - self.logger.error("%s: Retry limit reached for %s" % - (self.__class__.__name__, client.hostname)) + sys.exc_info()[1])) + raise PluginExecutionError("%s: Retry limit reached for %s" % + (self.__class__.__name__, client.hostname)) def shutdown(self): super(Reporting, self).shutdown() diff --git a/src/lib/Bcfg2/Server/Plugins/SSHbase.py b/src/lib/Bcfg2/Server/Plugins/SSHbase.py index 89c7107aa..e4fb9b565 100644 --- a/src/lib/Bcfg2/Server/Plugins/SSHbase.py +++ b/src/lib/Bcfg2/Server/Plugins/SSHbase.py @@ -199,20 +199,19 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, newnames.add(name.split('.')[0]) try: newips.update(self.get_ipcache_entry(name)[0]) - except: # pylint: disable=W0702 + except PluginExecutionError: continue names[cmeta.hostname].update(newnames) names[cmeta.hostname].update(cmeta.addresses) names[cmeta.hostname].update(newips) # TODO: Only perform reverse lookups on IPs if an # option is set. - if True: - for ip in newips: - try: - names[cmeta.hostname].update( - self.get_namecache_entry(ip)) - except: # pylint: disable=W0702 - continue + for ip in newips: + try: + names[cmeta.hostname].update( + self.get_namecache_entry(ip)) + except socket.gaierror: + continue names[cmeta.hostname] = sorted(names[cmeta.hostname]) pubkeys = [pubk for pubk in list(self.entries.keys()) @@ -309,7 +308,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, (event.filename, action)) def get_ipcache_entry(self, client): - """ Build a cache of dns results. """ + """Build a cache of dns results.""" if client in self.ipcache: if self.ipcache[client]: return self.ipcache[client] diff --git a/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py b/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py index 047fc062e..cec2de297 100644 --- a/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py +++ b/src/lib/Bcfg2/Server/Plugins/TemplateHelper.py @@ -55,6 +55,10 @@ class HelperModule(Debuggable): module = imp.load_source(safe_module_name(self._module_name), self.name) except: # pylint: disable=W0702 + # this needs to be a blanket except because the + # imp.load_source() call can raise literally any error, + # since it imports the module and just passes through any + # exceptions raised. err = sys.exc_info()[1] self.logger.error("TemplateHelper: Failed to import %s: %s" % (self.name, err)) diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py index b667d417a..e2636908e 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py @@ -129,7 +129,7 @@ class TestCfgGenshiGenerator(TestCfgGenerator): encoding=Bcfg2.Options.setup.encoding) cgg.loader.reset_mock() - cgg.loader.load.side_effect = OSError + cgg.loader.load.side_effect = TemplateError("test") self.assertRaises(PluginExecutionError, cgg.handle_event, event) cgg.loader.load.assert_called_with( diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py index d64bbaabf..45945911f 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py @@ -2,6 +2,7 @@ import os import sys import lxml.etree from mock import Mock, MagicMock, patch +from Bcfg2.Compat import StringIO from Bcfg2.Server.Plugins.Cfg import CfgCreationError from Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator import * from Bcfg2.Server.Plugin import PluginExecutionError @@ -29,24 +30,25 @@ class TestCfgPrivateKeyCreator(TestXMLCfgCreator): test_obj = CfgPrivateKeyCreator should_monitor = False + def setUp(self): + TestXMLCfgCreator.setUp(self) + set_setup_default("cfg_category", "category") + @patch("Bcfg2.Server.Plugins.Cfg.CfgPublicKeyCreator.get_cfg", Mock()) def get_obj(self, name=None, fam=None): return TestXMLCfgCreator.get_obj(self, name=name) @patch("shutil.rmtree") - @patch("tempfile.mkdtemp") - def test__gen_keypair(self, mock_mkdtemp, mock_rmtree): + def _gen_keypair(self, mock_mkdtemp, mock_rmtree): pkc = self.get_obj() pkc.cmd = Mock() pkc.XMLMatch = Mock() - mock_mkdtemp.return_value = datastore metadata = Mock() exc = Mock() exc.success = True pkc.cmd.run.return_value = exc - spec = lxml.etree.Element("PrivateKey") pkc.XMLMatch.return_value = spec def reset(): @@ -82,52 +84,62 @@ class TestCfgPrivateKeyCreator(TestXMLCfgCreator): mock_rmtree.assert_called_with(datastore) @patch("shutil.rmtree") + @patch("tempfile.mkdtemp") @patch("%s.open" % builtins) - def test_create_data(self, mock_open, mock_rmtree): - pkc = self.get_obj() - pkc.XMLMatch = Mock() - pkc.get_specificity = Mock() - # in order to make ** magic work in older versions of python, - # get_specificity() must return an actual dict, not just a - # Mock object that works like a dict. in order to test that - # the get_specificity() return value is being used - # appropriately, we put some dummy data in it and test for - # that data - pkc.get_specificity.side_effect = lambda m: dict(group="foo") - pkc._gen_keypair = Mock() - privkey = os.path.join(datastore, "privkey") - pkc._gen_keypair.return_value = privkey - pkc.pubkey_creator = Mock() - pkc.pubkey_creator.get_filename.return_value = "pubkey.filename" + def _create_private_key(self, expected, mock_open, mock_mkdtemp, + mock_rmtree, spec=None): + pkc = self.get_obj(name="/home/foo/.ssh/id_rsa/privkey.xml") + pkc.cmd = MockExecutor() + pkc.pubkey_creator.write_data = Mock() pkc.write_data = Mock() + mock_mkdtemp.return_value = datastore + + if spec is None: + pkc.xdata = lxml.etree.Element("PrivateKey") + else: + pkc.xdata = spec + + privkey_filename = os.path.join(datastore, "privkey") + pubkey_filename = os.path.join(datastore, "privkey.pub") entry = lxml.etree.Element("Path", name="/home/foo/.ssh/id_rsa") metadata = Mock() + metadata.group_in_category.return_value = "foo" - def open_read_rv(): - mock_open.return_value.read.side_effect = lambda: "privatekey" - return "ssh-rsa publickey foo@bar.com" + def open_key(fname): + if fname == privkey_filename: + return StringIO("privatekey") + elif fname == pubkey_filename: + return StringIO("ssh-rsa publickey foo@bar.com") + else: + self.fail("Unexpected open call: %s" % fname) - def reset(): - mock_open.reset_mock() - mock_rmtree.reset_mock() - pkc.XMLMatch.reset_mock() - pkc.get_specificity.reset_mock() - pkc._gen_keypair.reset_mock() - pkc.pubkey_creator.reset_mock() - pkc.write_data.reset_mock() - mock_open.return_value.read.side_effect = open_read_rv + mock_open.side_effect = open_key - reset() self.assertEqual(pkc.create_data(entry, metadata), "privatekey") - pkc.XMLMatch.assert_called_with(metadata) - pkc.get_specificity.assert_called_with(metadata) - pkc._gen_keypair.assert_called_with(metadata, - pkc.XMLMatch.return_value) self.assertItemsEqual(mock_open.call_args_list, - [call(privkey + ".pub"), call(privkey)]) - pkc.pubkey_creator.get_filename.assert_called_with(group="foo") + [call(pubkey_filename), call(privkey_filename)]) + self.assertItemsEqual( + pkc.cmd.calls[0]['command'], + ['ssh-keygen', '-f', privkey_filename] + expected) + metadata.group_in_category.assert_called_with("category") pkc.pubkey_creator.write_data.assert_called_with( - "ssh-rsa publickey pubkey.filename\n", group="foo") - pkc.write_data.assert_called_with("privatekey", group="foo") + "ssh-rsa publickey /home/foo/.ssh/id_rsa.pub/id_rsa.pub.G50_foo\n", + group="foo", prio=50) + pkc.write_data.assert_called_with("privatekey", group="foo", prio=50) mock_rmtree.assert_called_with(datastore) + + def test_create_data(self): + pass + + def test_create_private_key_defaults(self): + self._create_private_key(['-t', 'rsa', '-N', '']) + + def test_create_private_key_spec(self): + spec = lxml.etree.Element("PrivateKey") + lxml.etree.SubElement(spec, "Params", bits="768", type="dsa") + passphrase = lxml.etree.SubElement(spec, "Passphrase") + passphrase.text = "foo" + + self._create_private_key(['-t', 'dsa', '-b', '768', '-N', 'foo'], + spec=spec) diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py index 1b55beded..507f0c9c2 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py @@ -726,7 +726,7 @@ class TestCfgEntrySet(TestEntrySet): # test failure to create data reset() - creator.create_data.side_effect = OSError + creator.create_data.side_effect = CfgCreationError self.assertRaises(PluginExecutionError, eset._create_data, entry, metadata) diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py index d3fa15236..f2721c9ea 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestMetadata.py @@ -519,13 +519,6 @@ class TestMetadata(_TestMetadata, TestClientRunHooks, TestDatabaseBacked): os.path.join(metadata.data, "clients.xml"), metadata) - mock_get_fam.reset_mock() - fam = Mock() - fam.AddMonitor = Mock(side_effect=IOError) - mock_get_fam.return_value = fam - self.assertRaises(Bcfg2.Server.Plugin.PluginInitError, - self.get_obj, core=core) - @patch('os.makedirs', Mock()) @patch('%s.open' % builtins) def test_init_repo(self, mock_open): diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py index 159dc6e66..36baee899 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py @@ -124,7 +124,7 @@ class TestJSONPropertyFile(TestFileBacked, TestPropertyFile): mock_dumps.assert_called_with(pf.json) mock_dumps.reset_mock() - mock_dumps.side_effect = ValueError + mock_dumps.side_effect = TypeError self.assertRaises(PluginExecutionError, pf.validate_data) mock_dumps.assert_called_with(pf.json) diff --git a/testsuite/Testsrc/test_code_checks.py b/testsuite/Testsrc/test_code_checks.py index 79eff7959..ba4b19d1c 100644 --- a/testsuite/Testsrc/test_code_checks.py +++ b/testsuite/Testsrc/test_code_checks.py @@ -49,7 +49,10 @@ contingent_checks = { ["CfgEncryptedCheetahGenerator.py"]}, ("M2Crypto", "jinja2"): {"lib/Bcfg2/Server/Plugins/Cfg": ["CfgEncryptedJinja2Generator.py"]}, - } + ("mercurial",): {"lib/Bcfg2/Server/Plugins": ["Hg.py"]}, + ("guppy",): {"lib/Bcfg2/Server/Plugins": ["Guppy.py"]}, + ("boto",): {"lib/Bcfg2/Server/Plugins": ["AWSTags.py"]}, +} # perform only error checking on the listed files error_checks = { diff --git a/testsuite/common.py b/testsuite/common.py index fc2397560..4c7337e0d 100644 --- a/testsuite/common.py +++ b/testsuite/common.py @@ -14,6 +14,7 @@ import sys import codecs import lxml.etree import Bcfg2.Options +import Bcfg2.Utils from mock import patch, MagicMock, _patch, DEFAULT try: from unittest2 import skip, skipIf, skipUnless, TestCase @@ -119,6 +120,29 @@ else: return codecs.unicode_escape_decode(s)[0] +class MockExecutor(object): + """mock object for :class:`Bcfg2.Utils.Executor` objects.""" + def __init__(self, timeout=None): + self.timeout = timeout + + # variables that can be set to control the result returned + self.stdout = '' + self.stderr = '' + self.retval = 0 + + # variables that record how run() was called + self.calls = [] + + def run(self, command, inputdata=None, timeout=None, **kwargs): + self.calls.append({"command": command, + "inputdata": inputdata, + "timeout": timeout or self.timeout, + "kwargs": kwargs}) + + return Bcfg2.Utils.ExecutorResult(self.stdout, self.stderr, + self.retval) + + class Bcfg2TestCase(TestCase): """ Base TestCase class that inherits from :class:`unittest.TestCase`. This class adds @@ -138,6 +162,9 @@ class Bcfg2TestCase(TestCase): if cls.capture_stderr: sys.stderr = cls._stderr + if hasattr(TestCase, "assertCountEqual"): + assertItemsEqual = assertCountEqual + def assertXMLEqual(self, el1, el2, msg=None): """ Test that the two XML trees given are equal. """ if msg is None: diff --git a/testsuite/install.sh b/testsuite/install.sh index bbbd9ae76..42d5bbadb 100755 --- a/testsuite/install.sh +++ b/testsuite/install.sh @@ -17,7 +17,7 @@ if [[ "$WITH_OPTIONAL_DEPS" == "yes" ]]; then sudo apt-get install -y yum libaugeas0 augeas-lenses libacl1-dev libssl-dev pip install --use-mirrors PyYAML pyinotify boto pylibacl 'django<1.5' \ - Jinja2 + Jinja2 mercurial guppy easy_install https://fedorahosted.org/released/python-augeas/python-augeas-0.4.1.tar.gz if [[ ${PYVER:0:1} == "2" ]]; then # django supports py3k, but South doesn't, and the django bits -- cgit v1.2.3-1-g7c22