summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2013-02-06 15:45:20 -0500
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2013-02-06 15:45:20 -0500
commit2169edc1bba82076db776b75db89b79d6f2f4786 (patch)
tree25b7f500015bc61bf30927239297381ef8c82abd
parentc7aae4303ff622474af6ddd3014ad39b8ef0a92c (diff)
downloadbcfg2-2169edc1bba82076db776b75db89b79d6f2f4786.tar.gz
bcfg2-2169edc1bba82076db776b75db89b79d6f2f4786.tar.bz2
bcfg2-2169edc1bba82076db776b75db89b79d6f2f4786.zip
converted InfoXML objects from XMLSrc to StructFile
-rw-r--r--src/lib/Bcfg2/Server/Plugin/helpers.py359
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgInfoXML.py16
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py36
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/__init__.py26
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Rules.py29
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py312
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py37
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py58
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py82
9 files changed, 403 insertions, 552 deletions
diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py
index bc82ee6c2..6b73ea66a 100644
--- a/src/lib/Bcfg2/Server/Plugin/helpers.py
+++ b/src/lib/Bcfg2/Server/Plugin/helpers.py
@@ -60,38 +60,6 @@ def default_path_metadata():
return dict([(k, setup[k]) for k in attrs])
-def bind_info(entry, metadata, infoxml=None, default=None):
- """ Bind the file metadata in the given
- :class:`Bcfg2.Server.Plugin.helpers.InfoXML` object to the given
- entry.
-
- :param entry: The abstract entry to bind the info to
- :type entry: lxml.etree._Element
- :param metadata: The client metadata to get info for
- :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
- :param infoxml: The info.xml file to pull file metadata from
- :type infoxml: Bcfg2.Server.Plugin.helpers.InfoXML
- :param default: Default metadata to supply when the info.xml file
- does not include a particular attribute
- :type default: dict
- :returns: None
- :raises: :class:`Bcfg2.Server.Plugin.exceptions.PluginExecutionError`
- """
- if default is None:
- default = default_path_metadata()
- for attr, val in list(default.items()):
- entry.set(attr, val)
- if infoxml:
- mdata = dict()
- infoxml.pnode.Match(metadata, mdata, entry=entry)
- if 'Info' not in mdata:
- msg = "Failed to set metadata for file %s" % entry.get('name')
- LOGGER.error(msg)
- raise PluginExecutionError(msg)
- for attr, val in list(mdata['Info'][None].items()):
- entry.set(attr, val)
-
-
class DatabaseBacked(Plugin):
""" Provides capabilities for a plugin to read and write to a
database.
@@ -541,6 +509,7 @@ class StructFile(XMLFileBacked, Debuggable):
.. -----
.. autoattribute:: __identifier__
+ .. autofunction:: _include_element
"""
#: If ``__identifier__`` is not None, then it must be the name of
@@ -551,6 +520,20 @@ class StructFile(XMLFileBacked, Debuggable):
#: Whether or not encryption support is enabled in this file
encryption = True
+ #: Callbacks used to determine if children of items with the given
+ #: tags should be included in the return value of
+ #: :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` and
+ #: :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch`. Each
+ #: callback is passed the same arguments as
+ #: :func:`Bcfg2.Server.Plugin.helpers.StructFile._include_element`.
+ #: It should return True if children of the element should be
+ #: included in the match, False otherwise. The callback does
+ #: *not* need to consider negation; that will be handled in
+ #: :func:`Bcfg2.Server.Plugin.helpers.StructFile._include_element`
+ _include_tests = \
+ dict(Group=lambda el, md, *args: el.get('name') in md.groups,
+ Client=lambda el, md, *args: el.get('name') == md.hostname)
+
def __init__(self, filename, should_monitor=False):
XMLFileBacked.__init__(self, filename, should_monitor=should_monitor)
Debuggable.__init__(self)
@@ -621,15 +604,25 @@ class StructFile(XMLFileBacked, Debuggable):
return Bcfg2.Server.Encryption.bruteforce_decrypt(element.text)
raise Bcfg2.Server.Encryption.EVPError("Failed to decrypt")
- def _include_element(self, item, metadata):
- """ determine if an XML element matches the metadata """
+ def _include_element(self, item, metadata, *args):
+ """ Determine if an XML element matches the other arguments.
+
+ The first argument is always the XML element to match, and the
+ second will always be a single
+ :class:`Bcfg2.Server.Plugins.Metadata.ClientMetadata` object
+ representing the metadata to match against. Subsequent
+ arguments are as given to
+ :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` or
+ :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch`. In
+ the base StructFile implementation, there are no additional
+ arguments; in classes that inherit from StructFile, see the
+ :func:`Match` and :func:`XMLMatch` method signatures."""
if isinstance(item, lxml.etree._Comment): # pylint: disable=W0212
return False
- negate = item.get('negate', 'false').lower() == 'true'
- if item.tag == 'Group':
- return negate == (item.get('name') not in metadata.groups)
- elif item.tag == 'Client':
- return negate == (item.get('name') != metadata.hostname)
+ if item.tag in self._include_tests:
+ negate = item.get('negate', 'false').lower() == 'true'
+ return negate != self._include_tests[item.tag](item, metadata,
+ *args)
else:
return True
@@ -648,25 +641,42 @@ class StructFile(XMLFileBacked, Debuggable):
strip_whitespace=False),
parser=Bcfg2.Server.XMLParser)
- def _match(self, item, metadata):
- """ recursive helper for Match() """
- if self._include_element(item, metadata):
- if item.tag == 'Group' or item.tag == 'Client':
+ def _match(self, item, metadata, *args):
+ """ recursive helper for
+ :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` """
+ if self._include_element(item, metadata, *args):
+ if item.tag in self._include_tests.keys():
rv = []
- if self._include_element(item, metadata):
+ if self._include_element(item, metadata, *args):
for child in item.iterchildren():
- rv.extend(self._match(child, metadata))
+ rv.extend(self._match(child, metadata, *args))
return rv
else:
rv = copy.deepcopy(item)
for child in rv.iterchildren():
rv.remove(child)
for child in item.iterchildren():
- rv.extend(self._match(child, metadata))
+ rv.extend(self._match(child, metadata, *args))
return [rv]
else:
return []
+ def _do_match(self, metadata, *args):
+ """ Helper for
+ :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` that lets
+ a subclass of StructFile easily redefine the public Match()
+ interface to accept a different number of arguments. This
+ provides a sane prototype for the Match() function while
+ keeping the internals consistent. """
+ rv = []
+ if self.template is None:
+ entries = self.entries
+ else:
+ entries = self._render(metadata).getchildren()
+ for child in entries:
+ rv.extend(self._match(child, metadata, *args))
+ return rv
+
def Match(self, metadata):
""" Return matching fragments of the data in this file. A tag
is considered to match if all ``<Group>`` and ``<Client>``
@@ -680,25 +690,17 @@ class StructFile(XMLFileBacked, Debuggable):
:param metadata: Client metadata to match against.
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
:returns: list of lxml.etree._Element objects """
- rv = []
- if self.template is None:
- entries = self.entries
- else:
- entries = self._render(metadata).getchildren()
- print "rendered: %s" % lxml.etree.tostring(self._render(metadata),
- pretty_print=True)
- for child in entries:
- rv.extend(self._match(child, metadata))
- return rv
+ return self._do_match(metadata)
- def _xml_match(self, item, metadata):
- """ recursive helper for XMLMatch """
- if self._include_element(item, metadata):
- if item.tag == 'Group' or item.tag == 'Client':
+ def _xml_match(self, item, metadata, *args):
+ """ recursive helper for
+ :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch` """
+ if self._include_element(item, metadata, *args):
+ if item.tag in self._include_tests.keys():
for child in item.iterchildren():
item.remove(child)
item.getparent().append(child)
- self._xml_match(child, metadata)
+ self._xml_match(child, metadata, *args)
if item.text:
if item.getparent().text is None:
item.getparent().text = item.text
@@ -707,10 +709,25 @@ class StructFile(XMLFileBacked, Debuggable):
item.getparent().remove(item)
else:
for child in item.iterchildren():
- self._xml_match(child, metadata)
+ self._xml_match(child, metadata, *args)
else:
item.getparent().remove(item)
+ def _do_xmlmatch(self, metadata, *args):
+ """ Helper for
+ :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch` that lets
+ a subclass of StructFile easily redefine the public Match()
+ interface to accept a different number of arguments. This
+ provides a sane prototype for the Match() function while
+ keeping the internals consistent. """
+ if self.template is None:
+ rv = copy.deepcopy(self.xdata)
+ else:
+ rv = self._render(metadata)
+ for child in rv.iterchildren():
+ self._xml_match(child, metadata, *args)
+ return rv
+
def XMLMatch(self, metadata):
""" Return a rebuilt XML document that only contains the
matching portions of the original file. A tag is considered
@@ -723,14 +740,7 @@ class StructFile(XMLFileBacked, Debuggable):
:param metadata: Client metadata to match against.
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
:returns: lxml.etree._Element """
- if self.template is None:
- rv = copy.deepcopy(self.xdata)
- else:
- rv = self._render(metadata)
- for child in rv.iterchildren():
- self._xml_match(child, metadata)
- return rv
-
+ return self._do_xmlmatch(metadata)
class INode(object):
""" INodes provide lists of things available at a particular group
@@ -804,26 +814,6 @@ class INode(object):
child.Match(metadata, data, entry=entry)
-class InfoNode (INode):
- """ :class:`Bcfg2.Server.Plugin.helpers.INode` implementation that
- includes ``<Path>`` tags, suitable for use with :file:`info.xml`
- files."""
-
- raw = dict(
- Client="lambda m, e: '%(name)s' == m.hostname and predicate(m, e)",
- Group="lambda m, e: '%(name)s' in m.groups and predicate(m, e)",
- Path="lambda m, e: ('%(name)s' == e.get('name') or " +
- "'%(name)s' == e.get('realname')) and " +
- "predicate(m, e)")
- nraw = dict(
- Client="lambda m, e: '%(name)s' != m.hostname and predicate(m, e)",
- Group="lambda m, e: '%(name)s' not in m.groups and predicate(m, e)",
- Path="lambda m, e: '%(name)s' != e.get('name') and " +
- "'%(name)s' != e.get('realname') and " +
- "predicate(m, e)")
- containers = ['Group', 'Client', 'Path']
-
-
class XMLSrc(XMLFileBacked):
""" XMLSrc files contain a
:class:`Bcfg2.Server.Plugin.helpers.INode` hierarchy that returns
@@ -886,13 +876,49 @@ class XMLSrc(XMLFileBacked):
return str(self.items)
-class InfoXML(XMLSrc):
- """ InfoXML files contain a
- :class:`Bcfg2.Server.Plugin.helpers.InfoNode` hierarchy that
- returns matching entries, suitable for use with :file:`info.xml`
- files."""
- __node__ = InfoNode
- __priority_required__ = False
+class InfoXML(StructFile):
+ """ InfoXML files contain Group, Client, and Path tags to set the
+ metadata (permissions, owner, etc.) of files. """
+ encryption = False
+
+ _include_tests = StructFile._include_tests
+ _include_tests['Path'] = lambda el, md, entry, *args: \
+ entry.get("name") == el.get("name")
+
+ def Match(self, metadata, entry): # pylint: disable=W0221
+ """ Implementation of
+ :func:`Bcfg2.Server.Plugin.helpers.StructFile.Match` that
+ considers Path tags to allow ``info.xml`` files to set
+ different file metadata for different file paths. """
+ return self._do_match(metadata, entry)
+
+ def XMLMatch(self, metadata, entry): # pylint: disable=W0221
+ """ Implementation of
+ :func:`Bcfg2.Server.Plugin.helpers.StructFile.XMLMatch` that
+ considers Path tags to allow ``info.xml`` files to set
+ different file metadata for different file paths. """
+ return self._do_xmlmatch(metadata, entry)
+
+ def BindEntry(self, entry, metadata):
+ """ Bind the matching file metadata for this client and entry
+ to the entry.
+
+ :param entry: The abstract entry to bind the info to. This
+ will be modified in place
+ :type entry: lxml.etree._Element
+ :param metadata: The client metadata to get info for
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ :returns: None
+ """
+ fileinfo = self.Match(metadata, entry)
+ if len(fileinfo) == 0:
+ raise PluginExecutionError("No metadata found in %s for %s" %
+ (self.name, entry.get('name')))
+ elif len(fileinfo) > 1:
+ self.logger.warning("Multiple file metadata found in %s for %s" %
+ (self.name, entry.get('name')))
+ for attr, val in fileinfo[0].attrib.items():
+ entry.set(attr, val)
class XMLDirectoryBacked(DirectoryBacked):
@@ -908,6 +934,24 @@ class XMLDirectoryBacked(DirectoryBacked):
__child__ = XMLFileBacked
+class PriorityStructFile(StructFile):
+ """ A StructFile where each file has a priority, given as a
+ top-level XML attribute. """
+
+ def __init__(self, filename, should_monitor=False):
+ StructFile.__init__(self, filename, should_monitor=should_monitor)
+ self.priority = -1
+ __init__.__doc__ = StructFile.__init__.__doc__
+
+ def Index(self):
+ try:
+ self.priority = int(self.xdata.get('priority'))
+ except (ValueError, TypeError):
+ raise PluginExecutionError("Got bogus priority %s for file %s" %
+ (self.xdata.get('priority'), self.name))
+ Index.__doc__ = StructFile.Index.__doc__
+
+
class PrioDir(Plugin, Generator, XMLDirectoryBacked):
""" PrioDir handles a directory of XML files where each file has a
set priority.
@@ -918,8 +962,8 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked):
#: The type of child objects to create for files contained within
#: the directory that is tracked. Default is
- #: :class:`Bcfg2.Server.Plugin.helpers.XMLSrc`
- __child__ = XMLSrc
+ #: :class:`Bcfg2.Server.Plugin.helpers.PriorityStructFile`
+ __child__ = PriorityStructFile
def __init__(self, core, datastore):
Plugin.__init__(self, core, datastore)
@@ -939,21 +983,22 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked):
self.Entries[itype] = {child: self.BindEntry}
HandleEvent.__doc__ = XMLDirectoryBacked.HandleEvent.__doc__
- def _matches(self, entry, metadata, rules): # pylint: disable=W0613
- """ Whether or not a given entry has a matching entry in this
- PrioDir. By default this does strict matching (i.e., the
- entry name is in ``rules.keys()``), but this can be overridden
- to provide regex matching, etc.
+ def _matches(self, entry, metadata, candidate): # pylint: disable=W0613
+ """ Whether or not a given candidate matches the abstract
+ entry given. By default this does strict matching (i.e., the
+ entry name matches the candidate name), but this can be
+ overridden to provide regex matching, etc.
:param entry: The entry to find a match for
:type entry: lxml.etree._Element
:param metadata: The metadata to get attributes for
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
- :rules: A dict of rules to look in for a matching rule
- :type rules: dict
+ :candidate: A candidate concrete entry to match with
+ :type candidate: lxml.etree._Element
:returns: bool
"""
- return entry.get('name') in rules
+ return (entry.tag == candidate.tag and
+ entry.get('name') == candidate.get('name'))
def BindEntry(self, entry, metadata):
""" Bind the attributes that apply to an entry to it. The
@@ -965,71 +1010,40 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked):
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
:returns: None
"""
- attrs = self.get_attrs(entry, metadata)
- for key, val in list(attrs.items()):
- entry.attrib[key] = val
-
- def get_attrs(self, entry, metadata):
- """ Get a list of attributes to add to the entry during the
- bind. This is a complex method, in that it both modifies the
- entry, and returns attributes that need to be added to the
- entry. That seems sub-optimal, and should probably be changed
- at some point. Namely:
-
- * The return value includes all XML attributes that need to be
- added to the entry, but it does not add them.
- * If text contents or child tags need to be added to the
- entry, they are added to the entry in place.
-
- :param entry: The entry to add attributes to.
- :type entry: lxml.etree._Element
- :param metadata: The metadata to get attributes for
- :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
- :returns: dict of <attr name>:<attr value>
- :raises: :class:`Bcfg2.Server.Plugin.exceptions.PluginExecutionError`
- """
+ matching = []
for src in self.entries.values():
- src.Cache(metadata)
-
- matching = [src for src in list(self.entries.values())
- if (src.cache and
- entry.tag in src.cache[1] and
- self._matches(entry, metadata,
- src.cache[1][entry.tag]))]
+ for candidate in src.XMLMatch(metadata).xpath("//%s" % entry.tag):
+ if self._matches(entry, metadata, candidate):
+ matching.append((src, candidate))
if len(matching) == 0:
raise PluginExecutionError("No matching source for entry when "
- "retrieving attributes for %s(%s)" %
- (entry.tag, entry.attrib.get('name')))
+ "retrieving attributes for %s:%s" %
+ (entry.tag, entry.get('name')))
elif len(matching) == 1:
- index = 0
+ data = matching[0][1]
else:
- prio = [int(src.priority) for src in matching]
- if prio.count(max(prio)) > 1:
- msg = "Found conflicting sources with same priority for " + \
- "%s:%s for %s" % (entry.tag, entry.get("name"),
- metadata.hostname)
+ prio = [int(m[0].priority) for m in matching]
+ priority = max(prio)
+ if prio.count(priority) > 1:
+ msg = "Found conflicting sources with same priority (%s) " \
+ "for %s:%s for %s" % (priority, entry.tag,
+ entry.get("name"), metadata.hostname)
self.logger.error(msg)
- self.logger.error([item.name for item in matching])
- self.logger.error("Priority was %s" % max(prio))
+ self.logger.error([m[0].name for m in matching])
raise PluginExecutionError(msg)
- index = prio.index(max(prio))
- for rname in list(matching[index].cache[1][entry.tag].keys()):
- if self._matches(entry, metadata, [rname]):
- data = matching[index].cache[1][entry.tag][rname]
- break
- else:
- # Fall back on __getitem__. Required if override used
- data = matching[index].cache[1][entry.tag][entry.get('name')]
- if '__text__' in data:
- entry.text = data['__text__']
- if '__children__' in data:
- for item in data['__children__']:
- entry.append(copy.copy(item))
+ for src, candidate in matching:
+ if int(src.priority) == priority:
+ data = candidate
+ break
+
+ entry.text = data.text
+ for item in data.getchildren():
+ entry.append(copy.copy(item))
- return dict([(key, data[key])
- for key in list(data.keys())
- if not key.startswith('__')])
+ for key, val in list(data.attrib.items()):
+ if key not in entry.attrib:
+ entry.attrib[key] = val
class Specificity(CmpMixin):
@@ -1312,8 +1326,8 @@ class EntrySet(Debuggable):
self.entry_init(event)
else:
if event.filename not in self.entries:
- LOGGER.warning("Got %s event for unknown file %s" %
- (action, event.filename))
+ self.logger.warning("Got %s event for unknown file %s" %
+ (action, event.filename))
if action == 'changed':
# received a bogus changed event; warn, but treat
# it like a created event
@@ -1349,7 +1363,7 @@ class EntrySet(Debuggable):
entry_type = self.entry_type
if event.filename in self.entries:
- LOGGER.warn("Got duplicate add for %s" % event.filename)
+ self.logger.warn("Got duplicate add for %s" % event.filename)
else:
fpath = os.path.join(self.path, event.filename)
try:
@@ -1357,8 +1371,8 @@ class EntrySet(Debuggable):
specific=specific)
except SpecificityError:
if not self.ignore.match(event.filename):
- LOGGER.error("Could not process filename %s; ignoring" %
- fpath)
+ self.logger.error("Could not process filename %s; ignoring"
+ % fpath)
return
self.entries[event.filename] = entry_type(fpath, spec,
self.encoding)
@@ -1432,8 +1446,8 @@ class EntrySet(Debuggable):
self.infoxml = None
def bind_info_to_entry(self, entry, metadata):
- """ Shortcut to call :func:`bind_info` with the base
- info/info.xml for this EntrySet.
+ """ Bind the metadata for the given client in the base
+ info.xml for this EntrySet to the entry.
:param entry: The abstract entry to bind the info to. This
will be modified in place
@@ -1442,7 +1456,10 @@ class EntrySet(Debuggable):
:type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
:returns: None
"""
- bind_info(entry, metadata, infoxml=self.infoxml, default=self.metadata)
+ for attr, val in list(self.metadata.items()):
+ entry.set(attr, val)
+ if self.infoxml is not None:
+ self.infoxml.BindEntry(entry, metadata)
def bind_entry(self, entry, metadata):
""" Return the single best fully-bound entry from the set of
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgInfoXML.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgInfoXML.py
index 3b6fc8fa0..886b3993b 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgInfoXML.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgInfoXML.py
@@ -1,6 +1,6 @@
""" Handle info.xml files """
-from Bcfg2.Server.Plugin import PluginExecutionError, InfoXML
+from Bcfg2.Server.Plugin import InfoXML
from Bcfg2.Server.Plugins.Cfg import CfgInfo
@@ -17,21 +17,9 @@ class CfgInfoXML(CfgInfo):
__init__.__doc__ = CfgInfo.__init__.__doc__
def bind_info_to_entry(self, entry, metadata):
- mdata = dict()
- self.infoxml.pnode.Match(metadata, mdata, entry=entry)
- if 'Info' not in mdata:
- raise PluginExecutionError("Failed to set metadata for file %s" %
- entry.get('name'))
- self._set_info(entry, mdata['Info'][None])
+ self.infoxml.BindEntry(entry, metadata)
bind_info_to_entry.__doc__ = CfgInfo.bind_info_to_entry.__doc__
def handle_event(self, event):
self.infoxml.HandleEvent()
handle_event.__doc__ = CfgInfo.handle_event.__doc__
-
- def _set_info(self, entry, info):
- CfgInfo._set_info(self, entry, info)
- if '__children__' in info:
- for child in info['__children__']:
- entry.append(child)
- _set_info.__doc__ = CfgInfo._set_info.__doc__
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
index 0d8116c23..dc77339cc 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
@@ -218,10 +218,7 @@ class CfgFilter(CfgBaseFileMatcher):
class CfgInfo(CfgBaseFileMatcher):
""" CfgInfo handlers provide metadata (owner, group, paranoid,
- etc.) for a file entry.
-
- .. private-include: _set_info
- """
+ etc.) for a file entry. """
#: Whether or not the files handled by this handler are permitted
#: to have specificity indicators in their filenames -- e.g.,
@@ -251,20 +248,6 @@ class CfgInfo(CfgBaseFileMatcher):
"""
raise NotImplementedError
- def _set_info(self, entry, info):
- """ Helper function to assign a dict of info attributes to an
- entry object. ``entry`` is modified in-place.
-
- :param entry: The abstract entry to bind the info to
- :type entry: lxml.etree._Element
- :param info: A dict of attribute: value pairs
- :type info: dict
- :returns: None
- """
- for key, value in list(info.items()):
- if not key.startswith("__"):
- entry.attrib[key] = value
-
class CfgVerifier(CfgBaseFileMatcher):
""" CfgVerifier handlers validate entry data once it has been
@@ -422,22 +405,15 @@ class CfgDefaultInfo(CfgInfo):
""" :class:`Bcfg2.Server.Plugins.Cfg.Cfg` handler that supplies a
default set of file metadata """
- def __init__(self, defaults):
+ def __init__(self):
CfgInfo.__init__(self, '')
- self.defaults = defaults
__init__.__doc__ = CfgInfo.__init__.__doc__.split(".. -----")[0]
- def bind_info_to_entry(self, entry, metadata):
- self._set_info(entry, self.defaults)
+ def bind_info_to_entry(self, entry, _):
+ for key, value in Bcfg2.Server.Plugin.default_path_metadata().items():
+ entry.attrib[key] = value
bind_info_to_entry.__doc__ = CfgInfo.bind_info_to_entry.__doc__
-#: A :class:`CfgDefaultInfo` object instantiated with
-#: :func:`Bcfg2.Server.Plugin.helper.default_path_metadata` as its
-#: default metadata. This is used to set a default file metadata set
-#: on an entry before a "real" :class:`CfgInfo` handler applies its
-#: metadata to the entry.
-DEFAULT_INFO = CfgDefaultInfo(Bcfg2.Server.Plugin.default_path_metadata())
-
class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet,
Bcfg2.Server.Plugin.Debuggable):
@@ -646,7 +622,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet,
:returns: None
"""
info_handlers = self.get_handlers(metadata, CfgInfo)
- DEFAULT_INFO.bind_info_to_entry(entry, metadata)
+ CfgDefaultInfo().bind_info_to_entry(entry, metadata)
if len(info_handlers) > 1:
self.logger.error("More than one info supplier found for %s: %s" %
(entry.get("name"), info_handlers))
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
index ca7b7c530..5d3fbae2e 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/__init__.py
@@ -300,19 +300,16 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
if collection is None:
collection = self.get_collection(metadata)
- # initial is the set of packages that are explicitly specified
- # in the configuration
- initial = set()
- # base is the set of initial packages with groups expanded
+ # base is the set of initial packages -- explicitly
+ # given in the specification, from expanded package groups,
+ # and essential to the distribution
base = set()
- # essential pkgs are those marked as such by the distribution
- essential = collection.get_essential()
to_remove = []
groups = []
for struct in structures:
for pkg in struct.xpath('//Package | //BoundPackage'):
if pkg.get("name"):
- initial.update(collection.packages_from_entry(pkg))
+ base.update(collection.packages_from_entry(pkg))
elif pkg.get("group"):
groups.append((pkg.get("group"),
pkg.get("type")))
@@ -324,21 +321,24 @@ class Packages(Bcfg2.Server.Plugin.Plugin,
pkg,
xml_declaration=False).decode('UTF-8'))
+ # remove package groups
+ for el in to_remove:
+ el.getparent().remove(el)
+
gpkgs = collection.get_groups(groups)
for pkgs in gpkgs.values():
base.update(pkgs)
- base.update(initial | essential)
- for el in to_remove:
- el.getparent().remove(el)
+ # essential pkgs are those marked as such by the distribution
+ base.update(collection.get_essential())
packages, unknown = collection.complete(base)
if unknown:
self.logger.info("Packages: Got %d unknown entries" % len(unknown))
self.logger.info("Packages: %s" % list(unknown))
- newpkgs = collection.get_new_packages(initial, packages)
- self.debug_log("Packages: %d initial, %d complete, %d new" %
- (len(initial), len(packages), len(newpkgs)))
+ newpkgs = collection.get_new_packages(base, packages)
+ self.debug_log("Packages: %d base, %d complete, %d new" %
+ (len(base), len(packages), len(newpkgs)))
newpkgs.sort()
collection.packages_to_entry(newpkgs, independent)
diff --git a/src/lib/Bcfg2/Server/Plugins/Rules.py b/src/lib/Bcfg2/Server/Plugins/Rules.py
index 21862c5db..fb294972c 100644
--- a/src/lib/Bcfg2/Server/Plugins/Rules.py
+++ b/src/lib/Bcfg2/Server/Plugins/Rules.py
@@ -18,32 +18,25 @@ class Rules(Bcfg2.Server.Plugin.PrioDir):
self.Entries[entry.tag].keys())
return False
- def BindEntry(self, entry, metadata):
- attrs = self.get_attrs(entry, metadata)
- for key, val in list(attrs.items()):
- if key not in entry.attrib:
- entry.attrib[key] = val
+ HandleEntry = Bcfg2.Server.Plugin.PrioDir.BindEntry
- HandleEntry = BindEntry
-
- def _matches(self, entry, metadata, rules):
- if Bcfg2.Server.Plugin.PrioDir._matches(self, entry, metadata, rules):
+ def _matches(self, entry, metadata, candidate):
+ if Bcfg2.Server.Plugin.PrioDir._matches(self, entry, metadata,
+ candidate):
return True
elif (entry.tag == "Path" and
- ((entry.get('name').endswith("/") and
- entry.get('name').rstrip("/") in rules) or
- (not entry.get('name').endswith("/") and
- entry.get('name') + '/' in rules))):
+ entry.get('name').rstrip("/") == \
+ candidate.get("name").rstrip("/")):
# special case for Path tags:
# http://trac.mcs.anl.gov/projects/bcfg2/ticket/967
return True
elif self._regex_enabled:
# attempt regular expression matching
- for rule in rules:
- if rule not in self._regex_cache:
- self._regex_cache[rule] = re.compile("%s$" % rule)
- if self._regex_cache[rule].match(entry.get('name')):
- return True
+ rule = candidate.get("name")
+ if rule not in self._regex_cache:
+ self._regex_cache[rule] = re.compile("%s$" % rule)
+ if self._regex_cache[rule].match(entry.get('name')):
+ return True
return False
@property
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
index 9eb584e90..93bf69d04 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
@@ -47,47 +47,6 @@ class TestFunctions(Bcfg2TestCase):
data[1]]
self.assertItemsEqual(list(removecomment(stream)), data)
- def test_bind_info(self):
- entry = lxml.etree.Element("Path", name="/test")
- metadata = Mock()
- default = dict(test1="test1", test2="test2")
- # test without infoxml
- bind_info(entry, metadata, default=default)
- self.assertItemsEqual(entry.attrib,
- dict(test1="test1",
- test2="test2",
- name="/test"))
-
- # test with bogus infoxml
- entry = lxml.etree.Element("Path", name="/test")
- infoxml = Mock()
- self.assertRaises(PluginExecutionError,
- bind_info,
- entry, metadata, infoxml=infoxml)
- infoxml.pnode.Match.assert_called_with(metadata, dict(), entry=entry)
-
- # test with valid infoxml
- entry = lxml.etree.Element("Path", name="/test")
- infoxml.reset_mock()
- infodata = {None: {"test3": "test3", "test4": "test4"}}
- def infoxml_rv(metadata, rv, entry=None):
- rv['Info'] = infodata
- infoxml.pnode.Match.side_effect = infoxml_rv
- bind_info(entry, metadata, infoxml=infoxml, default=default)
- # mock objects don't properly track the called-with value of
- # arguments whose value is changed by the function, so it
- # thinks Match() was called with the final value of the mdata
- # arg, not the initial value. makes this test a little less
- # worthwhile, TBH.
- infoxml.pnode.Match.assert_called_with(metadata, dict(Info=infodata),
- entry=entry)
- self.assertItemsEqual(entry.attrib,
- dict(test1="test1",
- test2="test2",
- test3="test3",
- test4="test4",
- name="/test"))
-
class TestDatabaseBacked(TestPlugin):
test_obj = DatabaseBacked
@@ -652,7 +611,8 @@ class TestStructFile(TestXMLFileBacked):
lxml.etree.SubElement(groups[1], "Child", name="c3")
lxml.etree.SubElement(groups[1], "Child", name="c4")
- standalone.append(lxml.etree.SubElement(xdata, "Standalone", name="s1"))
+ standalone.append(lxml.etree.SubElement(xdata,
+ "Standalone", name="s1"))
groups[2] = lxml.etree.SubElement(xdata, "Client", name="client2",
include="false")
@@ -674,10 +634,10 @@ class TestStructFile(TestXMLFileBacked):
subchildren[3] = []
lxml.etree.SubElement(children[3][-1], "SubChild", name="subchild")
- standalone.append(lxml.etree.SubElement(xdata, "Standalone", name="s3"))
+ standalone.append(lxml.etree.SubElement(xdata,
+ "Standalone", name="s3"))
lxml.etree.SubElement(standalone[-1], "SubStandalone", name="sub1")
- children[4] = standalone
return (xdata, groups, subgroups, children, subchildren, standalone)
def _get_template_test_data(self):
@@ -860,7 +820,7 @@ class TestStructFile(TestXMLFileBacked):
self._get_test_data()
sf._include_element.side_effect = \
- lambda x, _: (x.tag not in ['Client', 'Group'] or
+ lambda x, _: (x.tag not in sf._include_tests.keys() or
x.get("include") == "true")
for i, group in groups.items():
@@ -879,7 +839,7 @@ class TestStructFile(TestXMLFileBacked):
for el in standalone:
self.assertXMLEqual(el, sf._match(el, metadata)[0])
- def test_Match(self):
+ def test_do_match(self):
sf = self.get_obj()
sf._match = Mock()
metadata = Mock()
@@ -892,19 +852,17 @@ class TestStructFile(TestXMLFileBacked):
sf.Index()
def match_rv(el, _):
- if el.tag not in ['Client', 'Group']:
+ if el.tag not in sf._include_tests.keys():
return [el]
elif el.get("include") == "true":
return el.getchildren()
else:
return []
sf._match.side_effect = match_rv
- actual = sf.Match(metadata)
+ actual = sf._do_match(metadata)
expected = reduce(lambda x, y: x + y,
- list(children.values()) + list(subgroups.values()))
- print "doc: %s" % lxml.etree.tostring(xdata, pretty_print=True)
- print "actual: %s" % [lxml.etree.tostring(el) for el in actual]
- print "expected: %s" % [lxml.etree.tostring(el) for el in expected]
+ list(children.values()) + \
+ list(subgroups.values())) + standalone
self.assertEqual(len(actual), len(expected))
# easiest way to compare the values is actually to make
# them into an XML document and let assertXMLEqual compare
@@ -924,7 +882,7 @@ class TestStructFile(TestXMLFileBacked):
self._get_test_data()
sf._include_element.side_effect = \
- lambda x, _: (x.tag not in ['Client', 'Group'] or
+ lambda x, _: (x.tag not in sf._include_tests.keys() or
x.get("include") == "true")
actual = copy.deepcopy(xdata)
@@ -937,7 +895,7 @@ class TestStructFile(TestXMLFileBacked):
expected.extend(standalone)
self.assertXMLEqual(actual, expected)
- def test_XMLMatch(self):
+ def test_do_xmlmatch(self):
sf = self.get_obj()
sf._xml_match = Mock()
metadata = Mock()
@@ -945,7 +903,7 @@ class TestStructFile(TestXMLFileBacked):
(sf.xdata, groups, subgroups, children, subchildren, standalone) = \
self._get_test_data()
- sf.XMLMatch(metadata)
+ sf._do_xmlmatch(metadata)
actual = []
for call in sf._xml_match.call_args_list:
actual.append(call[0][0])
@@ -1193,49 +1151,6 @@ class TestINode(Bcfg2TestCase):
inode.predicate.assert_called_with(metadata, child)
-class TestInfoNode(TestINode):
- __test__ = True
- test_obj = InfoNode
-
- def test_raw_predicates(self):
- TestINode.test_raw_predicates(self)
- metadata = Mock()
- entry = lxml.etree.Element("Path", name="/tmp/foo",
- realname="/tmp/bar")
-
- parent_predicate = lambda m, d: True
- pred = eval(self.test_obj.raw['Path'] % dict(name="/tmp/foo"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
- pred = eval(InfoNode.raw['Path'] % dict(name="/tmp/bar"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
- pred = eval(InfoNode.raw['Path'] % dict(name="/tmp/bogus"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
-
- pred = eval(self.test_obj.nraw['Path'] % dict(name="/tmp/foo"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(InfoNode.nraw['Path'] % dict(name="/tmp/bar"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(InfoNode.nraw['Path'] % dict(name="/tmp/bogus"),
- dict(predicate=parent_predicate))
- self.assertTrue(pred(metadata, entry))
-
- parent_predicate = lambda m, d: False
- pred = eval(self.test_obj.raw['Path'] % dict(name="/tmp/foo"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(InfoNode.raw['Path'] % dict(name="/tmp/bar"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
- pred = eval(InfoNode.nraw['Path'] % dict(name="/tmp/bogus"),
- dict(predicate=parent_predicate))
- self.assertFalse(pred(metadata, entry))
-
-
class TestXMLSrc(TestXMLFileBacked):
test_obj = XMLSrc
@@ -1301,9 +1216,78 @@ class TestXMLSrc(TestXMLFileBacked):
self.assertEqual(xsrc.cache[0], metadata)
-class TestInfoXML(TestXMLSrc):
+class TestInfoXML(TestStructFile):
test_obj = InfoXML
+ def _get_test_data(self):
+ (xdata, groups, subgroups, children, subchildren, standalone) = \
+ TestStructFile._get_test_data(self)
+ idx = max(groups.keys()) + 1
+ groups[idx] = lxml.etree.SubElement(
+ xdata, "Path", name="path1", include="true")
+ children[idx] = [lxml.etree.SubElement(groups[idx], "Child",
+ name="pc1")]
+ subgroups[idx] = [lxml.etree.SubElement(groups[idx], "Group",
+ name="pg1", include="true"),
+ lxml.etree.SubElement(groups[idx], "Client",
+ name="pc1", include="false")]
+ subchildren[idx] = [lxml.etree.SubElement(subgroups[idx][0],
+ "SubChild", name="sc1")]
+
+ idx += 1
+ groups[idx] = lxml.etree.SubElement(
+ xdata, "Path", name="path2", include="false")
+ children[idx] = []
+ subgroups[idx] = []
+ subchildren[idx] = []
+
+ path2 = lxml.etree.SubElement(groups[0], "Path", name="path2",
+ include="true")
+ subgroups[0].append(path2)
+ subchildren[0].append(lxml.etree.SubElement(path2, "SubChild",
+ name="sc2"))
+ return xdata, groups, subgroups, children, subchildren, standalone
+
+ def test_include_element(self):
+ TestStructFile.test_include_element(self)
+
+ ix = self.get_obj()
+ metadata = Mock()
+ entry = lxml.etree.Element("Path", name="/etc/foo.conf")
+ inc = lambda tag, **attrs: \
+ ix._include_element(lxml.etree.Element(tag, **attrs),
+ metadata, entry)
+
+ self.assertFalse(inc("Path", name="/etc/bar.conf"))
+ self.assertFalse(inc("Path", name="/etc/foo.conf", negate="true"))
+ self.assertFalse(inc("Path", name="/etc/foo.conf", negate="tRuE"))
+ self.assertTrue(inc("Path", name="/etc/foo.conf"))
+ self.assertTrue(inc("Path", name="/etc/foo.conf", negate="false"))
+ self.assertTrue(inc("Path", name="/etc/foo.conf", negate="faLSe"))
+ self.assertTrue(inc("Path", name="/etc/bar.conf", negate="true"))
+ self.assertTrue(inc("Path", name="/etc/bar.conf", negate="tRUe"))
+
+ def test_BindEntry(self):
+ ix = self.get_obj()
+ entry = lxml.etree.Element("Path", name=self.path)
+ metadata = Mock()
+
+ # test with bogus infoxml
+ ix.Match = Mock()
+ ix.Match.return_value = []
+ self.assertRaises(PluginExecutionError,
+ ix.BindEntry, entry, metadata)
+ ix.Match.assert_called_with(metadata, entry)
+
+ # test with valid infoxml
+ ix.Match.reset_mock()
+ ix.Match.return_value = [lxml.etree.Element("Info",
+ mode="0600", owner="root")]
+ ix.BindEntry(entry, metadata)
+ ix.Match.assert_called_with(metadata, entry)
+ self.assertItemsEqual(entry.attrib,
+ dict(name=self.path, mode="0600", owner="root"))
+
class TestXMLDirectoryBacked(TestDirectoryBacked):
test_obj = XMLDirectoryBacked
@@ -1348,32 +1332,17 @@ class TestPrioDir(TestPlugin, TestGenerator, TestXMLDirectoryBacked):
def test__matches(self):
pd = self.get_obj()
- self.assertTrue(pd._matches(lxml.etree.Element("Test",
- name="/etc/foo.conf"),
- Mock(),
- {"/etc/foo.conf": pd.BindEntry,
- "/etc/bar.conf": pd.BindEntry}))
- self.assertFalse(pd._matches(lxml.etree.Element("Test",
- name="/etc/baz.conf"),
- Mock(),
- {"/etc/foo.conf": pd.BindEntry,
- "/etc/bar.conf": pd.BindEntry}))
+ entry = lxml.etree.Element("Test", name="/etc/foo.conf")
+ self.assertTrue(pd._matches(entry, Mock(),
+ lxml.etree.Element("Test",
+ name="/etc/foo.conf")))
+ self.assertFalse(pd._matches(entry, Mock(),
+ lxml.etree.Element("Test",
+ name="/etc/baz.conf")))
def test_BindEntry(self):
pd = self.get_obj()
- pd.get_attrs = Mock(return_value=dict(test1="test1", test2="test2"))
- entry = lxml.etree.Element("Path", name="/etc/foo.conf", test1="bogus")
- metadata = Mock()
- pd.BindEntry(entry, metadata)
- pd.get_attrs.assert_called_with(entry, metadata)
- self.assertItemsEqual(entry.attrib,
- dict(name="/etc/foo.conf",
- test1="test1", test2="test2"))
-
- def test_get_attrs(self):
- pd = self.get_obj()
- entry = lxml.etree.Element("Path", name="/etc/foo.conf")
- children = [lxml.etree.Element("Child")]
+ children = [lxml.etree.Element("Child", name="child")]
metadata = Mock()
pd.entries = dict()
@@ -1381,58 +1350,59 @@ class TestPrioDir(TestPlugin, TestGenerator, TestXMLDirectoryBacked):
metadata.reset_mock()
for src in pd.entries.values():
src.reset_mock()
- src.cache = None
# test with no matches
- self.assertRaises(PluginExecutionError,
- pd.get_attrs, entry, metadata)
+ self.assertRaises(PluginExecutionError, pd.BindEntry, Mock(), metadata)
- def add_entry(name, data, prio=10):
+ def add_entry(name, data):
path = os.path.join(pd.data, name)
pd.entries[path] = Mock()
- pd.entries[path].priority = prio
- def do_Cache(metadata):
- pd.entries[path].cache = (metadata, data)
- pd.entries[path].Cache.side_effect = do_Cache
-
- add_entry('test1.xml',
- dict(Path={'/etc/foo.conf': dict(attr="attr1",
- __children__=children),
- '/etc/bar.conf': dict()}))
- add_entry('test2.xml',
- dict(Path={'/etc/bar.conf': dict(__text__="text",
- attr="attr1")},
- Package={'quux': dict(),
- 'xyzzy': dict()}),
- prio=20)
- add_entry('test3.xml',
- dict(Path={'/etc/baz.conf': dict()},
- Package={'xyzzy': dict()}),
- prio=20)
-
- # test with exactly one match, __children__
+ pd.entries[path].priority = data.get("priority")
+ pd.entries[path].XMLMatch.return_value = data
+
+ test1 = lxml.etree.Element("Rules", priority="10")
+ path1 = lxml.etree.SubElement(test1, "Path", name="/etc/foo.conf",
+ attr="attr1")
+ path1.extend(children)
+ lxml.etree.SubElement(test1, "Path", name="/etc/bar.conf")
+ add_entry('test1.xml', test1)
+
+ test2 = lxml.etree.Element("Rules", priority="20")
+ path2 = lxml.etree.SubElement(test2, "Path", name="/etc/bar.conf",
+ attr="attr1")
+ path2.text = "text"
+ lxml.etree.SubElement(test2, "Package", name="quux")
+ lxml.etree.SubElement(test2, "Package", name="xyzzy")
+ add_entry('test2.xml', test2)
+
+ test3 = lxml.etree.Element("Rules", priority="20")
+ lxml.etree.SubElement(test3, "Path", name="/etc/baz.conf")
+ lxml.etree.SubElement(test3, "Package", name="xyzzy")
+ add_entry('test3.xml', test3)
+
+ # test with exactly one match, children
reset()
- self.assertItemsEqual(pd.get_attrs(entry, metadata),
- dict(attr="attr1"))
+ entry = lxml.etree.Element("Path", name="/etc/foo.conf")
+ pd.BindEntry(entry, metadata)
+ self.assertXMLEqual(entry, path1)
+ self.assertIsNot(entry, path1)
for src in pd.entries.values():
- src.Cache.assert_called_with(metadata)
- self.assertEqual(len(entry.getchildren()), 1)
- self.assertXMLEqual(entry.getchildren()[0], children[0])
+ src.XMLMatch.assert_called_with(metadata)
- # test with multiple matches with different priorities, __text__
+ # test with multiple matches with different priorities, text
reset()
entry = lxml.etree.Element("Path", name="/etc/bar.conf")
- self.assertItemsEqual(pd.get_attrs(entry, metadata),
- dict(attr="attr1"))
+ pd.BindEntry(entry, metadata)
+ self.assertXMLEqual(entry, path2)
+ self.assertIsNot(entry, path2)
for src in pd.entries.values():
- src.Cache.assert_called_with(metadata)
- self.assertEqual(entry.text, "text")
+ src.XMLMatch.assert_called_with(metadata)
# test with multiple matches with identical priorities
reset()
entry = lxml.etree.Element("Package", name="xyzzy")
self.assertRaises(PluginExecutionError,
- pd.get_attrs, entry, metadata)
+ pd.BindEntry, entry, metadata)
class TestSpecificity(Bcfg2TestCase):
@@ -1866,22 +1836,22 @@ class TestEntrySet(TestDebuggable):
eset.reset_metadata(event)
self.assertIsNone(eset.infoxml)
- @patch("Bcfg2.Server.Plugin.helpers.bind_info")
- def test_bind_info_to_entry(self, mock_bind_info):
- # There's a strange scoping issue in py3k that prevents this
- # test from working as expected on sub-classes of EntrySet.
- # No idea what's going on, but until I can figure it out we
- # skip this test on subclasses
- if inPy3k and self.test_obj != EntrySet:
- return skip("Skipping this test for py3k scoping issues")
-
+ def test_bind_info_to_entry(self):
eset = self.get_obj()
- entry = Mock()
+ eset.metadata = dict(owner="root", group="root")
+ entry = lxml.etree.Element("Path", name="/test")
metadata = Mock()
+ eset.infoxml = None
+ eset.bind_info_to_entry(entry, metadata)
+ self.assertItemsEqual(entry.attrib,
+ dict(name="/test", owner="root", group="root"))
+
+ entry = lxml.etree.Element("Path", name="/test")
+ eset.infoxml = Mock()
eset.bind_info_to_entry(entry, metadata)
- mock_bind_info.assert_called_with(entry, metadata,
- infoxml=eset.infoxml,
- default=eset.metadata)
+ self.assertItemsEqual(entry.attrib,
+ dict(name="/test", owner="root", group="root"))
+ eset.infoxml.BindEntry.assert_called_with(entry, metadata)
def test_bind_entry(self):
eset = self.get_obj()
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py
index 839e9c3b8..7e7cb5e3c 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgInfoXML.py
@@ -27,47 +27,16 @@ class TestCfgInfoXML(TestCfgInfo):
self.assertIsInstance(ci.infoxml, InfoXML)
def test_bind_info_to_entry(self):
- entry = lxml.etree.Element("Path", name="/test.txt")
- metadata = Mock()
ci = self.get_obj()
ci.infoxml = Mock()
- ci._set_info = Mock()
-
- self.assertRaises(PluginExecutionError,
- ci.bind_info_to_entry, entry, metadata)
- ci.infoxml.pnode.Match.assert_called_with(metadata, dict(),
- entry=entry)
- self.assertFalse(ci._set_info.called)
-
- ci.infoxml.reset_mock()
- ci._set_info.reset_mock()
- mdata_value = Mock()
- def set_mdata(metadata, mdata, entry=None):
- mdata['Info'] = {None: mdata_value}
+ entry = Mock()
+ metadata = Mock()
- ci.infoxml.pnode.Match.side_effect = set_mdata
ci.bind_info_to_entry(entry, metadata)
- ci.infoxml.pnode.Match.assert_called_with(metadata,
- dict(Info={None: mdata_value}),
- entry=entry)
- ci._set_info.assert_called_with(entry, mdata_value)
+ ci.infoxml.BindEntry.assert_called_with(entry, metadata)
def test_handle_event(self):
ci = self.get_obj()
ci.infoxml = Mock()
ci.handle_event(Mock)
ci.infoxml.HandleEvent.assert_called_with()
-
- def test__set_info(self):
- @patch("Bcfg2.Server.Plugins.Cfg.CfgInfo._set_info")
- def inner(mock_set_info):
- ci = self.get_obj()
- entry = Mock()
- info = {"foo": "foo",
- "__children__": ["one", "two"]}
- ci._set_info(entry, info)
- self.assertItemsEqual(entry.append.call_args_list,
- [call(c) for c in info['__children__']])
-
- inner()
- TestCfgInfo.test__set_info(self)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
index ee0b0be9d..ab383e4f3 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
@@ -159,21 +159,6 @@ class TestCfgInfo(TestCfgBaseFileMatcher):
self.assertRaises(NotImplementedError,
ci.bind_info_to_entry, Mock(), Mock())
- def test__set_info(self):
- ci = self.get_obj()
- entry = Mock()
- entry.attrib = dict()
-
- info = {"foo": "foo",
- "_bar": "bar",
- "bar:baz=quux": "quux",
- "baz__": "baz",
- "__quux": "quux"}
- ci._set_info(entry, info)
- self.assertItemsEqual(entry.attrib,
- dict([(k, v) for k, v in info.items()
- if not k.startswith("__")]))
-
class TestCfgVerifier(TestCfgBaseFileMatcher):
test_obj = CfgVerifier
@@ -259,29 +244,26 @@ class TestCfgCreator(TestCfgBaseFileMatcher):
class TestCfgDefaultInfo(TestCfgInfo):
test_obj = CfgDefaultInfo
- def get_obj(self, defaults=None):
- if defaults is None:
- defaults = dict()
- return self.test_obj(defaults)
+ def get_obj(self, *_):
+ return self.test_obj()
- @patch("Bcfg2.Server.Plugins.Cfg.CfgInfo.__init__")
- def test__init(self, mock__init):
- defaults = Mock()
- cdi = self.get_obj(defaults=defaults)
- mock__init.assert_called_with(cdi, '')
- self.assertEqual(defaults, cdi.defaults)
+ def test__init(self):
+ pass
def test_handle_event(self):
# this CfgInfo handler doesn't handle any events -- it's not
# file-driven, but based on the built-in defaults
pass
- def test_bind_info_to_entry(self):
+ @patch("Bcfg2.Server.Plugin.default_path_metadata")
+ def test_bind_info_to_entry(self, mock_default_path_metadata):
cdi = self.get_obj()
- cdi._set_info = Mock()
- entry = Mock()
+ entry = lxml.etree.Element("Test", name="test")
+ mock_default_path_metadata.return_value = \
+ dict(owner="root", mode="0600")
cdi.bind_info_to_entry(entry, Mock())
- cdi._set_info.assert_called_with(entry, cdi.defaults)
+ self.assertItemsEqual(entry.attrib,
+ dict(owner="root", mode="0600", name="test"))
class TestCfgEntrySet(TestEntrySet):
@@ -599,24 +581,24 @@ class TestCfgEntrySet(TestEntrySet):
if entry.specific is not None:
self.assertFalse(entry.specific.matches.called)
- def test_bind_info_to_entry(self):
- default_info = Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgDefaultInfo")
+ def test_bind_info_to_entry(self, mock_DefaultInfo):
eset = self.get_obj()
eset.get_handlers = Mock()
eset.get_handlers.return_value = []
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO = Mock()
metadata = Mock()
def reset():
eset.get_handlers.reset_mock()
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO.reset_mock()
+ mock_DefaultInfo.reset_mock()
return lxml.etree.Element("Path", name="/test.txt")
# test with no info handlers
entry = reset()
eset.bind_info_to_entry(entry, metadata)
eset.get_handlers.assert_called_with(metadata, CfgInfo)
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO.bind_info_to_entry.assert_called_with(entry, metadata)
+ mock_DefaultInfo.return_value.bind_info_to_entry.assert_called_with(
+ entry, metadata)
self.assertEqual(entry.get("type"), "file")
# test with one info handler
@@ -625,7 +607,8 @@ class TestCfgEntrySet(TestEntrySet):
eset.get_handlers.return_value = [handler]
eset.bind_info_to_entry(entry, metadata)
eset.get_handlers.assert_called_with(metadata, CfgInfo)
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO.bind_info_to_entry.assert_called_with(entry, metadata)
+ mock_DefaultInfo.return_value.bind_info_to_entry.assert_called_with(
+ entry, metadata)
handler.bind_info_to_entry.assert_called_with(entry, metadata)
self.assertEqual(entry.get("type"), "file")
@@ -635,7 +618,8 @@ class TestCfgEntrySet(TestEntrySet):
eset.get_handlers.return_value = handlers
eset.bind_info_to_entry(entry, metadata)
eset.get_handlers.assert_called_with(metadata, CfgInfo)
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO.bind_info_to_entry.assert_called_with(entry, metadata)
+ mock_DefaultInfo.return_value.bind_info_to_entry.assert_called_with(
+ entry, metadata)
# we don't care which handler gets called as long as exactly
# one of them does
called = 0
@@ -646,8 +630,6 @@ class TestCfgEntrySet(TestEntrySet):
self.assertEqual(called, 1)
self.assertEqual(entry.get("type"), "file")
- Bcfg2.Server.Plugins.Cfg.DEFAULT_INFO = default_info
-
def test_create_data(self):
eset = self.get_obj()
eset.best_matching = Mock()
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py
index f018b45dc..7083fff06 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py
@@ -45,81 +45,37 @@ class TestRules(TestPrioDir):
entry = lxml.etree.Element("Package", name="foo")
self.assertFalse(r.HandlesEntry(entry, metadata))
- def test_BindEntry(self, method="BindEntry"):
- r = self.get_obj()
- r.get_attrs = Mock()
- r.get_attrs.return_value = dict(overwrite="new", add="add",
- text="text")
- entry = lxml.etree.Element("Test", overwrite="old", keep="keep")
- metadata = Mock()
-
- getattr(r, method)(entry, metadata)
- r.get_attrs.assert_called_with(entry, metadata)
- self.assertItemsEqual(entry.attrib,
- dict(overwrite="old", add="add", keep="keep",
- text="text"))
-
- def test_HandleEntry(self):
- self.test_BindEntry(method="HandleEntry")
-
@patch("Bcfg2.Server.Plugin.PrioDir._matches")
def test__matches(self, mock_matches):
- """ test _matches() behavior regardless of state of _regex_enabled """
r = self.get_obj()
metadata = Mock()
+ # test parent _matches() returning True
entry = lxml.etree.Element("Path", name="/etc/foo.conf")
- rules = []
+ candidate = lxml.etree.Element("Path", name="/etc/bar.conf")
mock_matches.return_value = True
- self.assertTrue(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
+ self.assertTrue(r._matches(entry, metadata, candidate))
+ mock_matches.assert_called_with(r, entry, metadata, candidate)
- # test special Path cases -- adding and removing trailing slash
+ # test all conditions returning False
mock_matches.reset_mock()
mock_matches.return_value = False
- rules = ["/etc/foo/", "/etc/bar"]
- entry = lxml.etree.Element("Path", name="/etc/foo")
- self.assertTrue(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
+ self.assertFalse(r._matches(entry, metadata, candidate))
+ mock_matches.assert_called_with(r, entry, metadata, candidate)
+ # test special Path cases -- adding and removing trailing slash
mock_matches.reset_mock()
- entry = lxml.etree.Element("Path", name="/etc/bar/")
- self.assertTrue(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
-
- @patch("Bcfg2.Server.Plugin.PrioDir._matches")
- def test__matches_regex_disabled(self, mock_matches):
- """ test failure to match with regex disabled """
- r = self.get_obj()
- self.set_regex_enabled(r, False)
- metadata = Mock()
- mock_matches.return_value = False
-
- entry = lxml.etree.Element("Path", name="/etc/foo.conf")
- rules = []
- self.assertFalse(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
-
- @patch("Bcfg2.Server.Plugin.PrioDir._matches")
- def test__matches_regex_enabled(self, mock_matches):
- """ test match with regex enabled """
- r = self.get_obj()
- self.set_regex_enabled(r, True)
- metadata = Mock()
- mock_matches.return_value = False
-
- entry = lxml.etree.Element("Path", name="/etc/foo.conf")
- rules = ["/etc/.*\.conf", "/etc/bar"]
- self.assertTrue(r._matches(entry, metadata, rules))
- mock_matches.assert_called_with(r, entry, metadata, rules)
- self.assertIn("/etc/.*\.conf", r._regex_cache.keys())
-
- def set_regex_enabled(self, rules_obj, state):
- """ set the state of regex_enabled for this implementation of
- Rules """
- if not isinstance(rules_obj.core.setup, MagicMock):
- rules_obj.core.setup = MagicMock()
- rules_obj.core.setup.cfp.getboolean.return_value = state
+ withslash = lxml.etree.Element("Path", name="/etc/foo")
+ withoutslash = lxml.etree.Element("Path", name="/etc/foo/")
+ self.assertTrue(r._matches(withslash, metadata, withoutslash))
+ self.assertTrue(r._matches(withoutslash, metadata, withslash))
+
+ if r._regex_enabled:
+ mock_matches.reset_mock()
+ candidate = lxml.etree.Element("Path", name="/etc/.*\.conf")
+ self.assertTrue(r._matches(entry, metadata, candidate))
+ mock_matches.assert_called_with(r, entry, metadata, candidate)
+ self.assertIn("/etc/.*\.conf", r._regex_cache.keys())
def test__regex_enabled(self):
r = self.get_obj()