summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2011-05-06 08:13:20 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2011-05-06 08:13:20 -0400
commita4de0e44bd10761f5ec7d948fd0a3824720a90f2 (patch)
treedf582678a12f77a29fede1965727fb27c94ecbe1
parente38ea05100ddf670898f1d11ffc09b9d44435f27 (diff)
downloadbcfg2-a4de0e44bd10761f5ec7d948fd0a3824720a90f2.tar.gz
bcfg2-a4de0e44bd10761f5ec7d948fd0a3824720a90f2.tar.bz2
bcfg2-a4de0e44bd10761f5ec7d948fd0a3824720a90f2.zip
Rewrote NagiosGen config to use NagiosGen/config.xml, which
understands <Group> and <Client> tags, rather than the client-specific Properties/NagiosGen.xml and the group-specific but limited NagiosGen/parents.xml. Includes schema and bcfg2-lint updates necessary. Wrote conversion tool, nagiosgen-convert.py, which converts everything but the <default/> tag in the old NagiosGen.xml, which cannot be reasonably converted to StructFile format. Also removed a _lot_ of string modification in NagiosGen.py, which should make it a fair bit faster.
-rw-r--r--doc/server/plugins/generators/nagiosgen.txt31
-rw-r--r--schemas/nagiosgen.xsd31
-rw-r--r--src/lib/Server/Lint/Validate.py3
-rw-r--r--src/lib/Server/Plugins/NagiosGen.py177
-rwxr-xr-xtools/nagiosgen-convert.py75
5 files changed, 228 insertions, 89 deletions
diff --git a/doc/server/plugins/generators/nagiosgen.txt b/doc/server/plugins/generators/nagiosgen.txt
index 196f1e76b..f3dc7823c 100644
--- a/doc/server/plugins/generators/nagiosgen.txt
+++ b/doc/server/plugins/generators/nagiosgen.txt
@@ -169,3 +169,34 @@ Note that some of these files are built on demand, each time a client
in group "nagios-server" checks in with the Bcfg2 server. Local nagios
instances can be configured to use the NagiosGen directory in the Bcfg2
repository directly.
+
+Fine-Grained Configuration
+==========================
+
+NagiosGen can be configured in excruciating detail by editing
+``NagiosGen/config.xml``, which will let you set individual Nagios
+options for hosts or groups. E.g.:
+
+.. code-block:: xml
+
+ <NagiosGen>
+ <Group name="datacenter-2">
+ <Option name="parents">dc-2-switch</Option>
+ <Group>
+ <Group name="non-production">
+ <Option name="notification_period">workhours</Option>
+ <Option name="notification_options">d</Option>
+ </Group>
+ <Client name="foo.example.com">
+ <Option name="max_check_attempts">10</Option>
+ </Client>
+ </NagiosGen>
+
+Obviously the sort of fine-grained control you get from this overlaps
+to some degree with Nagios' own templating, so use it wisely and in
+moderation.
+
+``NagiosGen/config.xml`` replaces the files
+``Properties/NagiosGen.xml`` and ``NagiosGen/parents.xml`` in older
+versions of Bcfg2; your old configs can be migrated using the
+``nagiosgen-convert.py`` tool.
diff --git a/schemas/nagiosgen.xsd b/schemas/nagiosgen.xsd
new file mode 100644
index 000000000..080994cd1
--- /dev/null
+++ b/schemas/nagiosgen.xsd
@@ -0,0 +1,31 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xml:lang="en">
+ <xsd:annotation>
+ <xsd:documentation>
+ NagiosGen config schema for bcfg2
+ Chris St. Pierre
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:complexType name="GroupType">
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element name="Option" type="OptionType"/>
+ <xsd:element name="Group" type="GroupType"/>
+ <xsd:element name="Client" type="GroupType"/>
+ </xsd:choice>
+ <xsd:attribute type="xsd:string" name="name" use="required"/>
+ <xsd:attribute type="xsd:string" name="negate"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="OptionType" mixed="true">
+ <xsd:attribute type="xsd:string" name="name" use="required"/>
+ </xsd:complexType>
+
+ <xsd:element name="NagiosGen">
+ <xsd:complexType>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element name="Group" type="GroupType"/>
+ <xsd:element name="Client" type="GroupType"/>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+</xsd:schema>
diff --git a/src/lib/Server/Lint/Validate.py b/src/lib/Server/Lint/Validate.py
index c7a77a4fb..c87c55ee9 100644
--- a/src/lib/Server/Lint/Validate.py
+++ b/src/lib/Server/Lint/Validate.py
@@ -23,7 +23,8 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
"%s/Deps/*.xml":"%s/deps.xsd",
"%s/Decisions/*.xml":"%s/decisions.xsd",
"%s/Packages/config.xml":"%s/packages.xsd",
- "%s/GroupPatterns/config.xml":"%s/grouppatterns.xsd"}
+ "%s/GroupPatterns/config.xml":"%s/grouppatterns.xsd",
+ "%s/NagiosGen/config.xml":"%s/nagiosgen.xsd"}
self.filelists = {}
self.get_filelists()
diff --git a/src/lib/Server/Plugins/NagiosGen.py b/src/lib/Server/Plugins/NagiosGen.py
index ca70ba80c..0f17a8015 100644
--- a/src/lib/Server/Plugins/NagiosGen.py
+++ b/src/lib/Server/Plugins/NagiosGen.py
@@ -1,40 +1,43 @@
'''This module implements a Nagios configuration generator'''
-import glob
-import logging
-import lxml.etree
import os
import re
+import sys
+import glob
import socket
+import logging
+import lxml.etree
import Bcfg2.Server.Plugin
LOGGER = logging.getLogger('Bcfg2.Plugins.NagiosGen')
-host_config_fmt = \
-'''
-define host{
- host_name %s
- alias %s
- address %s
-'''
+line_fmt = '\t%-32s %s'
+class NagiosGenConfig(Bcfg2.Server.Plugin.SingleXMLFileBacked,
+ Bcfg2.Server.Plugin.StructFile):
+ def __init__(self, filename, fam):
+ Bcfg2.Server.Plugin.SingleXMLFileBacked.__init__(self, filename, fam)
+ Bcfg2.Server.Plugin.StructFile.__init__(self, filename)
+
class NagiosGen(Bcfg2.Server.Plugin.Plugin,
Bcfg2.Server.Plugin.Generator):
"""NagiosGen is a Bcfg2 plugin that dynamically generates
Nagios configuration file based on Bcfg2 data.
"""
name = 'NagiosGen'
- __version__ = '0.6'
+ __version__ = '0.7'
__author__ = 'bcfg-dev@mcs.anl.gov'
def __init__(self, core, datastore):
Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
Bcfg2.Server.Plugin.Generator.__init__(self)
+ self.config = NagiosGenConfig(os.path.join(self.data, 'config.xml'),
+ core.fam)
self.Entries = {'Path':
- {'/etc/nagiosgen.status': self.createhostconfig,
- '/etc/nagios/nagiosgen.cfg': self.createserverconfig}}
+ {'/etc/nagiosgen.status': self.createhostconfig,
+ '/etc/nagios/nagiosgen.cfg': self.createserverconfig}}
self.client_attrib = {'encoding': 'ascii',
'owner': 'root',
@@ -47,106 +50,104 @@ class NagiosGen(Bcfg2.Server.Plugin.Plugin,
'type': 'file',
'perms': '0440'}
- def getparents(self, hostname):
- """Return parents for given hostname."""
- depends = []
- if not os.path.isfile('%s/parents.xml' % (self.data)):
- return depends
-
- tree = lxml.etree.parse('%s/parents.xml' % (self.data))
- for entry in tree.findall('.//Depend'):
- if entry.attrib['name'] == hostname:
- depends.append(entry.attrib['on'])
- return depends
-
def createhostconfig(self, entry, metadata):
"""Build host specific configuration file."""
host_address = socket.gethostbyname(metadata.hostname)
- host_groups = [grp for grp in metadata.groups if \
- os.path.isfile('%s/%s-group.cfg' % (self.data, grp))]
- host_config = host_config_fmt % \
- (metadata.hostname, metadata.hostname, host_address)
+ host_groups = [grp for grp in metadata.groups
+ if os.path.isfile('%s/%s-group.cfg' % (self.data, grp))]
+ host_config = []
+ host_config.append(line_fmt % ('host_name', metadata.hostname))
+ host_config.append(line_fmt % ('alias', metadata.hostname))
+ host_config.append(line_fmt % ('address', host_address))
if host_groups:
- host_config += ' hostgroups %s\n' % (",".join(host_groups))
-
- xtra = None
- if hasattr(metadata, 'Properties') and \
- 'NagiosGen.xml' in metadata.Properties:
- for q in (metadata.hostname, 'default'):
- xtra = metadata.Properties['NagiosGen.xml'].data.find(q)
- if xtra is not None:
- break
-
- if xtra is not None:
- directives = list(xtra)
- for item in directives:
- host_config += ' %-32s %s\n' % (item.tag, item.text)
-
+ host_config.append(line_fmt % ("hostgroups",
+ ",".join(host_groups)))
+
+ # read the old-style Properties config, but emit a warning.
+ xtra = dict()
+ props = None
+ if (hasattr(metadata, 'Properties') and
+ 'NagiosGen.xml' in metadata.Properties):
+ props = metadata.Properties['NagiosGen.xml'].data
+ if props is not None:
+ LOGGER.warn("Parsing deprecated Properties/NagiosGen.xml. "
+ "Update to the new-style config with "
+ "nagiosgen-convert.py.")
+ xtra = dict((el.tag, el.text)
+ for el in props.find(metadata.hostname))
+ # hold off on parsing the defaults until we've checked for
+ # a new-style config
+
+ # read the old-style parents.xml, but emit a warning
+ pfile = os.path.join(self.data, "parents.xml")
+ if os.path.exists(pfile):
+ LOGGER.warn("Parsing deprecated NagiosGen/parents.xml. "
+ "Update to the new-style config with "
+ "nagiosgen-convert.py.")
+ parents = lxml.etree.parse(pfile)
+ for el in parents.xpath("//Depend[@name='%s']" % metadata.hostname):
+ if 'parent' in xtra:
+ xtra['parent'] += "," + el.get("on")
+ else:
+ xtra['parent'] = el.get("on")
+
+ # read the new-style config and overwrite the old-style config
+ for el in self.config.Match():
+ if el.tag == 'Option':
+ xtra[el.get("name")] = el.text
+
+ # if we haven't found anything in the new- or old-style
+ # configs, finally read defaults from old-style config
+ if (not xtra and
+ hasattr(metadata, 'Properties') and
+ 'NagiosGen.xml' in metadata.Properties):
+ xtra = dict((el.tag, el.text) for el in props.find('default'))
+
+ if xtra:
+ host_config.extend([line_fmt % (opt, val)
+ for opt, val in list(xtra.items)])
else:
- host_config += ' use default\n'
+ host_config.append(line_fmt % ('use', 'default'))
- host_config += '}\n'
- entry.text = host_config
- [entry.attrib.__setitem__(key, value) for \
- (key, value) in list(self.client_attrib.items())]
+ entry.text = "define host {\n%s\n}" % "\n".join(host_config)
+ [entry.attrib.__setitem__(key, value)
+ for (key, value) in list(self.client_attrib.items())]
try:
- fileh = open("%s/%s-host.cfg" % \
- (self.data, metadata.hostname), 'w')
+ fileh = open("%s/%s-host.cfg" %
+ (self.data, metadata.hostname), 'w')
fileh.write(host_config)
fileh.close()
except OSError:
ioerr = sys.exc_info()[1]
- LOGGER.error("Failed to write %s/%s-host.cfg" % \
- (self.data, metadata.hostname))
+ LOGGER.error("Failed to write %s/%s-host.cfg" %
+ (self.data, metadata.hostname))
LOGGER.error(ioerr)
def createserverconfig(self, entry, _):
"""Build monolithic server configuration file."""
host_configs = glob.glob('%s/*-host.cfg' % self.data)
group_configs = glob.glob('%s/*-group.cfg' % self.data)
- host_data = ""
- group_data = ""
+ host_data = []
+ group_data = []
for host in host_configs:
- hostfile = open(host, 'r')
- hostname = host.split('/')[-1].replace('-host.cfg', '')
- parents = self.getparents(hostname)
- if parents:
- hostlines = hostfile.readlines()
- else:
- hostdata = hostfile.read()
- hostfile.close()
-
- if parents:
- hostdata = ''
- addparents = True
- for line in hostlines:
- line = line.replace('\n', '')
- if 'parents' in line:
- line += ',' + ','.join(parents)
- addparents = False
- if '}' in line:
- line = ''
- hostdata += "%s\n" % line
- if addparents:
- hostdata += " parents %s\n" % ','.join(parents)
- hostdata += "}\n"
-
- host_data += hostdata
+ host_data.append(open(host, 'r').read())
+
for group in group_configs:
group_name = re.sub("(-group.cfg|.*/(?=[^/]+))", "", group)
- if host_data.find(group_name) != -1:
+ if "\n".join(host_data).find(group_name) != -1:
groupfile = open(group, 'r')
- group_data += groupfile.read()
+ group_data.append(groupfile.read())
groupfile.close()
- entry.text = group_data + host_data
- [entry.attrib.__setitem__(key, value) for \
- (key, value) in list(self.server_attrib.items())]
+
+ entry.text = "%s\n\n%s" % ("\n".join(group_data), "\n".join(host_data))
+ [entry.attrib.__setitem__(key, value)
+ for (key, value) in list(self.server_attrib.items())]
try:
- fileh = open("%s/nagiosgen.cfg" % (self.data), 'w')
- fileh.write(group_data + host_data)
+ fileh = open("%s/nagiosgen.cfg" % self.data, 'w')
+ fileh.write(entry.text)
fileh.close()
except OSError:
ioerr = sys.exc_info()[1]
- LOGGER.error("Failed to write %s/nagiosgen.cfg" % (self.data))
+ LOGGER.error("Failed to write %s/nagiosgen.cfg" % self.data)
LOGGER.error(ioerr)
diff --git a/tools/nagiosgen-convert.py b/tools/nagiosgen-convert.py
new file mode 100755
index 000000000..2c2142735
--- /dev/null
+++ b/tools/nagiosgen-convert.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import lxml.etree
+
+import Bcfg2.Options
+
+def main():
+ opts = {'repo': Bcfg2.Options.SERVER_REPOSITORY}
+ setup = Bcfg2.Options.OptionParser(opts)
+ setup.parse(sys.argv[1:])
+ repo = setup['repo']
+ oldconfigfile = os.path.join(repo, 'Properties', 'NagiosGen.xml')
+ newconfigpath = os.path.join(repo, 'NagiosGen')
+ newconfigfile = os.path.join(newconfigpath, 'config.xml')
+ parentsfile = os.path.join(newconfigpath, 'parents.xml')
+
+ if not os.path.exists(oldconfigfile):
+ print("%s does not exist, nothing to do" % oldconfigfile)
+ return 1
+
+ if not os.path.exists(newconfigpath):
+ print("%s does not exist, cannot write %s" %
+ (newconfigpath, newconfigfile))
+ return 2
+
+ newconfig = lxml.etree.XML("<NagiosGen/>")
+
+ oldconfig = lxml.etree.parse(oldconfigfile)
+ for host in oldconfig.getroot().getchildren():
+ if host.tag == lxml.etree.Comment:
+ # skip comments
+ continue
+
+ if host.tag == 'default':
+ print("default tag will not be converted; use a suitable Group tag instead")
+ continue
+
+ newhost = lxml.etree.Element("Client", name=host.tag)
+ for opt in host:
+ newopt = lxml.etree.Element("Option", name=opt.tag)
+ newopt.text = opt.text
+ newhost.append(newopt)
+ newconfig.append(newhost)
+
+ # parse the parents config, if it exists
+ if os.path.exists(parentsfile):
+ parentsconfig = lxml.etree.parse(parentsfile)
+ for el in parentsconfig.xpath("//Depend"):
+ newhost = newconfig.find("Client[@name='%s']" % el.get("name"))
+ if newhost is not None:
+ newparents = newhost.find("Option[@name='parents']")
+ if newparents is not None:
+ newparents.text += "," + el.get("on")
+ else:
+ newparents = lxml.etree.Element("Option", name="parents")
+ newparents.text = el.get("on")
+ newhost.append(newparents)
+ else:
+ newhost = lxml.etree.Element("Client", name=el.get("name"))
+ newparents = lxml.etree.Element("Option", name="parents")
+ newparents.text = el.get("on")
+ newhost.append(newparents)
+ newconfig.append(newhost)
+
+ try:
+ open(newconfigfile, 'w').write(lxml.etree.tostring(newconfig,
+ pretty_print=True))
+ print("%s written" % newconfigfile)
+ except IOError:
+ print("Failed to write %s" % newconfigfile)
+
+if __name__ == '__main__':
+ sys.exit(main())