summaryrefslogtreecommitdiffstats
path: root/src/sbin
diff options
context:
space:
mode:
Diffstat (limited to 'src/sbin')
-rwxr-xr-xsrc/sbin/bcfg2-admin1
-rwxr-xr-xsrc/sbin/bcfg2-crypt2
-rwxr-xr-xsrc/sbin/bcfg2-info48
-rwxr-xr-xsrc/sbin/bcfg2-reports12
-rwxr-xr-xsrc/sbin/bcfg2-server1
-rwxr-xr-xsrc/sbin/bcfg2-test231
-rwxr-xr-xsrc/sbin/bcfg2-yum-helper4
7 files changed, 229 insertions, 70 deletions
diff --git a/src/sbin/bcfg2-admin b/src/sbin/bcfg2-admin
index 3c63cd3f5..3bce7fdab 100755
--- a/src/sbin/bcfg2-admin
+++ b/src/sbin/bcfg2-admin
@@ -10,6 +10,7 @@ import Bcfg2.Options
import Bcfg2.Server.Admin
from Bcfg2.Compat import StringIO
+
def mode_import(modename):
"""Load Bcfg2.Server.Admin.<mode>."""
modname = modename.capitalize()
diff --git a/src/sbin/bcfg2-crypt b/src/sbin/bcfg2-crypt
index 810406567..f7deba90c 100755
--- a/src/sbin/bcfg2-crypt
+++ b/src/sbin/bcfg2-crypt
@@ -258,7 +258,7 @@ class Encryptor(object):
(self.pname, pname))
return (passphrase, pname)
- def _get_passphrase(self, chunk): # pylint: disable=W0613
+ def _get_passphrase(self, chunk): # pylint: disable=W0613
""" get the passphrase for a chunk of a file """
return None
diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info
index 287d0a161..ad35bbeeb 100755
--- a/src/sbin/bcfg2-info
+++ b/src/sbin/bcfg2-info
@@ -473,7 +473,7 @@ Bcfg2 client itself.""")
('Password', self.setup['password']),
('Server Metadata Connector', self.setup['mconnect']),
('Filemonitor', self.setup['filemonitor']),
- ('Server address', self.setup['location']),
+ ('Server address', self.setup['location']),
('Path to key', self.setup['key']),
('Path to SSL certificate', self.setup['cert']),
('Path to SSL CA certificate', self.setup['ca']),
@@ -640,21 +640,24 @@ Bcfg2 client itself.""")
if 'Packages' not in self.plugins:
print("Packages plugin not enabled")
return
+ self.plugins['Packages'].toggle_debug()
+
+ indep = lxml.etree.Element("Independent")
+ structures = [lxml.etree.Element("Bundle", name="packages")]
+ for arg in arglist[1:]:
+ lxml.etree.SubElement(structures[0], "Package", name=arg)
+
hostname = arglist[0]
- initial = arglist[1:]
metadata = self.build_metadata(hostname)
- self.plugins['Packages'].toggle_debug()
- collection = self.plugins['Packages'].get_collection(metadata)
- packages, unknown = collection.complete(initial)
- newpkgs = list(packages.difference(initial))
- print("%d initial packages" % len(initial))
- print(" %s" % "\n ".join(initial))
- print("%d new packages added" % len(newpkgs))
- if newpkgs:
- print(" %s" % "\n ".join(newpkgs))
- print("%d unknown packages" % len(unknown))
- if unknown:
- print(" %s" % "\n ".join(unknown))
+
+ # pylint: disable=W0212
+ self.plugins['Packages']._build_packages(metadata, indep, structures)
+ # pylint: enable=W0212
+
+ print("%d new packages added" % len(indep.getchildren()))
+ if len(indep.getchildren()):
+ print(" %s" % "\n ".join(lxml.etree.tostring(p)
+ for p in indep.getchildren()))
def do_packagesources(self, args):
""" packagesources <hostname> - Show package sources """
@@ -726,17 +729,19 @@ Bcfg2 client itself.""")
pass
-
def build_usage():
""" build usage message """
cmd_blacklist = ["do_loop", "do_EOF"]
usage = dict()
for attrname in dir(InfoCore):
attr = getattr(InfoCore, attrname)
- if (hasattr(attr, "__func__") and
- attr.__func__.func_name not in cmd_blacklist and
- attr.__func__.func_name.startswith("do_") and
- attr.__func__.func_doc):
+
+ # shim for python 2.4, __func__ is im_func
+ funcattr = getattr(attr, "__func__", getattr(attr, "im_func", None))
+ if (funcattr != None and
+ funcattr.func_name not in cmd_blacklist and
+ funcattr.func_name.startswith("do_") and
+ funcattr.func_doc):
usage[attr.__name__] = re.sub(r'\s+', ' ', attr.__doc__)
return "Commands:\n" + "\n".join(usage[k] for k in sorted(usage.keys()))
@@ -748,9 +753,8 @@ def main():
optinfo = dict(profile=Bcfg2.Options.CORE_PROFILE,
interactive=Bcfg2.Options.INTERACTIVE,
interpreter=Bcfg2.Options.INTERPRETER)
- optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS)
- optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS)
- setup = Bcfg2.Options.load_option_parser(optinfo)
+ optinfo.update(Bcfg2.Options.INFO_COMMON_OPTIONS)
+ setup = Bcfg2.Options.OptionParser(optinfo)
setup.hm = "\n".join([" bcfg2-info [options] [command <command args>]",
"Options:",
setup.buildHelpMessage(),
diff --git a/src/sbin/bcfg2-reports b/src/sbin/bcfg2-reports
index 9f2ff96c2..2c4a918be 100755
--- a/src/sbin/bcfg2-reports
+++ b/src/sbin/bcfg2-reports
@@ -10,7 +10,7 @@ from Bcfg2.Compat import ConfigParser
try:
import Bcfg2.settings
except ConfigParser.NoSectionError:
- print("Your bcfg2.conf is currently missing the statistics section which "
+ print("Your bcfg2.conf is currently missing the [database] section which "
"is necessary for the reporting interface. Please see bcfg2.conf(5) "
"for more details.")
sys.exit(1)
@@ -121,7 +121,7 @@ def main():
help="Show hosts that haven't run in the last 24 "
"hours")
parser.add_option_group(allhostmodes)
-
+
# entry modes
entrymodes = \
OptionGroup(parser, "Entry Modes",
@@ -166,7 +166,7 @@ def main():
(mode.get_opt_string(), opt.get_opt_string()))
mode = opt
mode_family = parser.get_option_group(opt.get_opt_string())
-
+
# you can specify more than one of --bad, --extra, --modified, --show, so
# consider single-host options separately
if not mode_family:
@@ -174,7 +174,7 @@ def main():
if getattr(options, opt.dest):
mode_family = parser.get_option_group(opt.get_opt_string())
break
-
+
if not mode_family:
parser.error("You must specify a mode")
@@ -243,7 +243,7 @@ def main():
parser.error("%s require either a list of entries on the "
"command line or the --file options" %
mode_family.title)
-
+
if options.badentry:
result = hosts_by_entry_type(clients, "bad", entries)
elif options.modifiedentry:
@@ -263,7 +263,7 @@ def main():
# todo batch fetch this. sqlite could break
for client in clients:
- ents = entry_cls.objects.filter(name=entries[0][1],
+ ents = entry_cls.objects.filter(name=entries[0][1],
interaction=client.current_interaction)
if len(ents) == 0:
continue
diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server
index 8e96d65f2..33ee327fc 100755
--- a/src/sbin/bcfg2-server
+++ b/src/sbin/bcfg2-server
@@ -11,6 +11,7 @@ from Bcfg2.Server.Core import CoreInitError
LOGGER = logging.getLogger('bcfg2-server')
+
def main():
optinfo = dict()
optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS)
diff --git a/src/sbin/bcfg2-test b/src/sbin/bcfg2-test
index 75079ec3f..510bb898b 100755
--- a/src/sbin/bcfg2-test
+++ b/src/sbin/bcfg2-test
@@ -5,29 +5,87 @@ without failures"""
import os
import sys
-import signal
import fnmatch
import logging
import Bcfg2.Logger
import Bcfg2.Server.Core
+from math import ceil
from nose.core import TestProgram
from nose.suite import LazySuite
from unittest import TestCase
+try:
+ from multiprocessing import Process, Queue, active_children
+ HAS_MULTIPROC = True
+except ImportError:
+ HAS_MULTIPROC = False
+ active_children = lambda: [] # pylint: disable=C0103
+
+
+class CapturingLogger(object):
+ """ Fake logger that captures logging output so that errors are
+ only displayed for clients that fail tests """
+ def __init__(self, *args, **kwargs): # pylint: disable=W0613
+ self.output = []
+
+ def error(self, msg):
+ """ discard error messages """
+ self.output.append(msg)
+
+ def warning(self, msg):
+ """ discard error messages """
+ self.output.append(msg)
+
+ def info(self, msg):
+ """ discard error messages """
+ self.output.append(msg)
+
+ def debug(self, msg):
+ """ discard error messages """
+ self.output.append(msg)
+
+ def reset_output(self):
+ """ Reset the captured output """
+ self.output = []
+
+
+class ClientTestFromQueue(TestCase):
+ """ A test case that tests a value that has been enqueued by a
+ child test process. ``client`` is the name of the client that has
+ been tested; ``result`` is the result from the :class:`ClientTest`
+ test. ``None`` indicates a successful test; a string value
+ indicates a failed test; and an exception indicates an error while
+ running the test. """
+ __test__ = False # Do not collect
+
+ def __init__(self, client, result):
+ TestCase.__init__(self)
+ self.client = client
+ self.result = result
+
+ def shortDescription(self):
+ return "Building configuration for %s" % self.client
+
+ def runTest(self):
+ """ parse the result from this test """
+ if isinstance(self.result, Exception):
+ raise self.result
+ assert self.result is None, self.result
+
class ClientTest(TestCase):
- """
- A test case representing the build of all of the configuration for
+ """ A test case representing the build of all of the configuration for
a single host. Checks that none of the build config entities has
had a failure when it is building. Optionally ignores some config
files that we know will cause errors (because they are private
- files we don't have access to, for instance)
- """
+ files we don't have access to, for instance) """
__test__ = False # Do not collect
+ divider = "-" * 70
- def __init__(self, bcfg2_core, client, ignore=None):
+ def __init__(self, core, client, ignore=None):
TestCase.__init__(self)
- self.bcfg2_core = bcfg2_core
+ self.core = core
+ self.core.logger = CapturingLogger()
self.client = client
if ignore is None:
self.ignore = dict()
@@ -52,13 +110,34 @@ class ClientTest(TestCase):
def runTest(self):
""" run this individual test """
- config = self.bcfg2_core.BuildConfiguration(self.client)
+ config = self.core.BuildConfiguration(self.client)
+ output = self.core.logger.output[:]
+ if output:
+ output.append(self.divider)
+ self.core.logger.reset_output()
+ # check for empty client configuration
assert len(config.findall("Bundle")) > 0, \
- "%s has no content" % self.client
+ "\n".join(output + ["%s has no content" % self.client])
+
+ # check for missing bundles
+ metadata = self.core.build_metadata(self.client)
+ sbundles = [el.get('name') for el in config.findall("Bundle")]
+ missing = [b for b in metadata.bundles if b not in sbundles]
+ assert len(missing) == 0, \
+ "\n".join(output + ["Configuration is missing bundle(s): %s" %
+ ':'.join(missing)])
+
+ # check for unknown packages
+ unknown_pkgs = [el.get("name")
+ for el in config.xpath('//Package[@type="unknown"]')
+ if not self.ignore_entry(el.tag, el.get("name"))]
+ assert len(unknown_pkgs) == 0, \
+ "Configuration contains unknown packages: %s" % \
+ ", ".join(unknown_pkgs)
failures = []
- msg = ["Failures:"]
+ msg = output + ["Failures:"]
for failure in config.xpath('//*[@failure]'):
if not self.ignore_entry(failure.tag, failure.get('name')):
failures.append(failure)
@@ -73,23 +152,46 @@ class ClientTest(TestCase):
id = __str__
-def get_sigint_handler(core):
- """ Get a function that handles SIGINT/Ctrl-C by shutting down the
- core and exiting properly."""
+def get_core(setup):
+ """ Get a server core, with events handled """
+ core = Bcfg2.Server.Core.BaseCore(setup)
+ core.fam.handle_events_in_interval(0.1)
+ return core
- def hdlr(sig, frame): # pylint: disable=W0613
- """ Handle SIGINT/Ctrl-C by shutting down the core and exiting
- properly. """
- core.shutdown()
- os._exit(1) # pylint: disable=W0212
- return hdlr
+def get_ignore(setup):
+ """ Given an options dict, get a dict of entry tags and names to
+ ignore errors from """
+ ignore = dict()
+ for entry in setup['test_ignore']:
+ tag, name = entry.split(":")
+ try:
+ ignore[tag].append(name)
+ except KeyError:
+ ignore[tag] = [name]
+ return ignore
-def main():
- optinfo = dict(noseopts=Bcfg2.Options.TEST_NOSEOPTS,
- test_ignore=Bcfg2.Options.TEST_IGNORE,
- validate=Bcfg2.Options.CFG_VALIDATION)
+def run_child(setup, clients, queue):
+ """ Run tests for the given clients in a child process, returning
+ results via the given Queue """
+ core = get_core(setup)
+ ignore = get_ignore(setup)
+ for client in clients:
+ try:
+ ClientTest(core, client, ignore).runTest()
+ queue.put((client, None))
+ except AssertionError:
+ queue.put((client, str(sys.exc_info()[1])))
+ except:
+ queue.put((client, sys.exc_info()[1]))
+
+ core.shutdown()
+
+
+def parse_args():
+ """ Parse command line arguments. """
+ optinfo = dict(Bcfg2.Options.TEST_COMMON_OPTIONS)
optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS)
optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS)
setup = Bcfg2.Options.load_option_parser(optinfo)
@@ -109,37 +211,88 @@ def main():
to_syslog=False,
to_file=setup['logging'],
level=level)
+ logger = logging.getLogger(sys.argv[0])
if (setup['debug'] or setup['verbose']) and "-v" not in setup['noseopts']:
setup['noseopts'].append("-v")
- core = Bcfg2.Server.Core.BaseCore()
- signal.signal(signal.SIGINT, get_sigint_handler(core))
+ if setup['children'] and not HAS_MULTIPROC:
+ logger.warning("Python multiprocessing library not found, running "
+ "with no children")
+ setup['children'] = 0
- ignore = dict()
- for entry in setup['test_ignore']:
- tag, name = entry.split(":")
- try:
- ignore[tag].append(name)
- except KeyError:
- ignore[tag] = [name]
+ if (setup['children'] and ('--with-xunit' in setup['noseopts'] or
+ '--xunit-file' in setup['noseopts'])):
+ logger.warning("Use the --xunit option to bcfg2-test instead of the "
+ "--with-xunit or --xunit-file options to nosetest")
+ xunitfile = None
+ if '--with-xunit' in setup['noseopts']:
+ setup['noseopts'].remove('--with-xunit')
+ xunitfile = "nosetests.xml"
+ if '--xunit-file' in setup['noseopts']:
+ idx = setup['noseopts'].index('--xunit-file')
+ try:
+ setup['noseopts'].pop(idx) # remove --xunit-file
+ # remove the argument to it
+ xunitfile = setup['noseopts'].pop(idx)
+ except IndexError:
+ pass
+ if xunitfile and not setup['xunit']:
+ setup['xunit'] = xunitfile
+ return setup
- core.fam.handle_events_in_interval(0.1)
+
+def main():
+ setup = parse_args()
+ logger = logging.getLogger(sys.argv[0])
+ core = get_core(setup)
if setup['args']:
clients = setup['args']
else:
clients = core.metadata.clients
- def run_tests():
- """ Run the test suite """
- for client in clients:
- yield ClientTest(core, client, ignore)
+ ignore = get_ignore(setup)
- TestProgram(argv=sys.argv[0:1] + setup['noseopts'],
- suite=LazySuite(run_tests))
+ if setup['children']:
+ if setup['children'] > len(clients):
+ logger.info("Refusing to spawn more children than clients to test,"
+ " setting children=%s" % len(clients))
+ setup['children'] = len(clients)
+ perchild = int(ceil(len(clients) / float(setup['children'] + 1)))
+ queue = Queue()
+ for child in range(setup['children']):
+ start = child * perchild
+ end = (child + 1) * perchild
+ child = Process(target=run_child,
+ args=(setup, clients[start:end], queue))
+ child.start()
+
+ def generate_tests():
+ """ Read test results for the clients """
+ start = setup['children'] * perchild
+ for client in clients[start:]:
+ yield ClientTest(core, client, ignore)
+
+ for i in range(start): # pylint: disable=W0612
+ yield ClientTestFromQueue(*queue.get())
+ else:
+ def generate_tests():
+ """ Run tests for the clients """
+ for client in clients:
+ yield ClientTest(core, client, ignore)
+
+ TestProgram(argv=sys.argv[:1] + core.setup['noseopts'],
+ suite=LazySuite(generate_tests), exit=False)
+
+ # block until all children have completed -- should be
+ # immediate since we've already gotten all the results we
+ # expect
+ for child in active_children():
+ child.join()
core.shutdown()
os._exit(0) # pylint: disable=W0212
+
if __name__ == "__main__":
sys.exit(main())
diff --git a/src/sbin/bcfg2-yum-helper b/src/sbin/bcfg2-yum-helper
index ba6f30406..7e5c03fd5 100755
--- a/src/sbin/bcfg2-yum-helper
+++ b/src/sbin/bcfg2-yum-helper
@@ -129,7 +129,7 @@ class DepSolver(object):
err = sys.exc_info()[1]
self.logger.warning(err)
return []
-
+
if ptype == "default":
return [p
for p, d in list(group.default_packages.items())
@@ -254,6 +254,6 @@ def main():
rv[gdata['group']] = list(packages)
print(json.dumps(rv))
-
+
if __name__ == '__main__':
sys.exit(main())