diff options
115 files changed, 1347 insertions, 825 deletions
diff --git a/doc/development/index.txt b/doc/development/index.txt index 352000dc8..2a54bfad8 100644 --- a/doc/development/index.txt +++ b/doc/development/index.txt @@ -39,3 +39,4 @@ git access. Mail the :ref:`help-mailinglist` for details. testing documentation docstyleguide + unit-testing diff --git a/doc/development/plugins.txt b/doc/development/plugins.txt index 15b512365..b2b70f553 100644 --- a/doc/development/plugins.txt +++ b/doc/development/plugins.txt @@ -164,7 +164,6 @@ Example Connector Bcfg2.Server.Plugin.Connector): '''The Foo plugin is here to illustrate a barebones connector''' name = 'Foo' - version = '$Revision: $' experimental = True def __init__(self, core, datastore): @@ -195,13 +194,10 @@ do so. We will call our new plugin `MyMetadata`. .. code-block:: python - __revision__ = '$Revision$' - import Bcfg2.Server.Plugins.Metadata class MyMetadata(Bcfg2.Server.Plugins.Metadata.Metadata): '''This class contains data for bcfg2 server metadata''' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore, watch_clients=True): diff --git a/doc/development/unit-testing.txt b/doc/development/unit-testing.txt new file mode 100644 index 000000000..30217dcc5 --- /dev/null +++ b/doc/development/unit-testing.txt @@ -0,0 +1,25 @@ +.. -*- mode: rst -*- + +.. _development-unit-testing: + +================== +Bcfg2 unit testing +================== + +.. _Python Mock Module: http://python-mock.sourceforge.net/ +.. _Python Nose: http://readthedocs.org/docs/nose/en/latest/ + +You will first need to install the `Python Mock Module`_ and `Python +Nose`_ modules. You can then run the existing tests with the +following.:: + + cd testsuite + nosetests + +You should see output something like the following:: + + .................................................. + ---------------------------------------------------------------------- + Ran 50 tests in 0.121s + + OK diff --git a/schemas/base.xsd b/schemas/base.xsd index 91b7ac8f5..cca665b38 100644 --- a/schemas/base.xsd +++ b/schemas/base.xsd @@ -4,7 +4,6 @@ <xsd:documentation> base schema for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/clients.xsd b/schemas/clients.xsd index 0a9ce5202..9e8d5a22a 100644 --- a/schemas/clients.xsd +++ b/schemas/clients.xsd @@ -4,7 +4,6 @@ <xsd:documentation> client schema for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/decisions.xsd b/schemas/decisions.xsd index a354ec8cb..30115b367 100644 --- a/schemas/decisions.xsd +++ b/schemas/decisions.xsd @@ -4,7 +4,6 @@ <xsd:documentation> decision list schema for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/defaults.xsd b/schemas/defaults.xsd index 27e749470..c7e2edc7e 100644 --- a/schemas/defaults.xsd +++ b/schemas/defaults.xsd @@ -4,7 +4,6 @@ <xsd:documentation> string enumeration definitions for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/genshi.xsd b/schemas/genshi.xsd index 088ec56be..35d81e2f1 100644 --- a/schemas/genshi.xsd +++ b/schemas/genshi.xsd @@ -8,7 +8,6 @@ <xs:documentation> Genshi schema Chris St. Pierre - $Id$ </xs:documentation> </xs:annotation> diff --git a/schemas/metadata.xsd b/schemas/metadata.xsd index 58f9e8029..f79039d25 100644 --- a/schemas/metadata.xsd +++ b/schemas/metadata.xsd @@ -5,7 +5,6 @@ <xsd:documentation> metadata schema for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/pathentry.xsd b/schemas/pathentry.xsd index 40aa4ff2b..21a417125 100644 --- a/schemas/pathentry.xsd +++ b/schemas/pathentry.xsd @@ -5,7 +5,6 @@ <xsd:documentation> path entry schema for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/pkglist.xsd b/schemas/pkglist.xsd index c16ed654e..c0d449f54 100644 --- a/schemas/pkglist.xsd +++ b/schemas/pkglist.xsd @@ -4,7 +4,6 @@ <xsd:documentation> package list schema for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/pkgtype.xsd b/schemas/pkgtype.xsd index 3655c646b..9ddc0682e 100644 --- a/schemas/pkgtype.xsd +++ b/schemas/pkgtype.xsd @@ -5,7 +5,6 @@ <xsd:documentation> package list schema for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/rules.xsd b/schemas/rules.xsd index 06c3b38ac..924792b18 100644 --- a/schemas/rules.xsd +++ b/schemas/rules.xsd @@ -5,7 +5,6 @@ <xsd:documentation> string enumeration definitions for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/services.xsd b/schemas/services.xsd index 828959e82..b91e851d2 100644 --- a/schemas/services.xsd +++ b/schemas/services.xsd @@ -4,7 +4,6 @@ <xsd:documentation> services schema for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/servicetype.xsd b/schemas/servicetype.xsd index 6b497383d..af5bc64a6 100644 --- a/schemas/servicetype.xsd +++ b/schemas/servicetype.xsd @@ -5,7 +5,6 @@ <xsd:documentation> services schema for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/schemas/types.xsd b/schemas/types.xsd index 689e693b7..ead377192 100644 --- a/schemas/types.xsd +++ b/schemas/types.xsd @@ -4,7 +4,6 @@ <xsd:documentation> string enumeration definitions for bcfg2 Narayan Desai, Argonne National Laboratory - $Id$ </xsd:documentation> </xsd:annotation> diff --git a/src/lib/Client/Frame.py b/src/lib/Client/Frame.py index d17f70f1b..67222f245 100644 --- a/src/lib/Client/Frame.py +++ b/src/lib/Client/Frame.py @@ -2,7 +2,6 @@ Frame is the Client Framework that verifies and installs entries, and generates statistics. """ -__revision__ = '$Revision$' import logging import sys diff --git a/src/lib/Client/Tools/APK.py b/src/lib/Client/Tools/APK.py index 6a6fd51b3..aaaf2472f 100644 --- a/src/lib/Client/Tools/APK.py +++ b/src/lib/Client/Tools/APK.py @@ -1,5 +1,4 @@ """This provides Bcfg2 support for Alpine Linux APK packages.""" -__revision__ = '$Revision$' import Bcfg2.Client.Tools diff --git a/src/lib/Client/Tools/APT.py b/src/lib/Client/Tools/APT.py index db8cd56e7..6b839ffbc 100644 --- a/src/lib/Client/Tools/APT.py +++ b/src/lib/Client/Tools/APT.py @@ -1,5 +1,4 @@ """This is the Bcfg2 support for apt-get.""" -__revision__ = '$Revision$' # suppress apt API warnings import warnings diff --git a/src/lib/Client/Tools/Action.py b/src/lib/Client/Tools/Action.py index c089cde1d..dc49347e9 100644 --- a/src/lib/Client/Tools/Action.py +++ b/src/lib/Client/Tools/Action.py @@ -1,5 +1,4 @@ """Action driver""" -__revision__ = '$Revision$' import Bcfg2.Client.Tools from Bcfg2.Client.Frame import matches_white_list, passes_black_list diff --git a/src/lib/Client/Tools/Blast.py b/src/lib/Client/Tools/Blast.py index 29cdfa116..5d5e74ab2 100644 --- a/src/lib/Client/Tools/Blast.py +++ b/src/lib/Client/Tools/Blast.py @@ -1,6 +1,4 @@ -# This is the bcfg2 support for blastwave packages (pkg-get) """This provides Bcfg2 support for Blastwave.""" -__revision__ = '$Revision$' import tempfile import Bcfg2.Client.Tools.SYSV diff --git a/src/lib/Client/Tools/Chkconfig.py b/src/lib/Client/Tools/Chkconfig.py index 17e8bf09b..12ea5f132 100644 --- a/src/lib/Client/Tools/Chkconfig.py +++ b/src/lib/Client/Tools/Chkconfig.py @@ -1,8 +1,6 @@ # This is the bcfg2 support for chkconfig -# $Id$ """This is chkconfig support.""" -__revision__ = '$Revision$' import os diff --git a/src/lib/Client/Tools/DebInit.py b/src/lib/Client/Tools/DebInit.py index 022332602..ca6fc439e 100644 --- a/src/lib/Client/Tools/DebInit.py +++ b/src/lib/Client/Tools/DebInit.py @@ -1,5 +1,4 @@ """Debian Init Support for Bcfg2""" -__revision__ = '$Revision$' import glob import os diff --git a/src/lib/Client/Tools/Encap.py b/src/lib/Client/Tools/Encap.py index 92062a750..fa09c3ec7 100644 --- a/src/lib/Client/Tools/Encap.py +++ b/src/lib/Client/Tools/Encap.py @@ -1,7 +1,5 @@ """Bcfg2 Support for Encap Packages""" -__revision__ = '$Revision$' - import glob import re import Bcfg2.Client.Tools diff --git a/src/lib/Client/Tools/FreeBSDPackage.py b/src/lib/Client/Tools/FreeBSDPackage.py index 04c05adaa..3e6f2b6bb 100644 --- a/src/lib/Client/Tools/FreeBSDPackage.py +++ b/src/lib/Client/Tools/FreeBSDPackage.py @@ -1,5 +1,4 @@ """This is the Bcfg2 tool for the FreeBSD package system.""" -__revision__ = '$Rev$' # TODO # - actual package installation diff --git a/src/lib/Client/Tools/IPS.py b/src/lib/Client/Tools/IPS.py index 9afd23143..e30bbd2a4 100644 --- a/src/lib/Client/Tools/IPS.py +++ b/src/lib/Client/Tools/IPS.py @@ -1,5 +1,4 @@ """This is the Bcfg2 support for OpenSolaris packages.""" -__revision__ = '$Revision$' import pkg.client.image as image import pkg.client.progress as progress diff --git a/src/lib/Client/Tools/MacPorts.py b/src/lib/Client/Tools/MacPorts.py index 23b536451..e8c911390 100644 --- a/src/lib/Client/Tools/MacPorts.py +++ b/src/lib/Client/Tools/MacPorts.py @@ -1,5 +1,4 @@ """This provides Bcfg2 support for macports packages.""" -__revision__ = '$Revision$' import Bcfg2.Client.Tools diff --git a/src/lib/Client/Tools/POSIX.py b/src/lib/Client/Tools/POSIX.py index 3591c33ad..326c6e02d 100644 --- a/src/lib/Client/Tools/POSIX.py +++ b/src/lib/Client/Tools/POSIX.py @@ -1,5 +1,4 @@ """All POSIX Type client support for Bcfg2.""" -__revision__ = '$Revision$' import binascii from datetime import datetime diff --git a/src/lib/Client/Tools/Portage.py b/src/lib/Client/Tools/Portage.py index 17163afa9..646995f4e 100644 --- a/src/lib/Client/Tools/Portage.py +++ b/src/lib/Client/Tools/Portage.py @@ -1,5 +1,4 @@ """This is the Bcfg2 tool for the Gentoo Portage system.""" -__revision__ = '$Revision$' import re import Bcfg2.Client.Tools diff --git a/src/lib/Client/Tools/RPMng.py b/src/lib/Client/Tools/RPMng.py index 5376118c2..b28bec030 100644 --- a/src/lib/Client/Tools/RPMng.py +++ b/src/lib/Client/Tools/RPMng.py @@ -1,7 +1,5 @@ """Bcfg2 Support for RPMS""" -__revision__ = '$Revision$' - import os.path import rpm import rpmtools @@ -9,12 +7,6 @@ import Bcfg2.Client.Tools # Compatibility import from Bcfg2.Bcfg2Py3k import ConfigParser -# Fix for python2.3 -try: - set -except NameError: - from sets import Set as set - class RPMng(Bcfg2.Client.Tools.PkgTool): """Support for RPM packages.""" name = 'RPMng' diff --git a/src/lib/Client/Tools/RcUpdate.py b/src/lib/Client/Tools/RcUpdate.py index d832d98a8..1b9a29478 100644 --- a/src/lib/Client/Tools/RcUpdate.py +++ b/src/lib/Client/Tools/RcUpdate.py @@ -1,5 +1,4 @@ """This is rc-update support.""" -__revision__ = '$Revision$' import os import Bcfg2.Client.Tools diff --git a/src/lib/Client/Tools/SMF.py b/src/lib/Client/Tools/SMF.py index 944408326..f824410ad 100644 --- a/src/lib/Client/Tools/SMF.py +++ b/src/lib/Client/Tools/SMF.py @@ -1,5 +1,4 @@ """SMF support for Bcfg2""" -__revision__ = '$Revision$' import glob import os diff --git a/src/lib/Client/Tools/SYSV.py b/src/lib/Client/Tools/SYSV.py index b5e1f1c59..eb4a13dfb 100644 --- a/src/lib/Client/Tools/SYSV.py +++ b/src/lib/Client/Tools/SYSV.py @@ -1,6 +1,4 @@ -# This is the bcfg2 support for solaris sysv packages """This provides bcfg2 support for Solaris SYSV packages.""" -__revision__ = '$Revision$' import tempfile diff --git a/src/lib/Client/Tools/Upstart.py b/src/lib/Client/Tools/Upstart.py index 41a585c23..7afc8edd7 100644 --- a/src/lib/Client/Tools/Upstart.py +++ b/src/lib/Client/Tools/Upstart.py @@ -1,5 +1,4 @@ """Upstart support for Bcfg2.""" -__revision__ = '$Revision$' import glob import re diff --git a/src/lib/Client/Tools/YUM24.py b/src/lib/Client/Tools/YUM24.py index 66768fb34..4e488b9da 100644 --- a/src/lib/Client/Tools/YUM24.py +++ b/src/lib/Client/Tools/YUM24.py @@ -1,5 +1,4 @@ """This provides bcfg2 support for yum.""" -__revision__ = '$Revision: $' import copy import os.path @@ -10,12 +9,6 @@ import Bcfg2.Client.Tools.RPMng # Compatibility import from Bcfg2.Bcfg2Py3k import ConfigParser -# Fix for python2.3 -try: - set -except NameError: - from sets import Set as set - YAD = True CP = ConfigParser.ConfigParser() try: diff --git a/src/lib/Client/Tools/YUMng.py b/src/lib/Client/Tools/YUMng.py index 9b999df92..154676764 100644 --- a/src/lib/Client/Tools/YUMng.py +++ b/src/lib/Client/Tools/YUMng.py @@ -1,5 +1,4 @@ """This provides bcfg2 support for yum.""" -__revision__ = '$Revision$' import copy import os.path @@ -16,12 +15,6 @@ import Bcfg2.Client.Tools # Compatibility import from Bcfg2.Bcfg2Py3k import ConfigParser -# Fix for python2.3 -try: - set -except NameError: - from sets import Set as set - def build_yname(pkgname, inst): """Build yum appropriate package name.""" diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Client/Tools/__init__.py index e879d7dd6..c6cb6e239 100644 --- a/src/lib/Client/Tools/__init__.py +++ b/src/lib/Client/Tools/__init__.py @@ -1,8 +1,4 @@ """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 stat import sys @@ -10,7 +6,6 @@ from subprocess import Popen, PIPE import time import Bcfg2.Client.XML -__revision__ = '$Revision$' __all__ = [tool.split('.')[0] \ for tool in os.listdir(os.path.dirname(__file__)) \ diff --git a/src/lib/Client/Tools/launchd.py b/src/lib/Client/Tools/launchd.py index 03dd97e71..700234cc8 100644 --- a/src/lib/Client/Tools/launchd.py +++ b/src/lib/Client/Tools/launchd.py @@ -1,5 +1,4 @@ """launchd support for Bcfg2.""" -__revision__ = '$Revision$' import os import popen2 diff --git a/src/lib/Client/Tools/rpmtools.py b/src/lib/Client/Tools/rpmtools.py index 3cd2b7014..7441b2c06 100755 --- a/src/lib/Client/Tools/rpmtools.py +++ b/src/lib/Client/Tools/rpmtools.py @@ -18,7 +18,6 @@ Run 'rpmtools' -h for the options. """ -__revision__ = '$Revision$' import grp import optparse diff --git a/src/lib/Client/XML.py b/src/lib/Client/XML.py index 42b1017ac..858479611 100644 --- a/src/lib/Client/XML.py +++ b/src/lib/Client/XML.py @@ -1,5 +1,4 @@ '''XML lib compatibility layer for the Bcfg2 client''' -__revision__ = '$Revision$' # library will use lxml, then builtin xml.etree, then ElementTree diff --git a/src/lib/Client/__init__.py b/src/lib/Client/__init__.py index ea60a4259..6ed37b257 100644 --- a/src/lib/Client/__init__.py +++ b/src/lib/Client/__init__.py @@ -1,4 +1,3 @@ """This contains all Bcfg2 Client modules""" -__revision__ = '$Revision$' __all__ = ["Frame", "Tools", "XML"] diff --git a/src/lib/Component.py b/src/lib/Component.py index caea3eda9..e21b5d359 100644 --- a/src/lib/Component.py +++ b/src/lib/Component.py @@ -1,7 +1,5 @@ """Cobalt component base.""" -__revision__ = '$Revision$' - __all__ = ["Component", "exposed", "automatic", "run_component"] import inspect diff --git a/src/lib/Logger.py b/src/lib/Logger.py index e8f9ecd47..06aae615e 100644 --- a/src/lib/Logger.py +++ b/src/lib/Logger.py @@ -1,5 +1,4 @@ """Bcfg2 logging support""" -__revision__ = '$Revision$' import copy import fcntl diff --git a/src/lib/Options.py b/src/lib/Options.py index 2f535397f..527b42d00 100644 --- a/src/lib/Options.py +++ b/src/lib/Options.py @@ -1,5 +1,4 @@ """Option parsing library for utilities.""" -__revision__ = '$Revision$' import getopt import os diff --git a/src/lib/Proxy.py b/src/lib/Proxy.py index 977a49228..b4a7c6b41 100644 --- a/src/lib/Proxy.py +++ b/src/lib/Proxy.py @@ -8,9 +8,6 @@ load_config -- read configuration files """ -__revision__ = '$Revision: $' - - import logging import re import socket diff --git a/src/lib/SSLServer.py b/src/lib/SSLServer.py index 32ab9933b..418e259cc 100644 --- a/src/lib/SSLServer.py +++ b/src/lib/SSLServer.py @@ -1,7 +1,5 @@ """Bcfg2 SSL server.""" -__revision__ = '$Revision$' - __all__ = [ "SSLServer", "XMLRPCRequestHandler", "XMLRPCServer", ] diff --git a/src/lib/Server/Admin/__init__.py b/src/lib/Server/Admin/__init__.py index a9f0b8cd6..5031392d0 100644 --- a/src/lib/Server/Admin/__init__.py +++ b/src/lib/Server/Admin/__init__.py @@ -1,5 +1,3 @@ -__revision__ = '$Revision$' - __all__ = [ 'Backup', 'Bundle', diff --git a/src/lib/Server/Core.py b/src/lib/Server/Core.py index 38f854f66..cb95e3e9c 100644 --- a/src/lib/Server/Core.py +++ b/src/lib/Server/Core.py @@ -1,5 +1,4 @@ """Bcfg2.Server.Core provides the runtime support for Bcfg2 modules.""" -__revision__ = '$Revision$' import atexit import logging diff --git a/src/lib/Server/Hostbase/backends.py b/src/lib/Server/Hostbase/backends.py index bf774f695..ecaf3c109 100644 --- a/src/lib/Server/Hostbase/backends.py +++ b/src/lib/Server/Hostbase/backends.py @@ -2,8 +2,6 @@ from django.contrib.auth.models import User #from ldapauth import * from nisauth import * -__revision__ = '$Revision$' - ## class LDAPBackend(object): ## def authenticate(self,username=None,password=None): diff --git a/src/lib/Server/Hostbase/hostbase/views.py b/src/lib/Server/Hostbase/hostbase/views.py index ff1d4710d..57ef5eff8 100644 --- a/src/lib/Server/Hostbase/hostbase/views.py +++ b/src/lib/Server/Hostbase/hostbase/views.py @@ -2,8 +2,6 @@ Contains all the views associated with the hostbase app Also has does form validation """ -__revision__ = "$Revision: $" - from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.decorators import login_required diff --git a/src/lib/Server/Hostbase/nisauth.py b/src/lib/Server/Hostbase/nisauth.py index 9c7da8c0a..ae4c6c021 100644 --- a/src/lib/Server/Hostbase/nisauth.py +++ b/src/lib/Server/Hostbase/nisauth.py @@ -1,10 +1,8 @@ +"""Checks with NIS to see if the current user is in the support group""" import os import crypt, nis from Bcfg2.Server.Hostbase.settings import AUTHORIZED_GROUP -"""Checks with NIS to see if the current user is in the support group""" - -__revision__ = "$Revision: $" class NISAUTHError(Exception): """NISAUTHError is raised when somehting goes boom.""" diff --git a/src/lib/Server/Lint/__init__.py b/src/lib/Server/Lint/__init__.py index a9d57c46c..326f5b1dc 100644 --- a/src/lib/Server/Lint/__init__.py +++ b/src/lib/Server/Lint/__init__.py @@ -1,5 +1,3 @@ -__revision__ = '$Revision$' - __all__ = ['Bundles', 'Comments', 'Duplicates', diff --git a/src/lib/Server/Plugin.py b/src/lib/Server/Plugin.py index 692aa8a81..06de18f29 100644 --- a/src/lib/Server/Plugin.py +++ b/src/lib/Server/Plugin.py @@ -1,5 +1,4 @@ """This module provides the baseclass for Bcfg2 Server Plugins.""" -__revision__ = '$Revision$' import copy import logging @@ -85,7 +84,6 @@ class Plugin(Debuggable): """This is the base class for all Bcfg2 Server plugins. Several attributes must be defined in the subclass: name : the name of the plugin - __version__ : a version string __author__ : the author/contact for the plugin Plugins can provide three basic types of functionality: @@ -94,7 +92,6 @@ class Plugin(Debuggable): - Data collection (overloading GetProbes/ReceiveData) """ name = 'Plugin' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' experimental = False deprecated = False @@ -114,7 +111,7 @@ class Plugin(Debuggable): object.__init__(self) self.Entries = {} self.core = core - self.data = "%s/%s" % (datastore, self.name) + self.data = os.path.join(datastore, self.name) self.running = True Debuggable.__init__(self, name=self.name) @@ -1094,7 +1091,6 @@ class EntrySet: class GroupSpool(Plugin, Generator): """Unified interface for handling group-specific data (e.g. .G## files).""" name = 'GroupSpool' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' filename_pattern = "" es_child_cls = object diff --git a/src/lib/Server/Plugins/Account.py b/src/lib/Server/Plugins/Account.py index f67819b9d..f2703dccb 100644 --- a/src/lib/Server/Plugins/Account.py +++ b/src/lib/Server/Plugins/Account.py @@ -1,5 +1,4 @@ """This handles authentication setup.""" -__revision__ = '$Revision$' import Bcfg2.Server.Plugin @@ -16,7 +15,6 @@ class Account(Bcfg2.Server.Plugin.Plugin, """ name = 'Account' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/BB.py b/src/lib/Server/Plugins/BB.py index 137142b66..c015ec47c 100644 --- a/src/lib/Server/Plugins/BB.py +++ b/src/lib/Server/Plugins/BB.py @@ -62,7 +62,6 @@ class BB(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector): """The BB plugin maps users to machines and metadata to machines.""" name = 'BB' - version = '$Revision$' deprecated = True def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Base.py b/src/lib/Server/Plugins/Base.py index e8d798ae4..389ca7a95 100644 --- a/src/lib/Server/Plugins/Base.py +++ b/src/lib/Server/Plugins/Base.py @@ -1,5 +1,4 @@ """This module sets up a base list of configuration entries.""" -__revision__ = '$Revision$' import copy import lxml.etree @@ -18,7 +17,6 @@ class Base(Bcfg2.Server.Plugin.Plugin, needed for most actual systems. """ name = 'Base' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' __child__ = Bcfg2.Server.Plugin.StructFile deprecated = True diff --git a/src/lib/Server/Plugins/Bundler.py b/src/lib/Server/Plugins/Bundler.py index a58257712..ccb99481e 100644 --- a/src/lib/Server/Plugins/Bundler.py +++ b/src/lib/Server/Plugins/Bundler.py @@ -1,5 +1,4 @@ """This provides bundle clauses with translation functionality.""" -__revision__ = '$Revision$' import copy import lxml.etree @@ -35,7 +34,6 @@ class Bundler(Bcfg2.Server.Plugin.Plugin, bundle/translation scheme from Bcfg1. """ name = 'Bundler' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' patterns = re.compile('^(?P<name>.*)\.(xml|genshi)$') diff --git a/src/lib/Server/Plugins/Bzr.py b/src/lib/Server/Plugins/Bzr.py index a9a5eb814..a71021cb5 100644 --- a/src/lib/Server/Plugins/Bzr.py +++ b/src/lib/Server/Plugins/Bzr.py @@ -10,7 +10,6 @@ class Bzr(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Bzr is a version plugin for dealing with Bcfg2 repos.""" name = 'Bzr' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Cfg.py b/src/lib/Server/Plugins/Cfg.py index fafc6c638..917f27e20 100644 --- a/src/lib/Server/Plugins/Cfg.py +++ b/src/lib/Server/Plugins/Cfg.py @@ -1,5 +1,4 @@ """This module implements a config file repository.""" -__revision__ = '$Revision$' import binascii import logging @@ -271,7 +270,6 @@ class Cfg(Bcfg2.Server.Plugin.GroupSpool, Bcfg2.Server.Plugin.PullTarget): """This generator in the configuration file repository for Bcfg2.""" name = 'Cfg' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' es_cls = CfgEntrySet es_child_cls = Bcfg2.Server.Plugin.SpecificData diff --git a/src/lib/Server/Plugins/Cvs.py b/src/lib/Server/Plugins/Cvs.py index ea898c023..6ce72acd2 100644 --- a/src/lib/Server/Plugins/Cvs.py +++ b/src/lib/Server/Plugins/Cvs.py @@ -10,7 +10,6 @@ class Cvs(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """CVS is a version plugin for dealing with Bcfg2 repository.""" name = 'Cvs' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' experimental = True diff --git a/src/lib/Server/Plugins/DBStats.py b/src/lib/Server/Plugins/DBStats.py index 8761d282d..95395f74e 100644 --- a/src/lib/Server/Plugins/DBStats.py +++ b/src/lib/Server/Plugins/DBStats.py @@ -22,7 +22,6 @@ class DBStats(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.ThreadedStatistics, Bcfg2.Server.Plugin.PullSource): name = 'DBStats' - __version__ = '$Id$' def __init__(self, core, datastore): Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) diff --git a/src/lib/Server/Plugins/Darcs.py b/src/lib/Server/Plugins/Darcs.py index eb34a52c4..9fb9ff4f1 100644 --- a/src/lib/Server/Plugins/Darcs.py +++ b/src/lib/Server/Plugins/Darcs.py @@ -10,7 +10,6 @@ class Darcs(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Darcs is a version plugin for dealing with Bcfg2 repos.""" name = 'Darcs' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' experimental = True diff --git a/src/lib/Server/Plugins/Decisions.py b/src/lib/Server/Plugins/Decisions.py index 556f75502..b432474f2 100644 --- a/src/lib/Server/Plugins/Decisions.py +++ b/src/lib/Server/Plugins/Decisions.py @@ -50,7 +50,6 @@ class Decisions(DecisionSet, Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Decision): name = 'Decisions' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Defaults.py b/src/lib/Server/Plugins/Defaults.py index b208c1126..718192e2a 100644 --- a/src/lib/Server/Plugins/Defaults.py +++ b/src/lib/Server/Plugins/Defaults.py @@ -1,5 +1,4 @@ """This generator provides rule-based entry mappings.""" -__revision__ = '$Revision$' import re import Bcfg2.Server.Plugin @@ -9,7 +8,6 @@ class Defaults(Bcfg2.Server.Plugins.Rules.Rules, Bcfg2.Server.Plugin.StructureValidator): """Set default attributes on bound entries""" name = 'Defaults' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' # Rules is a Generator that happens to implement all of the diff --git a/src/lib/Server/Plugins/Deps.py b/src/lib/Server/Plugins/Deps.py index 482d457af..9b848baae 100644 --- a/src/lib/Server/Plugins/Deps.py +++ b/src/lib/Server/Plugins/Deps.py @@ -1,5 +1,4 @@ """This plugin provides automatic dependency handling.""" -__revision__ = '$Revision$' import lxml.etree @@ -45,7 +44,6 @@ class DepXMLSrc(Bcfg2.Server.Plugin.XMLSrc): class Deps(Bcfg2.Server.Plugin.PrioDir, Bcfg2.Server.Plugin.StructureValidator): name = 'Deps' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' __child__ = DepXMLSrc diff --git a/src/lib/Server/Plugins/Editor.py b/src/lib/Server/Plugins/Editor.py index 76a03a325..c0d2cfbad 100644 --- a/src/lib/Server/Plugins/Editor.py +++ b/src/lib/Server/Plugins/Editor.py @@ -59,7 +59,6 @@ class EditEntrySet(Bcfg2.Server.Plugin.EntrySet): class Editor(Bcfg2.Server.Plugin.GroupSpool, Bcfg2.Server.Plugin.Probing): name = 'Editor' - __version__ = '$Id$' __author__ = 'bcfg2-dev@mcs.anl.gov' filename_pattern = 'edits' es_child_cls = EditDirectives diff --git a/src/lib/Server/Plugins/FileProbes.py b/src/lib/Server/Plugins/FileProbes.py index c58040b65..a76d7cac4 100644 --- a/src/lib/Server/Plugins/FileProbes.py +++ b/src/lib/Server/Plugins/FileProbes.py @@ -3,7 +3,6 @@ added to the specification. On subsequent runs, the file will be replaced on the client if it is missing; if it has changed on the client, it can either be updated in the specification or replaced on the client """ -__revision__ = '$Revision: 1465 $' import os import sys @@ -55,7 +54,6 @@ class FileProbes(Bcfg2.Server.Plugin.Plugin, name = 'FileProbes' experimental = True - __version__ = '$Id$' __author__ = 'chris.a.st.pierre@gmail.com' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Fossil.py b/src/lib/Server/Plugins/Fossil.py index 57d427673..1b1627688 100644 --- a/src/lib/Server/Plugins/Fossil.py +++ b/src/lib/Server/Plugins/Fossil.py @@ -10,7 +10,6 @@ class Fossil(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Fossil is a version plugin for dealing with Bcfg2 repos.""" name = 'Fossil' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Git.py b/src/lib/Server/Plugins/Git.py index aaeac12ae..8f8ea87f1 100644 --- a/src/lib/Server/Plugins/Git.py +++ b/src/lib/Server/Plugins/Git.py @@ -13,7 +13,6 @@ class Git(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Git is a version plugin for dealing with Bcfg2 repos.""" name = 'Git' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Guppy.py b/src/lib/Server/Plugins/Guppy.py index b217378d6..046aedc0b 100644 --- a/src/lib/Server/Plugins/Guppy.py +++ b/src/lib/Server/Plugins/Guppy.py @@ -32,7 +32,6 @@ import Bcfg2.Server.Plugin class Guppy(Bcfg2.Server.Plugin.Plugin): """Guppy is a debugging plugin to help trace memory leaks""" name = 'Guppy' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' experimental = True diff --git a/src/lib/Server/Plugins/Hg.py b/src/lib/Server/Plugins/Hg.py index 70e33ef1f..0c3537613 100644 --- a/src/lib/Server/Plugins/Hg.py +++ b/src/lib/Server/Plugins/Hg.py @@ -10,7 +10,6 @@ class Hg(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Mercurial is a version plugin for dealing with Bcfg2 repository.""" name = 'Mercurial' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' experimental = True diff --git a/src/lib/Server/Plugins/Hostbase.py b/src/lib/Server/Plugins/Hostbase.py index 4180fd716..e9c1c1cff 100644 --- a/src/lib/Server/Plugins/Hostbase.py +++ b/src/lib/Server/Plugins/Hostbase.py @@ -2,7 +2,6 @@ This file provides the Hostbase plugin. It manages dns/dhcp/nis host information """ -__revision__ = '$Revision$' import os os.environ['DJANGO_SETTINGS_MODULE'] = 'Bcfg2.Server.Hostbase.settings' @@ -23,7 +22,6 @@ class Hostbase(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Generator): """The Hostbase plugin handles host/network info.""" name = 'Hostbase' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' filepath = '/my/adm/hostbase/files/bind' diff --git a/src/lib/Server/Plugins/Ldap.py b/src/lib/Server/Plugins/Ldap.py index f1e2198d2..04417339b 100644 --- a/src/lib/Server/Plugins/Ldap.py +++ b/src/lib/Server/Plugins/Ldap.py @@ -63,7 +63,6 @@ class Ldap(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector): The Ldap plugin allows adding data from an LDAP server to your metadata. """ name = "Ldap" - version = "$Revision: $" experimental = True debug_flag = False diff --git a/src/lib/Server/Plugins/Metadata.py b/src/lib/Server/Plugins/Metadata.py index f837b1305..fa2cf0a78 100644 --- a/src/lib/Server/Plugins/Metadata.py +++ b/src/lib/Server/Plugins/Metadata.py @@ -2,8 +2,6 @@ This file stores persistent metadata for the Bcfg2 Configuration Repository. """ -__revision__ = '$Revision$' - import copy import fcntl import lxml.etree @@ -67,16 +65,16 @@ class XMLMetadataConfig(object): def add_monitor(self, fname): """Add a fam monitor for an included file""" if self.should_monitor: - self.metadata.core.fam.AddMonitor("%s/%s" % (self.basedir, fname), + self.metadata.core.fam.AddMonitor(os.path.join(self.basedir, fname), self.metadata) self.extras.append(fname) def load_xml(self): """Load changes from XML""" try: - xdata = lxml.etree.parse("%s/%s" % (self.basedir, self.basefile)) + xdata = lxml.etree.parse(os.path.join(self.basedir, self.basefile)) except lxml.etree.XMLSyntaxError: - self.logger.error('Failed to parse %s' % (self.basefile)) + self.logger.error('Failed to parse %s' % self.basefile) return self.basedata = copy.copy(xdata) included = [ent.get('href') for ent in \ @@ -88,19 +86,20 @@ class XMLMetadataConfig(object): try: xdata.xinclude() except lxml.etree.XIncludeError: - self.logger.error("Failed to process XInclude for file %s" % self.basefile) + self.logger.error("Failed to process XInclude for file %s" % + self.basefile) self.data = xdata def write(self): """Write changes to xml back to disk.""" - self.write_xml("%s/%s" % (self.basedir, self.basefile), + self.write_xml(os.path.join(self.basedir, self.basefile), self.basedata) def write_xml(self, fname, xmltree): """Write changes to xml back to disk.""" tmpfile = "%s.new" % fname try: - datafile = open("%s" % tmpfile, 'w') + datafile = open(tmpfile, 'w') except IOError: e = sys.exc_info()[1] self.logger.error("Failed to write %s: %s" % (tmpfile, e)) @@ -116,40 +115,42 @@ class XMLMetadataConfig(object): datafile.write(newcontents) except: fcntl.lockf(fd, fcntl.LOCK_UN) - self.logger.error("Metadata: Failed to write new xml data to %s" % tmpfile, exc_info=1) - os.unlink("%s" % tmpfile) + self.logger.error("Metadata: Failed to write new xml data to %s" % + tmpfile, exc_info=1) + os.unlink(tmpfile) raise MetadataRuntimeError datafile.close() # check if clients.xml is a symlink - xmlfile = "%s" % fname - if os.path.islink(xmlfile): - xmlfile = os.readlink(xmlfile) + if os.path.islink(fname): + fname = os.readlink(fname) try: - os.rename("%s" % tmpfile, xmlfile) + os.rename(tmpfile, fname) except: self.logger.error("Metadata: Failed to rename %s" % tmpfile) raise MetadataRuntimeError def find_xml_for_xpath(self, xpath): - """Find and load xml data containing the xpath query""" + """Find and load xml file containing the xpath query""" if self.pseudo_monitor: # Reload xml if we don't have a real monitor self.load_xml() cli = self.basedata.xpath(xpath) if len(cli) > 0: - return {'filename': "%s/%s" % (self.basedir, self.basefile), + return {'filename': os.path.join(self.basedir, self.basefile), 'xmltree': self.basedata, 'xquery': cli} else: """Try to find the data in included files""" for included in self.extras: try: - xdata = lxml.etree.parse("%s/%s" % (self.basedir, included)) + xdata = lxml.etree.parse(os.path.join(self.basedir, + included)) cli = xdata.xpath(xpath) if len(cli) > 0: - return {'filename': "%s/%s" % (self.basedir, included), + return {'filename': os.path.join(self.basedir, + included), 'xmltree': xdata, 'xquery': cli} except lxml.etree.XMLSyntaxError: @@ -221,7 +222,6 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Metadata, Bcfg2.Server.Plugin.Statistics): """This class contains data for bcfg2 server metadata.""" - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' name = "Metadata" sort_order = 500 @@ -232,8 +232,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Statistics.__init__(self) if watch_clients: try: - core.fam.AddMonitor("%s/%s" % (self.data, "groups.xml"), self) - core.fam.AddMonitor("%s/%s" % (self.data, "clients.xml"), self) + core.fam.AddMonitor(os.path.join(self.data, "groups.xml"), self) + core.fam.AddMonitor(os.path.join(self.data, "clients.xml"), self) except: print("Unable to add file monitor for groups.xml or clients.xml") raise Bcfg2.Server.Plugin.PluginInitError @@ -274,269 +274,241 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, @classmethod def init_repo(cls, repo, groups, os_selection, clients): - path = '%s/%s' % (repo, cls.name) + path = os.path.join(repo, cls.name) os.makedirs(path) - open("%s/Metadata/groups.xml" % - repo, "w").write(groups % os_selection) - open("%s/Metadata/clients.xml" % - repo, "w").write(clients % socket.getfqdn()) + open(os.path.join(repo, "Metadata", "groups.xml"), + "w").write(groups % os_selection) + open(os.path.join(repo, "Metadata", "clients.xml"), + "w").write(clients % socket.getfqdn()) def get_groups(self): '''return groups xml tree''' - groups_tree = lxml.etree.parse(self.data + "/groups.xml") + groups_tree = lxml.etree.parse(os.path.join(self.data, "groups.xml")) root = groups_tree.getroot() return root + def _search_xdata(self, tag, name, tree, alias=False): + for node in tree.findall("//%s" % tag): + if node.get("name") == name: + return node + elif alias: + for child in node: + if (child.tag == "Alias" and + child.attrib["name"] == name): + return node + return None + def search_group(self, group_name, tree): """Find a group.""" - for node in tree.findall("//Group"): - if node.get("name") == group_name: - return node - for child in node: - if child.tag == "Alias" and child.attrib["name"] == group_name: - return node - return None + return self._search_xdata("Group", group_name, tree) - def add_group(self, group_name, attribs): - """Add group to groups.xml.""" + def search_bundle(self, bundle_name, tree): + """Find a bundle.""" + return self._search_xdata("Bundle", bundle_name, tree) - node = self.search_group(group_name, self.groups_xml.xdata) + def search_client(self, client_name, tree): + return self._search_xdata("Client", client_name, tree, alias=True) + + def _add_xdata(self, config, tag, name, attribs=None, alias=False): + node = self._search_xdata(tag, name, config.xdata, alias=alias) if node != None: - self.logger.error("Group \"%s\" already exists" % (group_name)) + self.logger.error("%s \"%s\" already exists" % (tag, name)) raise MetadataConsistencyError + element = lxml.etree.SubElement(config.base_xdata.getroot(), + tag, name=name) + if attribs: + for key, val in list(attribs.items()): + element.set(key, val) + config.write() + + def add_group(self, group_name, attribs): + """Add group to groups.xml.""" + return self._add_xdata(self.groups_xml, "Group", group_name, + attribs=attribs) - element = lxml.etree.SubElement(self.groups_xml.base_xdata.getroot(), - "Group", name=group_name) - for key, val in list(attribs.items()): - element.set(key, val) - self.groups_xml.write() + def add_bundle(self, bundle_name): + """Add bundle to groups.xml.""" + return self._add_xdata(self.groups_xml, "Bundle", bundle_name) - def update_group(self, group_name, attribs): - """Update a groups attributes.""" - node = self.search_group(group_name, self.groups_xml.xdata) + def add_client(self, client_name, attribs): + """Add client to clients.xml.""" + return self._add_xdata(self.clients_xml, "Client", client_name, + attribs=attribs, alias=True) + + def _update_xdata(self, config, tag, name, attribs, alias=False): + node = self._search_xdata(tag, name, config.xdata, alias=alias) if node == None: - self.logger.error("Group \"%s\" does not exist" % (group_name)) + self.logger.error("%s \"%s\" does not exist" % (tag, name)) raise MetadataConsistencyError - xdict = self.groups_xml.find_xml_for_xpath('.//Group[@name="%s"]' % (node.get('name'))) + xdict = config.find_xml_for_xpath('.//%s[@name="%s"]' % + (tag, node.get('name'))) if not xdict: - self.logger.error("Unexpected error finding group") + self.logger.error("Unexpected error finding %s \"%s\"" % + (tag, name)) raise MetadataConsistencyError - for key, val in list(attribs.items()): xdict['xquery'][0].set(key, val) - self.groups_xml.write_xml(xdict['filename'], xdict['xmltree']) + config.write_xml(xdict['filename'], xdict['xmltree']) - def remove_group(self, group_name): - """Remove a group.""" - node = self.search_group(group_name, self.groups_xml.xdata) + def update_group(self, group_name, attribs): + """Update a groups attributes.""" + return self._update_xdata(self.groups_xml, "Group", group_name, attribs) + + def update_client(self, client_name, attribs): + """Update a clients attributes.""" + return self._update_xdata(self.clients_xml, "Client", client_name, + attribs, alias=True) + + def _remove_xdata(self, config, tag, name, alias=False): + node = self._search_xdata(tag, name, config.xdata) if node == None: - self.logger.error("Group \"%s\" does not exist" % (group_name)) + self.logger.error("%s \"%s\" does not exist" % (tag, name)) raise MetadataConsistencyError - xdict = self.groups_xml.find_xml_for_xpath('.//Group[@name="%s"]' % (node.get('name'))) + xdict = config.find_xml_for_xpath('.//%s[@name="%s"]' % + (tag, node.get('name'))) if not xdict: - self.logger.error("Unexpected error finding group") + self.logger.error("Unexpected error finding %s \"%s\"" % + (tag, name)) raise MetadataConsistencyError xdict['xquery'][0].getparent().remove(xdict['xquery'][0]) self.groups_xml.write_xml(xdict['filename'], xdict['xmltree']) - def add_bundle(self, bundle_name): - """Add bundle to groups.xml.""" - tree = lxml.etree.parse(self.data + "/groups.xml") - root = tree.getroot() - element = lxml.etree.Element("Bundle", name=bundle_name) - node = self.search_group(bundle_name, tree) - if node != None: - self.logger.error("Bundle \"%s\" already exists" % (bundle_name)) - raise MetadataConsistencyError - root.append(element) - group_tree = open(self.data + "/groups.xml", "w") - fd = group_tree.fileno() - while True: - try: - fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError: - continue - else: - break - tree.write(group_tree) - fcntl.lockf(fd, fcntl.LOCK_UN) - group_tree.close() + def remove_group(self, group_name): + """Remove a group.""" + return self._remove_xdata(self.groups_xml, "Group", group_name) def remove_bundle(self, bundle_name): """Remove a bundle.""" - tree = lxml.etree.parse(self.data + "/groups.xml") - root = tree.getroot() - node = self.search_group(bundle_name, tree) - if node == None: - self.logger.error("Bundle \"%s\" not found" % (bundle_name)) - raise MetadataConsistencyError - root.remove(node) - group_tree = open(self.data + "/groups.xml", "w") - fd = group_tree.fileno() - while True: - try: - fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError: - continue - else: - break - tree.write(group_tree) - fcntl.lockf(fd, fcntl.LOCK_UN) - group_tree.close() + return self._remove_xdata(self.groups_xml, "Bundle", bundle_name) - def search_client(self, client_name, tree): - """Find a client.""" - for node in tree.findall("//Client"): - if node.get("name") == client_name: - return node - for child in node: - if child.tag == "Alias" and child.attrib["name"] == client_name: - return node - return None - - def add_client(self, client_name, attribs): - """Add client to clients.xml.""" - node = self.search_client(client_name, self.clients_xml.xdata) - if node != None: - self.logger.error("Client \"%s\" already exists" % (client_name)) - raise MetadataConsistencyError - - element = lxml.etree.SubElement(self.clients_xml.base_xdata.getroot(), - "Client", name=client_name) - for key, val in list(attribs.items()): - element.set(key, val) - self.clients_xml.write() - - def update_client(self, client_name, attribs): - """Update a clients attributes.""" - node = self.search_client(client_name, self.clients_xml.xdata) - if node == None: - self.logger.error("Client \"%s\" does not exist" % (client_name)) - raise MetadataConsistencyError - - xdict = self.clients_xml.find_xml_for_xpath('.//Client[@name="%s"]' % (node.get('name'))) - if not xdict: - self.logger.error("Unexpected error finding client") - raise MetadataConsistencyError - - node = xdict['xquery'][0] - [node.set(key, value) for key, value in list(attribs.items())] - self.clients_xml.write_xml(xdict['filename'], xdict['xmltree']) + def _handle_clients_xml_event(self, event): + xdata = self.clients_xml.xdata + self.clients = {} + self.aliases = {} + self.raliases = {} + self.bad_clients = {} + self.secure = [] + self.floating = [] + self.addresses = {} + self.raddresses = {} + for client in xdata.findall('.//Client'): + clname = client.get('name').lower() + if 'address' in client.attrib: + caddr = client.get('address') + if caddr in self.addresses: + self.addresses[caddr].append(clname) + else: + self.addresses[caddr] = [clname] + if clname not in self.raddresses: + self.raddresses[clname] = set() + self.raddresses[clname].add(caddr) + if 'auth' in client.attrib: + self.auth[client.get('name')] = client.get('auth', + 'cert+password') + if 'uuid' in client.attrib: + self.uuid[client.get('uuid')] = clname + if client.get('secure', 'false') == 'true': + self.secure.append(clname) + if client.get('location', 'fixed') == 'floating': + self.floating.append(clname) + if 'password' in client.attrib: + self.passwords[clname] = client.get('password') + + self.raliases[clname] = set() + for alias in client.findall('Alias'): + self.aliases.update({alias.get('name'): clname}) + self.raliases[clname].add(alias.get('name')) + if 'address' not in alias.attrib: + continue + if alias.get('address') in self.addresses: + self.addresses[alias.get('address')].append(clname) + else: + self.addresses[alias.get('address')] = [clname] + if clname not in self.raddresses: + self.raddresses[clname] = set() + self.raddresses[clname].add(alias.get('address')) + self.clients.update({clname: client.get('profile')}) + self.states['clients.xml'] = True + + def _handle_groups_xml_event(self, event): + xdata = self.groups_xml.xdata + self.public = [] + self.private = [] + self.profiles = [] + self.groups = {} + grouptmp = {} + self.categories = {} + groupseen = list() + for group in xdata.xpath('//Groups/Group'): + if group.get('name') not in groupseen: + groupseen.append(group.get('name')) + else: + self.logger.error("Metadata: Group %s defined multiply" % + group.get('name')) + grouptmp[group.get('name')] = \ + ([item.get('name') for item in group.findall('./Bundle')], + [item.get('name') for item in group.findall('./Group')]) + grouptmp[group.get('name')][1].append(group.get('name')) + if group.get('default', 'false') == 'true': + self.default = group.get('name') + if group.get('profile', 'false') == 'true': + self.profiles.append(group.get('name')) + if group.get('public', 'false') == 'true': + self.public.append(group.get('name')) + elif group.get('public', 'true') == 'false': + self.private.append(group.get('name')) + if 'category' in group.attrib: + self.categories[group.get('name')] = group.get('category') + + for group in grouptmp: + # self.groups[group] => (bundles, groups, categories) + self.groups[group] = (set(), set(), {}) + tocheck = [group] + group_cat = self.groups[group][2] + while tocheck: + now = tocheck.pop() + self.groups[group][1].add(now) + if now in grouptmp: + (bundles, groups) = grouptmp[now] + for ggg in groups: + if ggg in self.groups[group][1]: + continue + if (ggg not in self.categories or \ + self.categories[ggg] not in self.groups[group][2]): + self.groups[group][1].add(ggg) + tocheck.append(ggg) + if ggg in self.categories: + group_cat[self.categories[ggg]] = ggg + elif ggg in self.categories: + self.logger.info("Group %s: %s cat-suppressed %s" % \ + (group, + group_cat[self.categories[ggg]], + ggg)) + [self.groups[group][0].add(bund) for bund in bundles] + self.states['groups.xml'] = True def HandleEvent(self, event): """Handle update events for data files.""" if self.clients_xml.HandleEvent(event): - xdata = self.clients_xml.xdata - self.clients = {} - self.aliases = {} - self.raliases = {} - self.bad_clients = {} - self.secure = [] - self.floating = [] - self.addresses = {} - self.raddresses = {} - for client in xdata.findall('.//Client'): - clname = client.get('name').lower() - if 'address' in client.attrib: - caddr = client.get('address') - if caddr in self.addresses: - self.addresses[caddr].append(clname) - else: - self.addresses[caddr] = [clname] - if clname not in self.raddresses: - self.raddresses[clname] = set() - self.raddresses[clname].add(caddr) - if 'auth' in client.attrib: - self.auth[client.get('name')] = client.get('auth', - 'cert+password') - if 'uuid' in client.attrib: - self.uuid[client.get('uuid')] = clname - if client.get('secure', 'false') == 'true': - self.secure.append(clname) - if client.get('location', 'fixed') == 'floating': - self.floating.append(clname) - if 'password' in client.attrib: - self.passwords[clname] = client.get('password') - for alias in [alias for alias in client.findall('Alias')\ - if 'address' in alias.attrib]: - if alias.get('address') in self.addresses: - self.addresses[alias.get('address')].append(clname) - else: - self.addresses[alias.get('address')] = [clname] - if clname not in self.raddresses: - self.raddresses[clname] = set() - self.raddresses[clname].add(alias.get('address')) - self.clients.update({clname: client.get('profile')}) - [self.aliases.update({alias.get('name'): clname}) \ - for alias in client.findall('Alias')] - self.raliases[clname] = set() - [self.raliases[clname].add(alias.get('name')) for alias \ - in client.findall('Alias')] - self.states['clients.xml'] = True + self._handle_clients_xml_event(event) elif self.groups_xml.HandleEvent(event): - xdata = self.groups_xml.xdata - self.public = [] - self.private = [] - self.profiles = [] - self.groups = {} - grouptmp = {} - self.categories = {} - groupseen = list() - for group in xdata.xpath('//Groups/Group'): - if group.get('name') not in groupseen: - groupseen.append(group.get('name')) - else: - self.logger.error("Metadata: Group %s defined multiply" % (group.get('name'))) - grouptmp[group.get('name')] = tuple([[item.get('name') for item in group.findall(spec)] - for spec in ['./Bundle', './Group']]) - grouptmp[group.get('name')][1].append(group.get('name')) - if group.get('default', 'false') == 'true': - self.default = group.get('name') - if group.get('profile', 'false') == 'true': - self.profiles.append(group.get('name')) - if group.get('public', 'false') == 'true': - self.public.append(group.get('name')) - elif group.get('public', 'true') == 'false': - self.private.append(group.get('name')) - if 'category' in group.attrib: - self.categories[group.get('name')] = group.get('category') - for group in grouptmp: - # self.groups[group] => (bundles, groups, categories) - self.groups[group] = (set(), set(), {}) - tocheck = [group] - group_cat = self.groups[group][2] - while tocheck: - now = tocheck.pop() - self.groups[group][1].add(now) - if now in grouptmp: - (bundles, groups) = grouptmp[now] - for ggg in [ggg for ggg in groups if ggg not in self.groups[group][1]]: - if ggg not in self.categories or \ - self.categories[ggg] not in self.groups[group][2]: - self.groups[group][1].add(ggg) - tocheck.append(ggg) - if ggg in self.categories: - group_cat[self.categories[ggg]] = ggg - elif ggg in self.categories: - self.logger.info("Group %s: %s cat-suppressed %s" % \ - (group, - group_cat[self.categories[ggg]], - ggg)) - [self.groups[group][0].add(bund) for bund in bundles] - self.states['groups.xml'] = True + self._handle_groups_xml_event(event) + if False not in list(self.states.values()): # check that all client groups are real and complete real = list(self.groups.keys()) for client in list(self.clients.keys()): if self.clients[client] not in self.profiles: - self.logger.error("Client %s set as nonexistent or incomplete group %s" \ - % (client, self.clients[client])) - self.logger.error("Removing client mapping for %s" % (client)) + self.logger.error("Client %s set as nonexistent or " + "incomplete group %s" % + (client, self.clients[client])) + self.logger.error("Removing client mapping for %s" % client) self.bad_clients[client] = self.clients[client] del self.clients[client] for bclient in list(self.bad_clients.keys()): if self.bad_clients[bclient] in self.profiles: - self.logger.info("Restored profile mapping for client %s" % bclient) + self.logger.info("Restored profile mapping for client %s" % + bclient) self.clients[bclient] = self.bad_clients[bclient] del self.bad_clients[bclient] @@ -546,42 +518,37 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, if False in list(self.states.values()): raise MetadataRuntimeError if profile not in self.public: - self.logger.error("Failed to set client %s to private group %s" % (client, profile)) + self.logger.error("Failed to set client %s to private group %s" % + (client, profile)) raise MetadataConsistencyError if client in self.clients: - self.logger.info("Changing %s group from %s to %s" % (client, self.clients[client], profile)) - xdict = self.clients_xml.find_xml_for_xpath('.//Client[@name="%s"]' % (client)) - if not xdict: - self.logger.error("Metadata: Unable to update profile for client %s. Use of Xinclude?" % client) - raise MetadataConsistencyError - xdict['xquery'][0].set('profile', profile) - self.clients_xml.write_xml(xdict['filename'], xdict['xmltree']) + self.logger.info("Changing %s group from %s to %s" % + (client, self.clients[client], profile)) + self.update_client(client, dict(profile=profile)) else: - self.logger.info("Creating new client: %s, profile %s" % \ + self.logger.info("Creating new client: %s, profile %s" % (client, profile)) if addresspair in self.session_cache: # we are working with a uuid'd client - lxml.etree.SubElement(self.clients_xml.base_xdata.getroot(), - 'Client', - name=self.session_cache[addresspair][1], - uuid=client, profile=profile, - address=addresspair[0]) + self.add_client(self.session_cache[addresspair][1], + dict(uuid=client, profile=profile, + address=addresspair[0])) else: - lxml.etree.SubElement(self.clients_xml.base_xdata.getroot(), - 'Client', name=client, - profile=profile) + self.add_client(client, dict(profile=profile)) self.clients[client] = profile self.clients_xml.write() def resolve_client(self, addresspair, cleanup_cache=False): """Lookup address locally or in DNS to get a hostname.""" if addresspair in self.session_cache: - # client _was_ cached, so there can be some expired entries - # we need to clean them up to avoid potentially infinite memory swell + # client _was_ cached, so there can be some expired + # entries. we need to clean them up to avoid potentially + # infinite memory swell cache_ttl = 90 if cleanup_cache: - # remove entries for this client's IP address with _any_ port numbers - # - perhaps a priority queue could be faster? + # remove entries for this client's IP address with + # _any_ port numbers - perhaps a priority queue could + # be faster? curtime = time.time() for addrpair in self.session_cache.keys(): if addresspair[0] == addrpair[0]: @@ -589,13 +556,18 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, if curtime - stamp > cache_ttl: del self.session_cache[addrpair] # return the cached data - (stamp, uuid) = self.session_cache[addresspair] - if time.time() - stamp < cache_ttl: - return self.session_cache[addresspair][1] + try: + (stamp, uuid) = self.session_cache[addresspair] + if time.time() - stamp < cache_ttl: + return self.session_cache[addresspair][1] + except KeyError: + # we cleaned all cached data for this client in cleanup_cache + pass address = addresspair[0] if address in self.addresses: if len(self.addresses[address]) != 1: - self.logger.error("Address %s has multiple reverse assignments; a uuid must be used" % (address)) + self.logger.error("Address %s has multiple reverse assignments; " + "a uuid must be used" % (address)) raise MetadataConsistencyError return self.addresses[address][0] try: @@ -620,7 +592,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, (bundles, groups, categories) = self.groups[profile] else: if self.default == None: - self.logger.error("Cannot set group for client %s; no default group set" % (client)) + self.logger.error("Cannot set group for client %s; " + "no default group set" % client) raise MetadataConsistencyError self.set_profile(client, self.default, (None, None)) profile = self.default @@ -635,7 +608,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, password = self.passwords[client] else: password = None - uuids = [item for item, value in list(self.uuid.items()) if value == client] + uuids = [item for item, value in list(self.uuid.items()) + if value == client] if uuids: uuid = uuids[0] else: @@ -649,7 +623,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, [newgroups.add(g) for g in ngroups if g not in newgroups] newcategories.update(ncategories) return ClientMetadata(client, profile, newgroups, newbundles, aliases, - addresses, newcategories, uuid, password, self.query) + addresses, newcategories, uuid, password, + self.query) def get_all_group_names(self): all_groups = set() @@ -673,22 +648,27 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, def merge_additional_groups(self, imd, groups): for group in groups: - if group in self.categories and \ - self.categories[group] in imd.categories: + if (group in self.categories and + self.categories[group] in imd.categories): continue - nb, ng, _ = self.groups.get(group, (list(), [group], dict())) - for b in nb: - if b not in imd.bundles: - imd.bundles.add(b) - for g in ng: - if g not in imd.groups: - if g in self.categories and \ - self.categories[g] in imd.categories: + newbundles, newgroups, _ = self.groups.get(group, + (list(), + [group], + dict())) + for newbundle in newbundles: + if newbundle not in imd.bundles: + imd.bundles.add(newbundle) + for newgroup in newgroups: + if newgroup not in imd.groups: + if (newgroup in self.categories and + self.categories[newgroup] in imd.categories): continue - if g in self.private: - self.logger.error("Refusing to add dynamic membership in private group %s for client %s" % (g, imd.hostname)) + if newgroup in self.private: + self.logger.error("Refusing to add dynamic membership " + "in private group %s for client %s" % + (newgroup, imd.hostname)) continue - imd.groups.add(g) + imd.groups.add(newgroup) def merge_additional_data(self, imd, source, data): if not hasattr(imd, source): @@ -703,18 +683,19 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, return True if address in self.addresses: if client in self.addresses[address]: - self.debug_log("Client %s matches address %s" % (client, address)) + self.debug_log("Client %s matches address %s" % + (client, address)) return True else: - self.logger.error("Got request for non-float client %s from %s" \ - % (client, address)) + self.logger.error("Got request for non-float client %s from %s" % + (client, address)) return False resolved = self.resolve_client(addresspair) if resolved.lower() == client.lower(): return True else: - self.logger.error("Got request for %s from incorrect address %s" \ - % (client, address)) + self.logger.error("Got request for %s from incorrect address %s" % + (client, address)) self.logger.error("Resolved to %s" % resolved) return False @@ -732,7 +713,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, try: client = self.resolve_client(address) except MetadataConsistencyError: - self.logger.error("Client %s failed to resolve; metadata problem" % (address[0])) + self.logger.error("Client %s failed to resolve; metadata problem" + % address[0]) return False else: id_method = 'uuid' @@ -764,10 +746,12 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, if client not in self.passwords: if client in self.secure: - self.logger.error("Client %s in secure mode but has no password" % (address[0])) + self.logger.error("Client %s in secure mode but has no password" + % address[0]) return False if password != self.password: - self.logger.error("Client %s used incorrect global password" % (address[0])) + self.logger.error("Client %s used incorrect global password" % + address[0]) return False if client not in self.secure: if client in self.passwords: @@ -775,14 +759,14 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, else: plist = [self.password] if password not in plist: - self.logger.error("Client %s failed to use either allowed password" % \ - (address[0])) + self.logger.error("Client %s failed to use either allowed " + "password" % address[0]) return False else: # client in secure mode and has a client password if password != self.passwords[client]: - self.logger.error("Client %s failed to use client password in secure mode" % \ - (address[0])) + self.logger.error("Client %s failed to use client password in " + "secure mode" % address[0]) return False # populate the session cache if user.decode('utf-8') != 'root': @@ -793,13 +777,7 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, """Hook into statistics interface to toggle clients in bootstrap mode.""" client = meta.hostname if client in self.auth and self.auth[client] == 'bootstrap': - self.logger.info("Asserting client %s auth mode to cert" % client) - xdict = self.clients_xml.find_xml_for_xpath('.//Client[@name="%s"]' % (client)) - if not xdict: - self.logger.error("Metadata: Unable to update profile for client %s. Use of Xinclude?" % client) - raise MetadataConsistencyError - xdict['xquery'][0].set('auth', 'cert') - self.clients_xml.write_xml(xdict['filename'], xdict['xmltree']) + self.update_client(client, dict(auth='cert')) def viz(self, hosts, bundles, key, only_client, colors): """Admin mode viz support.""" @@ -815,14 +793,14 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, def include_group(group): return not only_client or group in clientmeta.groups - groups_tree = lxml.etree.parse(self.data + "/groups.xml") + groups_tree = lxml.etree.parse(os.path.join(self.data, "groups.xml")) try: groups_tree.xinclude() except lxml.etree.XIncludeError: self.logger.error("Failed to process XInclude for file %s" % dest) groups = groups_tree.getroot() categories = {'default': 'grey83'} - viz_str = "" + viz_str = [] egroups = groups.findall("Group") + groups.findall('.//Groups/Group') for group in egroups: if not group.get('category') in categories: @@ -842,10 +820,10 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, instances[profile] = [client] for profile, clist in list(instances.items()): clist.sort() - viz_str += '''\t"%s-instances" [ label="%s", shape="record" ];\n''' \ - % (profile, '|'.join(clist)) - viz_str += '''\t"%s-instances" -> "group-%s";\n''' \ - % (profile, profile) + viz_str.append('"%s-instances" [ label="%s", shape="record" ];' % + (profile, '|'.join(clist))) + viz_str.append('"%s-instances" -> "group-%s";' % + (profile, profile)) if bundles: bundles = [] [bundles.append(bund.get('name')) \ @@ -854,8 +832,8 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, and include_bundle(bund.get('name'))] bundles.sort() for bundle in bundles: - viz_str += '''\t"bundle-%s" [ label="%s", shape="septagon"];\n''' \ - % (bundle, bundle) + viz_str.append('"bundle-%s" [ label="%s", shape="septagon"];' % + (bundle, bundle)) gseen = [] for group in egroups: if group.get('profile', 'false') == 'true': @@ -864,24 +842,25 @@ class Metadata(Bcfg2.Server.Plugin.Plugin, style = "filled" gseen.append(group.get('name')) if include_group(group.get('name')): - viz_str += '\t"group-%s" [label="%s", style="%s", fillcolor=%s];\n' % \ - (group.get('name'), group.get('name'), style, group.get('color')) + viz_str.append('"group-%s" [label="%s", style="%s", fillcolor=%s];' % + (group.get('name'), group.get('name'), style, + group.get('color'))) if bundles: for bundle in group.findall('Bundle'): - viz_str += '\t"group-%s" -> "bundle-%s";\n' % \ - (group.get('name'), bundle.get('name')) - gfmt = '\t"group-%s" [label="%s", style="filled", fillcolor="grey83"];\n' + viz_str.append('"group-%s" -> "bundle-%s";' % + (group.get('name'), bundle.get('name'))) + gfmt = '"group-%s" [label="%s", style="filled", fillcolor="grey83"];' for group in egroups: for parent in group.findall('Group'): if parent.get('name') not in gseen and include_group(parent.get('name')): - viz_str += gfmt % (parent.get('name'), parent.get('name')) + viz_str.append(gfmt % (parent.get('name'), + parent.get('name'))) gseen.append(parent.get("name")) if include_group(group.get('name')): - viz_str += '\t"group-%s" -> "group-%s" ;\n' % \ - (group.get('name'), parent.get('name')) + viz_str.append('"group-%s" -> "group-%s";' % + (group.get('name'), parent.get('name'))) if key: for category in categories: - viz_str += '''\t"''' + category + '''" [label="''' + category + \ - '''", shape="record", style="filled", fillcolor=''' + \ - categories[category] + '''];\n''' - return viz_str + viz_str.append('"%s" [label="%s", shape="record", style="filled", fillcolor="%s"];' % + (category, category, categories[category])) + return "\n".join("\t" + s for s in viz_str) diff --git a/src/lib/Server/Plugins/NagiosGen.py b/src/lib/Server/Plugins/NagiosGen.py index 8a76c130d..d67dac6a6 100644 --- a/src/lib/Server/Plugins/NagiosGen.py +++ b/src/lib/Server/Plugins/NagiosGen.py @@ -27,7 +27,6 @@ class NagiosGen(Bcfg2.Server.Plugin.Plugin, Nagios configuration file based on Bcfg2 data. """ name = 'NagiosGen' - __version__ = '0.7' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Pkgmgr.py b/src/lib/Server/Plugins/Pkgmgr.py index bc11bfdcf..e9254cdcc 100644 --- a/src/lib/Server/Plugins/Pkgmgr.py +++ b/src/lib/Server/Plugins/Pkgmgr.py @@ -1,5 +1,4 @@ '''This module implements a package management scheme for all images''' -__revision__ = '$Revision$' import logging import re @@ -135,7 +134,6 @@ class PkgSrc(Bcfg2.Server.Plugin.XMLSrc): class Pkgmgr(Bcfg2.Server.Plugin.PrioDir): """This is a generator that handles package assignments.""" name = 'Pkgmgr' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' __child__ = PkgSrc __element__ = 'Package' diff --git a/src/lib/Server/Plugins/Probes.py b/src/lib/Server/Plugins/Probes.py index ae1ed4c2b..2af3635e9 100644 --- a/src/lib/Server/Plugins/Probes.py +++ b/src/lib/Server/Plugins/Probes.py @@ -181,7 +181,6 @@ class Probes(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Connector): """A plugin to gather information from a client machine.""" name = 'Probes' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Properties.py b/src/lib/Server/Plugins/Properties.py index 76945b3a0..680881858 100644 --- a/src/lib/Server/Plugins/Properties.py +++ b/src/lib/Server/Plugins/Properties.py @@ -60,7 +60,6 @@ class Properties(Bcfg2.Server.Plugin.Plugin, files into client metadata instances. """ name = 'Properties' - version = '$Revision$' def __init__(self, core, datastore): Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) diff --git a/src/lib/Server/Plugins/Rules.py b/src/lib/Server/Plugins/Rules.py index c66276179..684f0c290 100644 --- a/src/lib/Server/Plugins/Rules.py +++ b/src/lib/Server/Plugins/Rules.py @@ -1,5 +1,4 @@ """This generator provides rule-based entry mappings.""" -__revision__ = '$Revision$' import re import Bcfg2.Server.Plugin @@ -10,7 +9,6 @@ class RulesConfig(Bcfg2.Server.Plugin.SimpleConfig): class Rules(Bcfg2.Server.Plugin.PrioDir): """This is a generator that handles service assignments.""" name = 'Rules' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/SGenshi.py b/src/lib/Server/Plugins/SGenshi.py index f6a98c141..0ba08125e 100644 --- a/src/lib/Server/Plugins/SGenshi.py +++ b/src/lib/Server/Plugins/SGenshi.py @@ -1,5 +1,4 @@ '''This module implements a templating generator based on Genshi''' -__revision__ = '$Revision$' import genshi.input import genshi.template @@ -84,7 +83,6 @@ class SGenshi(SGenshiEntrySet, Bcfg2.Server.Plugin.Structure): """The SGenshi plugin provides templated structures.""" name = 'SGenshi' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' deprecated = True diff --git a/src/lib/Server/Plugins/SSHbase.py b/src/lib/Server/Plugins/SSHbase.py index eb91bea39..2e247caa7 100644 --- a/src/lib/Server/Plugins/SSHbase.py +++ b/src/lib/Server/Plugins/SSHbase.py @@ -1,5 +1,4 @@ '''This module manages ssh key files for bcfg2''' -__revision__ = '$Revision$' import binascii import re @@ -99,7 +98,6 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, """ name = 'SSHbase' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' keypatterns = ["ssh_host_dsa_key", diff --git a/src/lib/Server/Plugins/SSLCA.py b/src/lib/Server/Plugins/SSLCA.py index 7b4a08ae1..0072dc62d 100644 --- a/src/lib/Server/Plugins/SSLCA.py +++ b/src/lib/Server/Plugins/SSLCA.py @@ -16,7 +16,6 @@ class SSLCA(Bcfg2.Server.Plugin.GroupSpool): management of ssl certificates and their keys. """ name = 'SSLCA' - __version__ = '$Id:$' __author__ = 'g.hagger@gmail.com' __child__ = Bcfg2.Server.Plugin.FileBacked key_specs = {} diff --git a/src/lib/Server/Plugins/Statistics.py b/src/lib/Server/Plugins/Statistics.py index 9dbbeec28..265ef95a8 100644 --- a/src/lib/Server/Plugins/Statistics.py +++ b/src/lib/Server/Plugins/Statistics.py @@ -1,5 +1,4 @@ '''This file manages the statistics collected by the BCFG2 Server''' -__revision__ = '$Revision$' import binascii import copy @@ -117,7 +116,6 @@ class Statistics(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.ThreadedStatistics, Bcfg2.Server.Plugin.PullSource): name = 'Statistics' - __version__ = '$Id$' def __init__(self, core, datastore): Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) diff --git a/src/lib/Server/Plugins/Svcmgr.py b/src/lib/Server/Plugins/Svcmgr.py index 6d25c1a6d..f4232ad5c 100644 --- a/src/lib/Server/Plugins/Svcmgr.py +++ b/src/lib/Server/Plugins/Svcmgr.py @@ -1,5 +1,4 @@ """This generator provides service mappings.""" -__revision__ = '$Revision$' import Bcfg2.Server.Plugin @@ -7,6 +6,5 @@ import Bcfg2.Server.Plugin class Svcmgr(Bcfg2.Server.Plugin.PrioDir): """This is a generator that handles service assignments.""" name = 'Svcmgr' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' deprecated = True diff --git a/src/lib/Server/Plugins/Svn.py b/src/lib/Server/Plugins/Svn.py index 9fd6f1051..ae43388ea 100644 --- a/src/lib/Server/Plugins/Svn.py +++ b/src/lib/Server/Plugins/Svn.py @@ -12,7 +12,6 @@ class Svn(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Svn is a version plugin for dealing with Bcfg2 repos.""" name = 'Svn' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/Svn2.py b/src/lib/Server/Plugins/Svn2.py index b8a8e6b7e..8d79348f8 100644 --- a/src/lib/Server/Plugins/Svn2.py +++ b/src/lib/Server/Plugins/Svn2.py @@ -9,7 +9,6 @@ class Svn2(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Version): """Svn is a version plugin for dealing with Bcfg2 repos.""" name = 'Svn2' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' conflicts = ['Svn'] diff --git a/src/lib/Server/Plugins/TCheetah.py b/src/lib/Server/Plugins/TCheetah.py index 49be88881..8879fdef1 100644 --- a/src/lib/Server/Plugins/TCheetah.py +++ b/src/lib/Server/Plugins/TCheetah.py @@ -1,5 +1,4 @@ '''This module implements a templating generator based on Cheetah''' -__revision__ = '$Revision$' import binascii import logging @@ -76,7 +75,6 @@ class TemplateFile: class TCheetah(Bcfg2.Server.Plugin.GroupSpool): """The TCheetah generator implements a templating mechanism for configuration files.""" name = 'TCheetah' - __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' filename_pattern = 'template' es_child_cls = TemplateFile diff --git a/src/lib/Server/Plugins/TGenshi.py b/src/lib/Server/Plugins/TGenshi.py index bc5e00400..3ba0f4272 100644 --- a/src/lib/Server/Plugins/TGenshi.py +++ b/src/lib/Server/Plugins/TGenshi.py @@ -1,5 +1,4 @@ """This module implements a templating generator based on Genshi.""" -__revision__ = '$Revision$' import binascii import logging @@ -131,7 +130,6 @@ class TGenshi(Bcfg2.Server.Plugin.GroupSpool): """ name = 'TGenshi' - __version__ = '$Id$' __author__ = 'jeff@ocjtech.us' filename_pattern = 'template\.(txt|newtxt|xml)' es_child_cls = TemplateFile diff --git a/src/lib/Server/Plugins/Trigger.py b/src/lib/Server/Plugins/Trigger.py index eb3310a4e..b0d21545c 100644 --- a/src/lib/Server/Plugins/Trigger.py +++ b/src/lib/Server/Plugins/Trigger.py @@ -17,7 +17,6 @@ class Trigger(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Statistics): """Trigger is a plugin that calls external scripts (on the server).""" name = 'Trigger' - __version__ = '$Id' __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): diff --git a/src/lib/Server/Plugins/__init__.py b/src/lib/Server/Plugins/__init__.py index c69c37452..a36a9b766 100644 --- a/src/lib/Server/Plugins/__init__.py +++ b/src/lib/Server/Plugins/__init__.py @@ -1,5 +1,4 @@ """Imports for Bcfg2.Server.Plugins.""" -__revision__ = '$Revision$' __all__ = [ 'Account', diff --git a/src/lib/Server/Reports/importscript.py b/src/lib/Server/Reports/importscript.py index 7dfac6fae..cbdf019f5 100755 --- a/src/lib/Server/Reports/importscript.py +++ b/src/lib/Server/Reports/importscript.py @@ -3,7 +3,6 @@ Imports statistics.xml and clients.xml files in to database backend for new statistics engine """ -__revision__ = '$Revision$' import binascii import os diff --git a/src/lib/Server/Reports/nisauth.py b/src/lib/Server/Reports/nisauth.py index 6fc346f1e..b3e37113b 100644 --- a/src/lib/Server/Reports/nisauth.py +++ b/src/lib/Server/Reports/nisauth.py @@ -4,8 +4,6 @@ from Bcfg2.Server.Reports.settings import AUTHORIZED_GROUP """Checks with NIS to see if the current user is in the support group""" -__revision__ = "$Revision: $" - class NISAUTHError(Exception): """NISAUTHError is raised when somehting goes boom.""" diff --git a/src/lib/Server/__init__.py b/src/lib/Server/__init__.py index 25f397565..96777b0bf 100644 --- a/src/lib/Server/__init__.py +++ b/src/lib/Server/__init__.py @@ -1,6 +1,4 @@ -# $Id$ """This is the set of modules for Bcfg2.Server.""" -__revision__ = '$Revision$' __all__ = ["Admin", "Core", "FileMonitor", "Plugin", "Plugins", "Hostbase", "Reports", "Snapshots"] diff --git a/src/lib/__init__.py b/src/lib/__init__.py index d36c0a00a..357f66f6d 100644 --- a/src/lib/__init__.py +++ b/src/lib/__init__.py @@ -1,4 +1,3 @@ """Base modules definition.""" -__revision__ = '$Revision$' __all__ = ['Server', 'Client', 'Component', 'Logger', 'Options', 'Proxy', 'Statistics'] diff --git a/src/sbin/bcfg2 b/src/sbin/bcfg2 index 1d1cc8424..fb34e627b 100755 --- a/src/sbin/bcfg2 +++ b/src/sbin/bcfg2 @@ -1,7 +1,6 @@ #!/usr/bin/env python """Bcfg2 Client""" -__revision__ = '$Revision$' import fcntl import logging diff --git a/src/sbin/bcfg2-build-reports b/src/sbin/bcfg2-build-reports index 7122fb300..7fa08110a 100755 --- a/src/sbin/bcfg2-build-reports +++ b/src/sbin/bcfg2-build-reports @@ -4,8 +4,6 @@ bcfg2-build-reports generates & distributes reports of statistic information for Bcfg2.""" -__revision__ = '$Revision$' - import copy import getopt import re diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info index 808761d7e..b68434e11 100755 --- a/src/sbin/bcfg2-info +++ b/src/sbin/bcfg2-info @@ -1,7 +1,6 @@ #!/usr/bin/env python """This tool loads the Bcfg2 core into an interactive debugger.""" -__revision__ = '$Revision$' from code import InteractiveConsole import cmd @@ -51,8 +50,7 @@ profile <command> <args> - Profile a single bcfg2-info command quit - Exit the bcfg2-info command line showentries <hostname> <type> - Show abstract configuration entries for a given host showclient <client1> <client2> - Show metadata for given hosts -update - Process pending file events -version - Print version of this tool""" +update - Process pending file events""" BUILDDIR_USAGE = """Usage: builddir [-f] <hostname> <output dir> @@ -192,10 +190,6 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.Core): """Process pending filesystem events.""" self.fam.handle_events_in_interval(0.1) - def do_version(self, _): - """Print out code version.""" - print(__revision__) - def do_build(self, args): """Build client configuration.""" alist = args.split() diff --git a/src/sbin/bcfg2-lint b/src/sbin/bcfg2-lint index 2d371f4aa..ad6b6139c 100755 --- a/src/sbin/bcfg2-lint +++ b/src/sbin/bcfg2-lint @@ -1,7 +1,6 @@ #!/usr/bin/env python """This tool examines your Bcfg2 specifications for errors.""" -__revision__ = '$Revision$' import sys import inspect diff --git a/src/sbin/bcfg2-ping-sweep b/src/sbin/bcfg2-ping-sweep index 70f718690..be8994be3 100755 --- a/src/sbin/bcfg2-ping-sweep +++ b/src/sbin/bcfg2-ping-sweep @@ -3,8 +3,6 @@ """Generates hostinfo.xml at a regular interval.""" -__revision__ = '$Revision$' - from os import dup2, execl, fork, uname, wait import sys import time diff --git a/src/sbin/bcfg2-reports b/src/sbin/bcfg2-reports index 6acdd27e3..1f101b9a7 100755 --- a/src/sbin/bcfg2-reports +++ b/src/sbin/bcfg2-reports @@ -1,6 +1,5 @@ #!/usr/bin/env python """Query reporting system for client status.""" -__revision__ = '$Revision$' import os import sys diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server index 546d5a249..89bc7c331 100755 --- a/src/sbin/bcfg2-server +++ b/src/sbin/bcfg2-server @@ -1,7 +1,6 @@ #!/usr/bin/env python """The XML-RPC Bcfg2 server.""" -__revision__ = '$Revision$' import logging import os.path diff --git a/src/sbin/bcfg2-yum-helper b/src/sbin/bcfg2-yum-helper index dc46bb81a..2da7c6336 100755 --- a/src/sbin/bcfg2-yum-helper +++ b/src/sbin/bcfg2-yum-helper @@ -5,8 +5,6 @@ the right way to get around that in long-running processes it to have a short-lived helper. No, seriously -- check out the yum-updatesd code. It's pure madness. """ -__revision__ = '$Revision$' - import os import sys import yum diff --git a/testsuite/TestFrame.py b/testsuite/TestFrame.py deleted file mode 100644 index a76c97eec..000000000 --- a/testsuite/TestFrame.py +++ /dev/null @@ -1,104 +0,0 @@ -import lxml.etree - -import Bcfg2.Client.Frame -import Bcfg2.Client.Tools - -c1 = lxml.etree.XML("<Configuration><Bundle name='foo'><Configfile name='/tmp/test12' owner='root' group='root' empty='true' perms='644'/></Bundle></Configuration>") - -c2 = lxml.etree.XML("<Configuration><Bundle name='foo'><Configfile name='/tmp/test12' owner='root' group='root' empty='true' perms='644'/><Configfile name='/tmp/test12' owner='root' group='root' empty='true' perms='644'/></Bundle></Configuration>") - - -class DriverInitFail(object): - def __init__(self, *args): - raise Bcfg2.Client.Tools.toolInstantiationError - - -class DriverInventoryFail(object): - __name__ = 'dif' - - def __init__(self, logger, setup, config): - self.config = config - self.handled = [] - self.modified = [] - self.extra = [] - - def Inventory(self): - raise Error - - -class TestFrame(object): - def test__init(self): - setup = {} - times = {} - config = lxml.etree.Element('Configuration') - frame = Bcfg2.Client.Frame.Frame(config, setup, times, [], False) - assert frame.tools == [] - - def test__init2(self): - setup = {} - times = {} - frame2 = Bcfg2.Client.Frame.Frame(c1, setup, times, ['POSIX'], False) - assert len(frame2.tools) == 1 - - def test__init3(self): - setup = {} - times = {} - frame3 = Bcfg2.Client.Frame.Frame(c2, setup, times, ['foo'], False) - assert len(frame3.tools) == 0 - - def test__init4(self): - setup = {} - times = {} - frame = Bcfg2.Client.Frame.Frame(c2, setup, times, [DriverInitFail], False) - assert len(frame.tools) == 0 - - def test__Decide_Inventory(self): - setup = {'remove': 'none', - 'bundle': [], - 'interactive': False} - times = {} - frame = Bcfg2.Client.Frame.Frame(c2, setup, times, - [DriverInventoryFail], False) - assert len(frame.tools) == 1 - frame.Inventory() - assert len([x for x in list(frame.states.values()) if x]) == 0 - frame.Decide() - assert len(frame.whitelist) - - def test__Decide_Bundle(self): - setup = {'remove': 'none', - 'bundle': ['bar'], - 'interactive': False} - times = {} - frame = Bcfg2.Client.Frame.Frame(c2, setup, times, - [DriverInventoryFail], False) - assert len(frame.tools) == 1 - frame.Inventory() - assert len([x for x in list(frame.states.values()) if x]) == 0 - frame.Decide() - assert len(frame.whitelist) == 0 - - def test__Decide_Dryrun(self): - setup = {'remove': 'none', - 'bundle': [], - 'interactive': False} - times = {} - frame = Bcfg2.Client.Frame.Frame(c2, setup, times, - [DriverInventoryFail], True) - assert len(frame.tools) == 1 - frame.Inventory() - assert len([x for x in list(frame.states.values()) if x]) == 0 - frame.Decide() - assert len(frame.whitelist) == 0 - - def test__GenerateStats(self): - setup = {'remove': 'none', - 'bundle': [], - 'interactive': False} - times = {} - frame = Bcfg2.Client.Frame.Frame(c2, setup, times, - [DriverInventoryFail], False) - frame.Inventory() - frame.Decide() - stats = frame.GenerateStats() - assert len(stats.findall('.//Bad')[0].getchildren()) != 0 diff --git a/testsuite/TestOptions.py b/testsuite/TestOptions.py deleted file mode 100644 index 735455d45..000000000 --- a/testsuite/TestOptions.py +++ /dev/null @@ -1,103 +0,0 @@ -import os -import sys - -import Bcfg2.Options - - -class TestOption(object): - def test__init(self): - o = Bcfg2.Options.Option('foo', False, cmd='-F') - try: - p = Bcfg2.Options.Option('foo', False, cmd='--F') - assert False - except Bcfg2.Options.OptionFailure: - pass - - def test_parse(self): - o = Bcfg2.Options.Option('foo', 'test4', cmd='-F', env='TEST2', - odesc='bar', cf=('communication', 'password')) - o.parse([], ['-F', 'test']) - assert o.value == 'test' - o.parse([('-F', 'test2')], []) - assert o.value == 'test2' - os.environ['TEST2'] = 'test3' - o.parse([], []) - assert o.value == 'test3' - del os.environ['TEST2'] - o.parse([], []) - print(o.value) - assert o.value == 'foobat' - o.cf = ('communication', 'pwd') - o.parse([], []) - print(o.value) - assert o.value == 'test4' - o.cf = False - o.parse([], []) - assert o.value == 'test4' - - def test_cook(self): - # check that default value isn't cooked - o1 = Bcfg2.Options.Option('foo', 'test4', cook=Bcfg2.Options.bool_cook) - o1.parse([], []) - assert o1.value == 'test4' - o2 = Bcfg2.Options.Option('foo', False, cmd='-F') - o2.parse([('-F', '')], []) - assert o2.value == True - - -class TestOptionSet(object): - def test_buildGetopt(self): - opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')), - ('bar', Bcfg2.Options.Option('foo', 'test2')), - ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H', - odesc='1'))] - os = Bcfg2.Options.OptionSet(opts) - res = os.buildGetopt() - assert 'H:' in res and 'G' in res and len(res) == 3 - - def test_buildLongGetopt(self): - opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')), - ('bar', Bcfg2.Options.Option('foo', 'test2')), - ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='--H', - odesc='1', long_arg=True))] - os = Bcfg2.Options.OptionSet(opts) - res = os.buildLongGetopt() - print(res) - assert 'H=' in res and len(res) == 1 - - def test_parse(self): - opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')), - ('bar', Bcfg2.Options.Option('foo', 'test2')), - ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H', - odesc='1'))] - os = Bcfg2.Options.OptionSet(opts) - try: - os.parse(['-G', '-H']) - assert False - except SystemExit: - pass - os2 = Bcfg2.Options.OptionSet(opts) - try: - os2.parse(['-h']) - assert False - except SystemExit: - pass - os3 = Bcfg2.Options.OptionSet(opts) - os3.parse(['-G']) - assert os3['foo'] == True - - -class TestOptionParser(object): - def test__init(self): - opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-h')), - ('bar', Bcfg2.Options.Option('foo', 'test2')), - ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H', - odesc='1'))] - os1 = Bcfg2.Options.OptionParser(opts) - assert Bcfg2.Options.Option.cfpath == '/etc/bcfg2.conf' - sys.argv = ['foo', '-C', '/usr/local/etc/bcfg2.conf'] - os2 = Bcfg2.Options.OptionParser(opts) - assert Bcfg2.Options.Option.cfpath == '/usr/local/etc/bcfg2.conf' - sys.argv = [] - os3 = Bcfg2.Options.OptionParser(opts) - assert Bcfg2.Options.Option.cfpath == '/etc/bcfg2.conf' diff --git a/testsuite/TestPlugin.py b/testsuite/TestPlugin.py deleted file mode 100644 index aa619249a..000000000 --- a/testsuite/TestPlugin.py +++ /dev/null @@ -1,122 +0,0 @@ -import gamin -import lxml.etree -import os - -import Bcfg2.Server.Core -from Bcfg2.Server.Plugin import EntrySet - - -class es_testtype(object): - def __init__(self, name, properties, specific): - self.name = name - self.properties = properties - self.specific = specific - self.handled = 0 - self.built = 0 - - def handle_event(self, event): - self.handled += 1 - - def bind_entry(self, entry, metadata): - entry.set('bound', '1') - entry.set('name', self.name) - self.built += 1 - - -class metadata(object): - def __init__(self, hostname): - self.hostname = hostname - self.groups = ['base', 'debian'] - -#FIXME add test_specific - - -class test_entry_set(object): - def __init__(self): - self.dirname = '/tmp/estest-%d' % os.getpid() - os.path.isdir(self.dirname) or os.mkdir(self.dirname) - self.metadata = metadata('testhost') - self.es = EntrySet('template', self.dirname, None, es_testtype) - self.e = Bcfg2.Server.Core.GaminEvent(1, 'template', - gamin.GAMExists) - - def test_init(self): - es = self.es - e = self.e - e.action = 'exists' - es.handle_event(e) - es.handle_event(e) - assert len(es.entries) == 1 - assert list(es.entries.values())[0].handled == 2 - e.action = 'changed' - es.handle_event(e) - assert list(es.entries.values())[0].handled == 3 - - def test_info(self): - """Test info and info.xml handling.""" - es = self.es - e = self.e - dirname = self.dirname - metadata = self.metadata - - # test 'info' handling - assert es.metadata['group'] == 'root' - self.mk_info(dirname) - e.filename = 'info' - e.action = 'exists' - es.handle_event(e) - assert es.metadata['group'] == 'sys' - e.action = 'deleted' - es.handle_event(e) - assert es.metadata['group'] == 'root' - - # test 'info.xml' handling - assert es.infoxml == None - self.mk_info_xml(dirname) - e.filename = 'info.xml' - e.action = 'exists' - es.handle_event(e) - assert es.infoxml - e.action = 'deleted' - es.handle_event(e) - assert es.infoxml == None - - def test_file_building(self): - """Test file building.""" - self.test_init() - ent = lxml.etree.Element('foo') - self.es.bind_entry(ent, self.metadata) - print(list(self.es.entries.values())[0]) - assert list(self.es.entries.values())[0].built == 1 - - def test_host_specific_file_building(self): - """Add a host-specific template and build it.""" - self.e.filename = 'template.H_%s' % self.metadata.hostname - self.e.action = 'exists' - self.es.handle_event(self.e) - assert len(self.es.entries) == 1 - ent = lxml.etree.Element('foo') - self.es.bind_entry(ent, self.metadata) - # FIXME need to test that it built the _right_ file here - - def test_deletion(self): - """Test deletion of files.""" - self.test_init() - self.e.filename = 'template' - self.e.action = 'deleted' - self.es.handle_event(self.e) - assert len(self.es.entries) == 0 - - # TODO - how to clean up the temp dir & files after tests done? - - def mk_info(self, dir): - i = open("%s/info" % dir, 'w') - i.write('owner: root\n') - i.write('group: sys\n') - i.write('perms: 0600\n') - i.close - - def mk_info_xml(self, dir): - i = open("%s/info.xml" % dir, 'w') - i.write('<FileInfo><Info owner="root" group="other" perms="0600" /></FileInfo>\n') - i.close diff --git a/testsuite/Testlib/TestOptions.py b/testsuite/Testlib/TestOptions.py new file mode 100644 index 000000000..f5833a54a --- /dev/null +++ b/testsuite/Testlib/TestOptions.py @@ -0,0 +1,124 @@ +import os +import sys +import unittest +from mock import Mock, patch +import Bcfg2.Options + +class TestOption(unittest.TestCase): + def test__init(self): + self.assertRaises(Bcfg2.Options.OptionFailure, + Bcfg2.Options.Option, + 'foo', False, cmd='f') + self.assertRaises(Bcfg2.Options.OptionFailure, + Bcfg2.Options.Option, + 'foo', False, cmd='--f') + self.assertRaises(Bcfg2.Options.OptionFailure, + Bcfg2.Options.Option, + 'foo', False, cmd='-foo') + self.assertRaises(Bcfg2.Options.OptionFailure, + Bcfg2.Options.Option, + 'foo', False, cmd='-foo', long_arg=True) + + @patch('ConfigParser.ConfigParser') + @patch('__builtin__.open') + def test_getCFP(self, mock_open, mock_cp): + mock_cp.return_value = Mock() + o = Bcfg2.Options.Option('foo', False, cmd='-f') + self.assertFalse(o._Option__cfp) + o.getCFP() + mock_cp.assert_any_call() + mock_open.assert_any_call(Bcfg2.Options.DEFAULT_CONFIG_LOCATION) + self.assertTrue(mock_cp.return_value.readfp.called) + + @patch('Bcfg2.Options.Option.cfp') + def test_parse(self, mock_cfp): + cf = ('communication', 'password') + o = Bcfg2.Options.Option('foo', 'test4', cmd='-F', env='TEST2', + odesc='bar', cf=cf) + o.parse([], ['-F', 'test']) + self.assertEqual(o.value, 'test') + o.parse([('-F', 'test2')], []) + self.assertEqual(o.value, 'test2') + + os.environ['TEST2'] = 'test3' + o.parse([], []) + self.assertEqual(o.value, 'test3') + del os.environ['TEST2'] + + mock_cfp.get = Mock() + mock_cfp.get.return_value = 'test5' + o.parse([], []) + self.assertEqual(o.value, 'test5') + mock_cfp.get.assert_any_call(*cf) + + o.cf = False + o.parse([], []) + assert o.value == 'test4' + + def test_cook(self): + # check that default value isn't cooked + o1 = Bcfg2.Options.Option('foo', 'test4', cook=Bcfg2.Options.bool_cook) + o1.parse([], []) + assert o1.value == 'test4' + o2 = Bcfg2.Options.Option('foo', False, cmd='-F') + o2.parse([('-F', '')], []) + assert o2.value == True + + +class TestOptionSet(unittest.TestCase): + def test_buildGetopt(self): + opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')), + ('bar', Bcfg2.Options.Option('foo', 'test2')), + ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H', + odesc='1'))] + oset = Bcfg2.Options.OptionSet(opts) + res = oset.buildGetopt() + self.assertIn('H:', res) + self.assertIn('G', res) + self.assertEqual(len(res), 3) + + def test_buildLongGetopt(self): + opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')), + ('bar', Bcfg2.Options.Option('foo', 'test2')), + ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='--H', + odesc='1', long_arg=True))] + oset = Bcfg2.Options.OptionSet(opts) + res = oset.buildLongGetopt() + self.assertIn('H=', res) + self.assertEqual(len(res), 1) + + def test_parse(self): + opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-G')), + ('bar', Bcfg2.Options.Option('foo', 'test2')), + ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H', + odesc='1'))] + oset = Bcfg2.Options.OptionSet(opts) + self.assertRaises(SystemExit, + oset.parse, + ['-G', '-H']) + oset2 = Bcfg2.Options.OptionSet(opts) + self.assertRaises(SystemExit, + oset2.parse, + ['-h']) + oset3 = Bcfg2.Options.OptionSet(opts) + oset3.parse(['-G']) + self.assertTrue(oset3['foo']) + + +class TestOptionParser(unittest.TestCase): + def test__init(self): + opts = [('foo', Bcfg2.Options.Option('foo', 'test1', cmd='-h')), + ('bar', Bcfg2.Options.Option('foo', 'test2')), + ('baz', Bcfg2.Options.Option('foo', 'test1', cmd='-H', + odesc='1'))] + oset1 = Bcfg2.Options.OptionParser(opts) + self.assertEqual(Bcfg2.Options.Option.cfpath, + Bcfg2.Options.DEFAULT_CONFIG_LOCATION) + sys.argv = ['foo', '-C', '/usr/local/etc/bcfg2.conf'] + oset2 = Bcfg2.Options.OptionParser(opts) + self.assertEqual(Bcfg2.Options.Option.cfpath, + '/usr/local/etc/bcfg2.conf') + sys.argv = [] + oset3 = Bcfg2.Options.OptionParser(opts) + self.assertEqual(Bcfg2.Options.Option.cfpath, + Bcfg2.Options.DEFAULT_CONFIG_LOCATION) diff --git a/testsuite/Testlib/TestServer/TestPlugins/TestMetadata.py b/testsuite/Testlib/TestServer/TestPlugins/TestMetadata.py new file mode 100644 index 000000000..4d0b96ee7 --- /dev/null +++ b/testsuite/Testlib/TestServer/TestPlugins/TestMetadata.py @@ -0,0 +1,898 @@ +import os +import copy +import time +import socket +import unittest +import lxml.etree +from mock import Mock, patch +import Bcfg2.Server.Plugin +from Bcfg2.Server.Plugins.Metadata import * + +XI_NAMESPACE = "http://www.w3.org/2001/XInclude" +XI = "{%s}" % XI_NAMESPACE + +clients_test_tree = lxml.etree.XML(''' +<Clients> + <Client name="client1" address="1.2.3.1" auth="cert" + location="floating" password="password2" profile="group1"/> + <Client name="client2" address="1.2.3.2" secure="true" profile="group2"/> + <Client name="client3" address="1.2.3.3" uuid="uuid1" profile="group1" + password="password2"> + <Alias name="alias1"/> + </Client> + <Client name="client4" profile="group1"> + <Alias name="alias2" address="1.2.3.2"/> + <Alias name="alias3"/> + </Client> + <Client name="client5" profile="group1"/> + <Client name="client6" profile="group1" auth="bootstrap"/> + <Client name="client7" profile="group1" auth="cert" address="1.2.3.4"/> + <Client name="client8" profile="group1" auth="cert+password" + address="1.2.3.5"/> + <Client name="client9" profile="group2" secure="true" password="password3"/> +</Clients>''').getroottree() + +groups_test_tree = lxml.etree.XML(''' +<Groups xmlns:xi="http://www.w3.org/2001/XInclude"> + <Group name="group1" default="true" profile="true" public="true" + category="category1"/> + <Group name="group2" profile="true" public="true" category="category1"> + <Bundle name="bundle1"/> + <Bundle name="bundle2"/> + <Group name="group1"/> + <Group name="group4"/> + </Group> + <Group name="group3" category="category2" public="false"/> + <Group name="group4" category="category1"> + <Group name="group1"/> + <Group name="group6"/> + </Group> + <Group name="group5"/> + <Group name="group7"> + <Bundle name="bundle3"/> + </Group> + <Group name="group8"> + <Group name="group9"/> + </Group> +</Groups>''').getroottree() + +datastore = "/" + +def get_metadata_object(core=None, watch_clients=False): + if core is None: + core = Mock() + metadata = Metadata(core, datastore, watch_clients=watch_clients) + #metadata.logger = Mock() + return metadata + + +class TestXMLMetadataConfig(unittest.TestCase): + def get_config_object(self, basefile=None, core=None, watch_clients=False): + self.metadata = get_metadata_object(core=core, + watch_clients=watch_clients) + return XMLMetadataConfig(self.metadata, watch_clients, basefile) + + def test_xdata(self): + config = self.get_config_object() + # we can't use assertRaises here because xdata is a property + try: + config.xdata + except MetadataRuntimeError: + pass + except: + assert False + config.data = "<test/>" + self.assertEqual(config.xdata, "<test/>") + + def test_base_xdata(self): + config = self.get_config_object() + # we can't use assertRaises here because base_xdata is a property + try: + config.base_xdata + except MetadataRuntimeError: + pass + except: + assert False + config.basedata = "<test/>" + self.assertEqual(config.base_xdata, "<test/>") + + def test_add_monitor(self): + core = Mock() + config = self.get_config_object(core=core) + config.extras = [] + config.add_monitor("test.xml") + self.assertFalse(core.fam.AddMonitor.called) + self.assertEqual(config.extras, []) + + config = self.get_config_object(core=core, watch_clients=True) + config.add_monitor("test.xml") + core.fam.AddMonitor.assert_called_with(os.path.join(self.metadata.data, + "test.xml"), + self.metadata) + self.assertItemsEqual(config.extras, ["test.xml"]) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.add_monitor") + @patch("lxml.etree.parse") + def test_load_xml(self, mock_parse, mock_add_monitor): + config = self.get_config_object("clients.xml") + mock_parse.side_effect = lxml.etree.XMLSyntaxError(None, None, None, + None) + config.load_xml() + self.assertIsNone(config.data) + self.assertIsNone(config.basedata) + + config.data = None + config.basedata = None + mock_parse.side_effect = None + mock_parse.return_value.findall = Mock(return_value=[]) + config.load_xml() + self.assertIsNotNone(config.data) + self.assertIsNotNone(config.basedata) + + config.data = None + config.basedata = None + mock_parse.return_value.findall = \ + Mock(return_value=[lxml.etree.Element(XI + "include", + href="more.xml"), + lxml.etree.Element(XI + "include", + href="evenmore.xml")]) + config.load_xml() + mock_add_monitor.assert_any_call("more.xml") + mock_add_monitor.assert_any_call("evenmore.xml") + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.write_xml") + def test_write(self, mock_write_xml): + config = self.get_config_object("clients.xml") + config.basedata = "<test/>" + config.write() + mock_write_xml.assert_called_with(os.path.join(self.metadata.data, + "clients.xml"), + "<test/>") + + @patch('Bcfg2.Server.Plugins.Metadata.locked', Mock(return_value=False)) + @patch('fcntl.lockf', Mock()) + @patch('__builtin__.open') + @patch('os.unlink') + @patch('os.rename') + @patch('os.path.islink') + @patch('os.readlink') + def test_write_xml(self, mock_readlink, mock_islink, mock_rename, + mock_unlink, mock_open): + fname = "clients.xml" + config = self.get_config_object(fname) + fpath = os.path.join(self.metadata.data, fname) + tmpfile = "%s.new" % fpath + linkdest = os.path.join(self.metadata.data, "client-link.xml") + + mock_islink.return_value = False + + config.write_xml(fpath, clients_test_tree) + mock_open.assert_called_with(tmpfile, "w") + self.assertTrue(mock_open.return_value.write.called) + mock_islink.assert_called_with(fpath) + mock_rename.assert_called_with(tmpfile, fpath) + + mock_islink.return_value = True + mock_readlink.return_value = linkdest + config.write_xml(fpath, clients_test_tree) + mock_rename.assert_called_with(tmpfile, linkdest) + + mock_rename.side_effect = OSError + self.assertRaises(MetadataRuntimeError, + config.write_xml, fpath, clients_test_tree) + + mock_open.return_value.write.side_effect = IOError + self.assertRaises(MetadataRuntimeError, + config.write_xml, fpath, clients_test_tree) + mock_unlink.assert_called_with(tmpfile) + + mock_open.side_effect = IOError + self.assertRaises(MetadataRuntimeError, + config.write_xml, fpath, clients_test_tree) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + @patch('lxml.etree.parse') + def test_find_xml_for_xpath(self, mock_parse): + config = self.get_config_object("groups.xml") + config.basedata = groups_test_tree + xpath = "//Group[@name='group1']" + self.assertItemsEqual(config.find_xml_for_xpath(xpath), + dict(filename=os.path.join(self.metadata.data, + "groups.xml"), + xmltree=groups_test_tree, + xquery=groups_test_tree.xpath(xpath))) + + self.assertEqual(config.find_xml_for_xpath("//boguselement"), dict()) + + config.extras = ["foo.xml", "bar.xml", "clients.xml"] + + def parse_side_effect(fname): + if fname == os.path.join(self.metadata.data, "clients.xml"): + return clients_test_tree + else: + return lxml.etree.XML("<null/>").getroottree() + + mock_parse.side_effect = parse_side_effect + xpath = "//Client[@secure='true']" + self.assertItemsEqual(config.find_xml_for_xpath(xpath), + dict(filename=os.path.join(self.metadata.data, + "clients.xml"), + xmltree=clients_test_tree, + xquery=clients_test_tree.xpath(xpath))) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml") + def test_HandleEvent(self, mock_load_xml): + config = self.get_config_object("groups.xml") + evt = Mock() + evt.filename = os.path.join(self.metadata.data, "groups.xml") + evt.code2str = Mock(return_value="changed") + self.assertTrue(config.HandleEvent(evt)) + mock_load_xml.assert_called_with() + + +class TestClientMetadata(unittest.TestCase): + def test_inGroup(self): + cm = ClientMetadata("client1", "group1", ["group1", "group2"], + ["bundle1"], [], [], [], None, None, None) + self.assertTrue(cm.inGroup("group1")) + self.assertFalse(cm.inGroup("group3")) + + +class TestMetadata(unittest.TestCase): + def test__init_no_fam(self): + # test with watch_clients=False + core = Mock() + metadata = get_metadata_object(core=core) + self.check_metadata_object(metadata) + self.assertEqual(metadata.states, dict()) + + def test__init_with_fam(self): + # test with watch_clients=True + core = Mock() + core.fam = Mock() + metadata = get_metadata_object(core=core, watch_clients=True) + self.assertEqual(len(metadata.states), 2) + core.fam.AddMonitor.assert_any_call(os.path.join(metadata.data, + "groups.xml"), + metadata) + core.fam.AddMonitor.assert_any_call(os.path.join(metadata.data, + "clients.xml"), + metadata) + + core.fam.reset_mock() + core.fam.AddMonitor = Mock(side_effect=IOError) + self.assertRaises(Bcfg2.Server.Plugin.PluginInitError, + get_metadata_object, + core=core, watch_clients=True) + + def check_metadata_object(self, metadata): + self.assertIsInstance(metadata, Bcfg2.Server.Plugin.Plugin) + self.assertIsInstance(metadata, Bcfg2.Server.Plugin.Metadata) + self.assertIsInstance(metadata, Bcfg2.Server.Plugin.Statistics) + self.assertIsInstance(metadata.clients_xml, XMLMetadataConfig) + self.assertIsInstance(metadata.groups_xml, XMLMetadataConfig) + self.assertIsInstance(metadata.query, MetadataQuery) + + @patch('os.makedirs', Mock()) + @patch('__builtin__.open') + def test_init_repo(self, mock_open): + groups = "groups %s" + os_selection = "os" + clients = "clients %s" + Metadata.init_repo(datastore, groups, os_selection, clients) + mock_open.assert_any_call(os.path.join(datastore, "Metadata", + "groups.xml"), "w") + mock_open.assert_any_call(os.path.join(datastore, "Metadata", + "clients.xml"), "w") + + @patch('lxml.etree.parse') + def test_get_groups(self, mock_parse): + metadata = get_metadata_object() + mock_parse.return_value = groups_test_tree + groups = metadata.get_groups() + mock_parse.assert_called_with(os.path.join(datastore, "Metadata", + "groups.xml")) + self.assertIsInstance(groups, lxml.etree._Element) + + def test_search_xdata_name(self): + # test finding a node with the proper name + metadata = get_metadata_object() + tree = groups_test_tree + res = metadata._search_xdata("Group", "group1", tree) + self.assertIsInstance(res, lxml.etree._Element) + self.assertEqual(res.get("name"), "group1") + + def test_search_xdata_alias(self): + # test finding a node with the wrong name but correct alias + metadata = get_metadata_object() + tree = clients_test_tree + res = metadata._search_xdata("Client", "alias3", tree, alias=True) + self.assertIsInstance(res, lxml.etree._Element) + self.assertNotEqual(res.get("name"), "alias3") + + def test_search_xdata_not_found(self): + # test failure finding a node + metadata = get_metadata_object() + tree = clients_test_tree + res = metadata._search_xdata("Client", "bogus_client", tree, alias=True) + self.assertIsNone(res) + + def search_xdata(self, tag, name, tree, alias=False): + metadata = get_metadata_object() + res = metadata._search_xdata(tag, name, tree, alias=alias) + self.assertIsInstance(res, lxml.etree._Element) + if not alias: + self.assertEqual(res.get("name"), name) + + def test_search_group(self): + # test finding a group with the proper name + tree = groups_test_tree + self.search_xdata("Group", "group1", tree) + + def test_search_bundle(self): + # test finding a bundle with the proper name + tree = groups_test_tree + self.search_xdata("Bundle", "bundle1", tree) + + def test_search_client(self): + # test finding a client with the proper name + tree = clients_test_tree + self.search_xdata("Client", "client1", tree, alias=True) + self.search_xdata("Client", "alias1", tree, alias=True) + + def test_add_group(self): + metadata = get_metadata_object() + metadata.groups_xml.write = Mock() + metadata.groups_xml.data = lxml.etree.XML('<Groups/>').getroottree() + metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data) + + metadata.add_group("test1", dict()) + metadata.groups_xml.write.assert_any_call() + grp = metadata.search_group("test1", metadata.groups_xml.base_xdata) + self.assertIsNotNone(grp) + self.assertEqual(grp.attrib, dict(name='test1')) + + # have to call this explicitly -- usually load_xml does this + # on FAM events + metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data) + + metadata.add_group("test2", dict(foo='bar')) + metadata.groups_xml.write.assert_any_call() + grp = metadata.search_group("test2", metadata.groups_xml.base_xdata) + self.assertIsNotNone(grp) + self.assertEqual(grp.attrib, dict(name='test2', foo='bar')) + + # have to call this explicitly -- usually load_xml does this + # on FAM events + metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data) + + metadata.groups_xml.write.reset_mock() + self.assertRaises(MetadataConsistencyError, + metadata.add_group, + "test1", dict()) + self.assertFalse(metadata.groups_xml.write.called) + + def test_update_group(self): + metadata = get_metadata_object() + metadata.groups_xml.write_xml = Mock() + metadata.groups_xml.data = copy.deepcopy(groups_test_tree) + metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data) + + metadata.update_group("group1", dict(foo="bar")) + grp = metadata.search_group("group1", metadata.groups_xml.base_xdata) + self.assertIsNotNone(grp) + self.assertIn("foo", grp.attrib) + self.assertEqual(grp.get("foo"), "bar") + self.assertTrue(metadata.groups_xml.write_xml.called) + + self.assertRaises(MetadataConsistencyError, + metadata.update_group, + "bogus_group", dict()) + + def test_remove_group(self): + metadata = get_metadata_object() + metadata.groups_xml.write_xml = Mock() + metadata.groups_xml.data = copy.deepcopy(groups_test_tree) + metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data) + + metadata.remove_group("group5") + grp = metadata.search_group("group5", metadata.groups_xml.base_xdata) + self.assertIsNone(grp) + self.assertTrue(metadata.groups_xml.write_xml.called) + + self.assertRaises(MetadataConsistencyError, + metadata.remove_group, + "bogus_group") + + def test_add_bundle(self): + metadata = get_metadata_object() + metadata.groups_xml.write = Mock() + metadata.groups_xml.data = lxml.etree.XML('<Groups/>').getroottree() + metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data) + + metadata.add_bundle("bundle1") + metadata.groups_xml.write.assert_any_call() + bundle = metadata.search_bundle("bundle1", + metadata.groups_xml.base_xdata) + self.assertIsNotNone(bundle) + self.assertEqual(bundle.attrib, dict(name='bundle1')) + + # have to call this explicitly -- usually load_xml does this + # on FAM events + metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data) + + metadata.groups_xml.write.reset_mock() + self.assertRaises(MetadataConsistencyError, + metadata.add_bundle, + "bundle1") + self.assertFalse(metadata.groups_xml.write.called) + + def test_remove_bundle(self): + metadata = get_metadata_object() + metadata.groups_xml.write_xml = Mock() + metadata.groups_xml.data = copy.deepcopy(groups_test_tree) + metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data) + + metadata.remove_bundle("bundle1") + grp = metadata.search_bundle("bundle1", metadata.groups_xml.base_xdata) + self.assertIsNone(grp) + self.assertTrue(metadata.groups_xml.write_xml.called) + + self.assertRaises(MetadataConsistencyError, + metadata.remove_bundle, + "bogus_bundle") + + def test_add_client(self): + metadata = get_metadata_object() + metadata.clients_xml.write = Mock() + metadata.clients_xml.data = lxml.etree.XML('<Clients/>').getroottree() + metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data) + + metadata.add_client("test1", dict()) + metadata.clients_xml.write.assert_any_call() + grp = metadata.search_client("test1", metadata.clients_xml.base_xdata) + self.assertIsNotNone(grp) + self.assertEqual(grp.attrib, dict(name='test1')) + + # have to call this explicitly -- usually load_xml does this + # on FAM events + metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data) + + metadata.add_client("test2", dict(foo='bar')) + metadata.clients_xml.write.assert_any_call() + grp = metadata.search_client("test2", metadata.clients_xml.base_xdata) + self.assertIsNotNone(grp) + self.assertEqual(grp.attrib, dict(name='test2', foo='bar')) + + # have to call this explicitly -- usually load_xml does this + # on FAM events + metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data) + + metadata.clients_xml.write.reset_mock() + self.assertRaises(MetadataConsistencyError, + metadata.add_client, + "test1", dict()) + self.assertFalse(metadata.clients_xml.write.called) + + def test_update_client(self): + metadata = get_metadata_object() + metadata.clients_xml.write_xml = Mock() + metadata.clients_xml.data = copy.deepcopy(clients_test_tree) + metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data) + + metadata.update_client("client1", dict(foo="bar")) + grp = metadata.search_client("client1", metadata.clients_xml.base_xdata) + self.assertIsNotNone(grp) + self.assertIn("foo", grp.attrib) + self.assertEqual(grp.get("foo"), "bar") + self.assertTrue(metadata.clients_xml.write_xml.called) + + self.assertRaises(MetadataConsistencyError, + metadata.update_client, + "bogus_client", dict()) + + def load_clients_data(self, metadata=None, xdata=None): + if metadata is None: + metadata = get_metadata_object() + metadata.clients_xml.data = xdata or copy.deepcopy(clients_test_tree) + metadata.clients_xml.basedata = copy.copy(metadata.clients_xml.data) + evt = Mock() + evt.filename = os.path.join(datastore, "Metadata", "clients.xml") + evt.code2str = Mock(return_value="changed") + metadata.HandleEvent(evt) + return metadata + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml") + def test_clients_xml_event(self, mock_load_xml): + metadata = get_metadata_object() + metadata.profiles = ["group1", "group2"] + self.load_clients_data(metadata=metadata) + mock_load_xml.assert_any_call() + self.assertItemsEqual(metadata.clients, + dict([(c.get("name"), c.get("profile")) + for c in clients_test_tree.findall("//Client")])) + aliases = dict([(a.get("name"), a.getparent().get("name")) + for a in clients_test_tree.findall("//Alias")]) + self.assertItemsEqual(metadata.aliases, aliases) + + raliases = dict([(c.get("name"), set()) + for c in clients_test_tree.findall("//Client")]) + for alias in clients_test_tree.findall("//Alias"): + raliases[alias.getparent().get("name")].add(alias.get("name")) + self.assertItemsEqual(metadata.raliases, raliases) + + self.assertEqual(metadata.bad_clients, dict()) + self.assertEqual(metadata.secure, + [c.get("name") + for c in clients_test_tree.findall("//Client[@secure='true']")]) + self.assertEqual(metadata.floating, ["client1"]) + + addresses = dict([(c.get("address"), []) + for c in clients_test_tree.findall("//*[@address]")]) + raddresses = dict() + for client in clients_test_tree.findall("//Client[@address]"): + addresses[client.get("address")].append(client.get("name")) + try: + raddresses[client.get("name")].append(client.get("address")) + except KeyError: + raddresses[client.get("name")] = [client.get("address")] + for alias in clients_test_tree.findall("//Alias[@address]"): + addresses[alias.get("address")].append(alias.getparent().get("name")) + try: + raddresses[alias.getparent().get("name")].append(alias.get("address")) + except KeyError: + raddresses[alias.getparent().get("name")] = alias.get("address") + + self.assertItemsEqual(metadata.addresses, addresses) + self.assertItemsEqual(metadata.raddresses, raddresses) + self.assertTrue(metadata.states['clients.xml']) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + def test_clients_xml_event_bad_clients(self): + metadata = get_metadata_object() + metadata.profiles = ["group2"] + self.load_clients_data(metadata=metadata) + clients = dict() + badclients = dict() + for client in clients_test_tree.findall("//Client"): + if client.get("profile") in metadata.profiles: + clients[client.get("name")] = client.get("profile") + else: + badclients[client.get("name")] = client.get("profile") + self.assertItemsEqual(metadata.clients, clients) + self.assertItemsEqual(metadata.bad_clients, badclients) + + def load_groups_data(self, metadata=None, xdata=None): + if metadata is None: + metadata = get_metadata_object() + metadata.groups_xml.data = xdata or copy.deepcopy(groups_test_tree) + metadata.groups_xml.basedata = copy.copy(metadata.groups_xml.data) + evt = Mock() + evt.filename = os.path.join(datastore, "Metadata", "groups.xml") + evt.code2str = Mock(return_value="changed") + metadata.HandleEvent(evt) + return metadata + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml") + def test_groups_xml_event(self, mock_load_xml): + dup_data = copy.deepcopy(groups_test_tree) + lxml.etree.SubElement(dup_data.getroot(), + "Group", name="group1") + metadata = self.load_groups_data(xdata=dup_data) + mock_load_xml.assert_any_call() + self.assertEqual(metadata.public, ["group1", "group2"]) + self.assertEqual(metadata.private, ["group3"]) + self.assertEqual(metadata.profiles, ["group1", "group2"]) + self.assertItemsEqual(metadata.groups.keys(), + [g.get("name") + for g in groups_test_tree.findall("/Group")]) + self.assertEqual(metadata.categories, + dict(group1="category1", + group2="category1", + group3="category2", + group4="category1")) + self.assertEqual(metadata.default, "group1") + self.assertTrue(metadata.states['groups.xml']) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + @patch("Bcfg2.Server.Plugins.Metadata.Metadata.add_client") + @patch("Bcfg2.Server.Plugins.Metadata.Metadata.update_client") + def test_set_profile(self, mock_update_client, mock_add_client): + metadata = get_metadata_object() + metadata.states['clients.xml'] = False + self.assertRaises(MetadataRuntimeError, + metadata.set_profile, + None, None, None) + + self.load_groups_data(metadata=metadata) + self.load_clients_data(metadata=metadata) + + self.assertRaises(MetadataConsistencyError, + metadata.set_profile, + "client1", "group5", None) + + metadata.clients_xml.write = Mock() + metadata.set_profile("client1", "group2", None) + mock_update_client.assert_called_with("client1", dict(profile="group2")) + metadata.clients_xml.write.assert_any_call() + self.assertEqual(metadata.clients["client1"], "group2") + + metadata.clients_xml.write.reset_mock() + metadata.set_profile("client_new", "group1", None) + mock_add_client.assert_called_with("client_new", dict(profile="group1")) + metadata.clients_xml.write.assert_any_call() + self.assertEqual(metadata.clients["client_new"], "group1") + + metadata.session_cache[('1.2.3.6', None)] = (None, 'client_new2') + metadata.clients_xml.write.reset_mock() + metadata.set_profile("uuid_new", "group1", ('1.2.3.6', None)) + mock_add_client.assert_called_with("client_new2", + dict(uuid='uuid_new', + profile="group1", + address='1.2.3.6')) + metadata.clients_xml.write.assert_any_call() + self.assertEqual(metadata.clients["uuid_new"], "group1") + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + @patch("socket.gethostbyaddr") + def test_resolve_client(self, mock_gethostbyaddr): + metadata = self.load_clients_data(metadata=self.load_groups_data()) + metadata.session_cache[('1.2.3.3', None)] = (time.time(), 'client3') + self.assertEqual(metadata.resolve_client(('1.2.3.3', None)), 'client3') + + self.assertRaises(MetadataConsistencyError, + metadata.resolve_client, + ('1.2.3.2', None)) + self.assertEqual(metadata.resolve_client(('1.2.3.1', None)), 'client1') + + metadata.session_cache[('1.2.3.3', None)] = (time.time() - 100, + 'client3') + self.assertEqual(metadata.resolve_client(('1.2.3.3', None)), 'client3') + self.assertEqual(metadata.resolve_client(('1.2.3.3', None), + cleanup_cache=True), 'client3') + self.assertEqual(metadata.session_cache, dict()) + + mock_gethostbyaddr.return_value = ('client6', [], ['1.2.3.6']) + self.assertEqual(metadata.resolve_client(('1.2.3.6', None)), 'client6') + mock_gethostbyaddr.assert_called_with('1.2.3.6') + + mock_gethostbyaddr.reset_mock() + mock_gethostbyaddr.return_value = ('alias3', [], ['1.2.3.7']) + self.assertEqual(metadata.resolve_client(('1.2.3.7', None)), 'client4') + mock_gethostbyaddr.assert_called_with('1.2.3.7') + + mock_gethostbyaddr.reset_mock() + mock_gethostbyaddr.return_value = None + mock_gethostbyaddr.side_effect = socket.herror + self.assertRaises(MetadataConsistencyError, + metadata.resolve_client, + ('1.2.3.8', None)) + mock_gethostbyaddr.assert_called_with('1.2.3.8') + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.write_xml", Mock()) + @patch("Bcfg2.Server.Plugins.Metadata.ClientMetadata") + def test_get_initial_metadata(self, mock_clientmetadata): + metadata = get_metadata_object() + metadata.states['clients.xml'] = False + self.assertRaises(MetadataRuntimeError, + metadata.get_initial_metadata, None) + + self.load_groups_data(metadata=metadata) + self.load_clients_data(metadata=metadata) + + metadata.get_initial_metadata("client1") + self.assertEqual(mock_clientmetadata.call_args[0][:9], + ("client1", "group1", set(["group1"]), set(), set(), + set(["1.2.3.1"]), dict(), None, 'password2')) + + metadata.get_initial_metadata("client2") + self.assertEqual(mock_clientmetadata.call_args[0][:9], + ("client2", "group2", set(["group1", "group2"]), + set(["bundle1", "bundle2"]), set(), + set(["1.2.3.2"]), dict(category1="group1"), + None, None)) + + imd = metadata.get_initial_metadata("alias1") + self.assertEqual(mock_clientmetadata.call_args[0][:9], + ("client3", "group1", set(["group1"]), set(), + set(['alias1']), set(["1.2.3.3"]), dict(), 'uuid1', + 'password2')) + + imd = metadata.get_initial_metadata("client_new") + self.assertEqual(mock_clientmetadata.call_args[0][:9], + ("client_new", "group1", set(["group1"]), set(), + set(), set(), dict(), None, None)) + + metadata.default = None + self.assertRaises(MetadataConsistencyError, + metadata.get_initial_metadata, + "client_new2") + + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + def test_get_all_group_names(self): + metadata = self.load_groups_data() + self.assertItemsEqual(metadata.get_all_group_names(), + set([g.get("name") + for g in groups_test_tree.findall("//Group")])) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + def test_get_all_groups_in_category(self): + metadata = self.load_groups_data() + self.assertItemsEqual(metadata.get_all_groups_in_category("category1"), + set([g.get("name") + for g in groups_test_tree.findall("//Group[@category='category1']")])) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + def test_get_client_names_by_profiles(self): + metadata = self.load_clients_data(metadata=self.load_groups_data()) + self.assertItemsEqual(metadata.get_client_names_by_profiles("group2"), + [c.get("name") + for c in clients_test_tree.findall("//Client[@profile='group2']")]) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + def test_get_client_names_by_groups(self): + metadata = self.load_clients_data(metadata=self.load_groups_data()) + # this is not the best test in the world, since we mock + # core.build_metadata to just build _initial_ metadata, which + # is not at all the same thing. it turns out that mocking + # this out without starting a Bcfg2 server is pretty + # non-trivial, so this works-ish + metadata.core.build_metadata = Mock() + metadata.core.build_metadata.side_effect = \ + lambda c: metadata.get_initial_metadata(c) + self.assertItemsEqual(metadata.get_client_names_by_groups(["group2"]), + [c.get("name") + for c in clients_test_tree.findall("//Client[@profile='group2']")]) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + def test_merge_additional_groups(self): + metadata = self.load_clients_data(metadata=self.load_groups_data()) + imd = metadata.get_initial_metadata("client2") + + # test adding a group excluded by categories + oldgroups = imd.groups + metadata.merge_additional_groups(imd, ["group4"]) + self.assertEqual(imd.groups, oldgroups) + + # test adding a private group + oldgroups = imd.groups + metadata.merge_additional_groups(imd, ["group3"]) + self.assertEqual(imd.groups, oldgroups) + + # test adding groups with bundles + oldgroups = imd.groups + oldbundles = imd.bundles + metadata.merge_additional_groups(imd, ["group7"]) + self.assertEqual(imd.groups, oldgroups.union(["group7"])) + self.assertEqual(imd.bundles, oldbundles.union(["bundle3"])) + + # test adding multiple groups + imd = metadata.get_initial_metadata("client2") + oldgroups = imd.groups + metadata.merge_additional_groups(imd, ["group6", "group8"]) + self.assertItemsEqual(imd.groups, + oldgroups.union(["group6", "group8", "group9"])) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + def test_merge_additional_data(self): + metadata = self.load_clients_data(metadata=self.load_groups_data()) + imd = metadata.get_initial_metadata("client1") + + # we need to use a unique attribute name for this test. this + # is probably overkill, but it works + pattern = "connector%d" + for i in range(0, 100): + connector = pattern % i + if not hasattr(imd, connector): + break + self.assertFalse(hasattr(imd, connector), + "Could not find unique connector name to test " + "merge_additional_data()") + + metadata.merge_additional_data(imd, connector, "test data") + self.assertEqual(getattr(imd, connector), "test data") + self.assertIn(connector, imd.connectors) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + @patch("Bcfg2.Server.Plugins.Metadata.Metadata.resolve_client") + def test_validate_client_address(self, mock_resolve_client): + metadata = self.load_clients_data(metadata=self.load_groups_data()) + self.assertTrue(metadata.validate_client_address("client1", + (None, None))) + self.assertTrue(metadata.validate_client_address("client2", + ("1.2.3.2", None))) + self.assertFalse(metadata.validate_client_address("client2", + ("1.2.3.8", None))) + self.assertTrue(metadata.validate_client_address("client4", + ("1.2.3.2", None))) + # this is upper case to ensure that case is folded properly in + # validate_client_address() + mock_resolve_client.return_value = "CLIENT4" + self.assertTrue(metadata.validate_client_address("client4", + ("1.2.3.7", None))) + mock_resolve_client.assert_called_with(("1.2.3.7", None)) + + mock_resolve_client.reset_mock() + self.assertFalse(metadata.validate_client_address("client5", + ("1.2.3.5", None))) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + @patch("Bcfg2.Server.Plugins.Metadata.Metadata.validate_client_address") + @patch("Bcfg2.Server.Plugins.Metadata.Metadata.resolve_client") + def test_AuthenticateConnection(self, mock_resolve_client, + mock_validate_client_address): + metadata = self.load_clients_data(metadata=self.load_groups_data()) + metadata.password = "password1" + + cert = dict(subject=[[("commonName", "client1")]]) + mock_validate_client_address.return_value = False + self.assertFalse(metadata.AuthenticateConnection(cert, "root", None, + "1.2.3.1")) + mock_validate_client_address.return_value = True + self.assertTrue(metadata.AuthenticateConnection(cert, "root", None, + "1.2.3.1")) + # floating cert-auth clients add themselves to the cache + self.assertIn("1.2.3.1", metadata.session_cache) + self.assertEqual(metadata.session_cache["1.2.3.1"][1], "client1") + + cert = dict(subject=[[("commonName", "client7")]]) + self.assertTrue(metadata.AuthenticateConnection(cert, "root", None, + "1.2.3.4")) + # non-floating cert-auth clients do not add themselves to the cache + self.assertNotIn("1.2.3.4", metadata.session_cache) + + cert = dict(subject=[[("commonName", "client8")]]) + + mock_resolve_client.return_value = "client5" + self.assertTrue(metadata.AuthenticateConnection(None, "root", + "password1", "1.2.3.8")) + + mock_resolve_client.side_effect = MetadataConsistencyError + self.assertFalse(metadata.AuthenticateConnection(None, "root", + "password1", + "1.2.3.8")) + + # secure mode, no password + self.assertFalse(metadata.AuthenticateConnection(None, 'client2', None, + "1.2.3.2")) + + self.assertTrue(metadata.AuthenticateConnection(None, 'uuid1', + "password1", "1.2.3.3")) + # non-root, non-cert clients populate session cache + self.assertIn("1.2.3.3", metadata.session_cache) + self.assertEqual(metadata.session_cache["1.2.3.3"][1], "client3") + + # use alternate password + self.assertTrue(metadata.AuthenticateConnection(None, 'client3', + "password2", "1.2.3.3")) + + # test secure mode + self.assertFalse(metadata.AuthenticateConnection(None, 'client9', + "password1", + "1.2.3.9")) + self.assertTrue(metadata.AuthenticateConnection(None, 'client9', + "password3", "1.2.3.9")) + + self.assertFalse(metadata.AuthenticateConnection(None, "client5", + "password2", + "1.2.3.7")) + + @patch("Bcfg2.Server.Plugins.Metadata.XMLMetadataConfig.load_xml", Mock()) + @patch("Bcfg2.Server.Plugins.Metadata.Metadata.update_client") + def test_process_statistics(self, mock_update_client): + metadata = self.load_clients_data(metadata=self.load_groups_data()) + md = Mock() + md.hostname = "client6" + metadata.process_statistics(md, None) + mock_update_client.assert_called_with(md.hostname, + dict(auth='cert')) + + mock_update_client.reset_mock() + md.hostname = "client5" + metadata.process_statistics(md, None) + self.assertFalse(mock_update_client.called) + + def test_viz(self): + pass diff --git a/tools/bcfg2-export-config b/tools/bcfg2-export-config index 4e7790a22..f7e939a3e 100755 --- a/tools/bcfg2-export-config +++ b/tools/bcfg2-export-config @@ -16,7 +16,6 @@ The metadata information get stored in an index file in the output directory. The format of the index file is: user,group,permission,/path/to/file ''' -__revision__ = '$Revision: 221 $' import logging, lxml.etree, Bcfg2.Logging, Bcfg2.Server.Core import Bcfg2.Server.Plugins.Metadata, Bcfg2.Server.Plugin diff --git a/tools/create-debian-pkglist.py b/tools/create-debian-pkglist.py index 8e1210582..4b268c6ee 100755 --- a/tools/create-debian-pkglist.py +++ b/tools/create-debian-pkglist.py @@ -1,7 +1,6 @@ #!/usr/bin/env python '''Build debian/ubuntu package indexes''' -__revision__ = '$Id$' # Original code from Bcfg2 sources diff --git a/tools/encap-util-count.sh b/tools/encap-util-count.sh index 848a8d648..460bb689b 100755 --- a/tools/encap-util-count.sh +++ b/tools/encap-util-count.sh @@ -1,5 +1,4 @@ #!/bin/sh -# $Id$ # This shows a count of encap packages per directory # Can be useful to make sure you have everything diff --git a/tools/encap-util-expand.sh b/tools/encap-util-expand.sh index 2bb3f7f62..5764bdc79 100755 --- a/tools/encap-util-expand.sh +++ b/tools/encap-util-expand.sh @@ -1,5 +1,4 @@ #!/bin/sh -# $Id$ # This gets the encaps out of a makeself .run file diff --git a/tools/encap-util-place.sh b/tools/encap-util-place.sh index c3e96ebd8..dedf14448 100755 --- a/tools/encap-util-place.sh +++ b/tools/encap-util-place.sh @@ -1,5 +1,4 @@ #!/bin/bash -# $Id$ # This puts encaps in the right directories, creating the # directories if needed. diff --git a/tools/encap-util-xml.sh b/tools/encap-util-xml.sh index 0fdd75fe4..ae762637f 100755 --- a/tools/encap-util-xml.sh +++ b/tools/encap-util-xml.sh @@ -1,5 +1,4 @@ #!/bin/sh -# $Id$ # This builds the XML Pkgmgr files for the encap directory # structure created by the place script. It assumes the diff --git a/tools/hostbasepush.py b/tools/hostbasepush.py index 070711c82..02b7a582f 100755 --- a/tools/hostbasepush.py +++ b/tools/hostbasepush.py @@ -1,7 +1,5 @@ #!/usr/bin/python -__revision__ = "$Revision: $" - import os import Bcfg2.Client.Proxy diff --git a/tools/pkgmgr_gen.py b/tools/pkgmgr_gen.py index 03d36dfc0..7101ba17d 100755 --- a/tools/pkgmgr_gen.py +++ b/tools/pkgmgr_gen.py @@ -10,7 +10,6 @@ bcfg2 client drivers. The output can also contain the PackageList and nested group headers. """ -__revision__ = '$Revision: $' import collections import datetime import glob |