summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSol Jerome <sol.jerome@gmail.com>2013-06-02 14:20:37 -0500
committerSol Jerome <sol.jerome@gmail.com>2013-06-02 14:20:37 -0500
commit9de1086c937c5f8919cb279e7f601d7de064f901 (patch)
tree83362fdb6ee6bfb17bc8aa1477cb5c06f54022ca
parentfdeba3b8f2cea7ecb2200cab42ce28fd1e1e6a5c (diff)
parent521862b6584eb4d68e12df5d1a5c4f7a8ef1bdf5 (diff)
downloadbcfg2-9de1086c937c5f8919cb279e7f601d7de064f901.tar.gz
bcfg2-9de1086c937c5f8919cb279e7f601d7de064f901.tar.bz2
bcfg2-9de1086c937c5f8919cb279e7f601d7de064f901.zip
Merge branch 'maint'
Signed-off-by: Sol Jerome <sol.jerome@gmail.com> Conflicts: doc/installation/distributions.txt doc/server/snapshots/index.txt src/lib/Bcfg2/Server/Plugin/helpers.py src/sbin/bcfg2-server
-rw-r--r--.travis.yml1
-rw-r--r--COPYRIGHT3
-rwxr-xr-xdebian/bcfg2-server.init3
-rwxr-xr-xdebian/bcfg2.cron.daily2
-rwxr-xr-xdebian/bcfg2.cron.hourly2
-rw-r--r--doc/development/plugins.txt2
-rw-r--r--doc/exts/xmlschema.py10
-rw-r--r--doc/help/troubleshooting.txt2
-rw-r--r--doc/installation/distributions.txt14
-rw-r--r--doc/server/encryption.txt3
-rw-r--r--misc/bcfg2-selinux.spec2
-rw-r--r--misc/bcfg2.spec24
-rw-r--r--schemas/servicetype.xsd15
-rw-r--r--schemas/types.xsd7
-rw-r--r--src/lib/Bcfg2/Client/Proxy.py2
-rw-r--r--src/lib/Bcfg2/Client/Tools/Chkconfig.py98
-rw-r--r--src/lib/Bcfg2/Client/Tools/DebInit.py97
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Directory.py6
-rw-r--r--src/lib/Bcfg2/Client/Tools/RcUpdate.py106
-rw-r--r--src/lib/Bcfg2/Client/Tools/__init__.py16
-rw-r--r--src/lib/Bcfg2/Options.py2
-rw-r--r--src/lib/Bcfg2/Reporting/Storage/DjangoORM.py3
-rw-r--r--src/lib/Bcfg2/Reporting/models.py32
-rw-r--r--src/lib/Bcfg2/Server/Plugin/helpers.py57
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/FileProbes.py6
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Packages/Yum.py4
-rwxr-xr-xsrc/sbin/bcfg2-info12
-rwxr-xr-xsrc/sbin/bcfg2-server6
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py6
-rwxr-xr-xtestsuite/install.sh2
-rw-r--r--tools/upgrade/1.3/README5
-rwxr-xr-xtools/upgrade/1.3/migrate_info.py6
-rwxr-xr-xtools/upgrade/1.3/migrate_probe_groups_to_db.py68
34 files changed, 470 insertions, 158 deletions
diff --git a/.travis.yml b/.travis.yml
index 655d9fad5..73b8a9594 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,6 @@ python:
- "2.5"
- "2.6"
- "2.7"
- - "3.2"
env:
- WITH_OPTIONAL_DEPS=yes
- WITH_OPTIONAL_DEPS=no
diff --git a/COPYRIGHT b/COPYRIGHT
index 347d9b236..fa01cb568 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -156,3 +156,6 @@ add themselves to this file. See LICENSE for the full license.
- Zach Lowry <zach@mcs.anl.gov> wrote Solaris support and general
hardening.
+
+- Michael Fenn <fennm@deshawresearch.com> fixed various small bugs
+ related to bcfg2 on CentOS 5
diff --git a/debian/bcfg2-server.init b/debian/bcfg2-server.init
index 04774c063..b1c3aba21 100755
--- a/debian/bcfg2-server.init
+++ b/debian/bcfg2-server.init
@@ -17,7 +17,8 @@
### END INIT INFO
# Include lsb functions
-. /lib/lsb/init-functions
+test -f "/lib/lsb/init-functions" && . /lib/lsb/init-functions # debian
+test -f "/etc/init.d/functions" && . /etc/init.d/functions # redhat
# Commonly used stuff
DAEMON=/usr/sbin/bcfg2-server
diff --git a/debian/bcfg2.cron.daily b/debian/bcfg2.cron.daily
index f2d1efb9f..b872887cb 100755
--- a/debian/bcfg2.cron.daily
+++ b/debian/bcfg2.cron.daily
@@ -10,4 +10,4 @@ else
echo "No bcfg2-cron command found"
exit 1
fi
-$BCFG2CRON --daily 2>&1 | logger -t bcfg2-cron -p daemon.info
+$BCFG2CRON --daily 2>&1 | logger -t bcfg2-cron -p daemon.info -i
diff --git a/debian/bcfg2.cron.hourly b/debian/bcfg2.cron.hourly
index 73aae7606..9f666e083 100755
--- a/debian/bcfg2.cron.hourly
+++ b/debian/bcfg2.cron.hourly
@@ -10,4 +10,4 @@ else
echo "No bcfg2-cron command found"
exit 1
fi
-$BCFG2CRON --hourly 2>&1 | logger -t bcfg2-cron -p daemon.info
+$BCFG2CRON --hourly 2>&1 | logger -t bcfg2-cron -p daemon.info -i
diff --git a/doc/development/plugins.txt b/doc/development/plugins.txt
index 3ca137159..e4f16b84d 100644
--- a/doc/development/plugins.txt
+++ b/doc/development/plugins.txt
@@ -215,4 +215,4 @@ See Also
--------
* :ref:`development-compat`
-* :ref:`development-utils
+* :ref:`development-utils`
diff --git a/doc/exts/xmlschema.py b/doc/exts/xmlschema.py
index 24cbf2e2d..c26aed81e 100644
--- a/doc/exts/xmlschema.py
+++ b/doc/exts/xmlschema.py
@@ -115,6 +115,7 @@ class _XMLDirective(Directive):
def run(self):
name = self.arguments[0]
env = self.state.document.settings.env
+ reporter = self.state.memo.reporter
ns_name = self.options.get('namespace')
try:
ns_uri = env.xmlschema_namespaces[ns_name]
@@ -129,8 +130,9 @@ class _XMLDirective(Directive):
except KeyError:
pass
else:
- env.app.error("No XML %s %s found" %
- (" or ".join(self.types), name))
+ reporter.error("No XML %s %s found" %
+ (" or ".join(self.types), name))
+ return []
documentor = XMLDocumentor(entity, env, self.state, name=name,
ns_uri=ns_uri,
include=self.process_include(),
@@ -172,6 +174,7 @@ class XMLDocumentor(object):
self.include = include
self.options = options
self.app = self.env.app
+ self.reporter = self.state.memo.reporter
if name is None:
self.ns_uri = ns_uri
@@ -312,7 +315,8 @@ class XMLDocumentor(object):
rv.extend(doc.document_complexType())
return rv
else:
- self.app.error("Unknown element type %s" % fqtype)
+ self.reporter.error("Unknown element type %s" % fqtype)
+ return []
else:
rv = []
typespec = self.entity.xpath("xs:complexType", namespaces=NSMAP)[0]
diff --git a/doc/help/troubleshooting.txt b/doc/help/troubleshooting.txt
index 5ae668c89..aa46bda2a 100644
--- a/doc/help/troubleshooting.txt
+++ b/doc/help/troubleshooting.txt
@@ -54,7 +54,7 @@ the debug level individually on a given plugin, e.g.::
bcfg2-admin xcmd Probes.toggle_debug
Finally, the File Activity Monitor has its own analogue to these two
-methods, for setting the debug level of the FAM:
+methods, for setting the debug level of the FAM::
bcfg2-admin xcmd Inotify.toggle_debug
bcfg2-admin xcmd Inotify.set_debug false
diff --git a/doc/installation/distributions.txt b/doc/installation/distributions.txt
index eccd6e723..9db111682 100644
--- a/doc/installation/distributions.txt
+++ b/doc/installation/distributions.txt
@@ -66,19 +66,7 @@ This way is not recommended on production systems. Only for testing.
Gentoo
======
-Early in July 2008, Bcfg2 was added to the Gentoo portage tree. So far
-it's still keyworded for all architectures, but we are actively working
-to get it marked as stable.
-
-If you don't use portage to install Bcfg2, you'll want to make sure you
-have all the prerequisites installed first. For a server, you'll need:
-
-* ``app-admin/gamin``
-* ``dev-python/lxml``
-
-Clients will need at least:
-
-* ``app-portage/gentoolkit``
+Bcfg2 can be installed via portage.
OS X
====
diff --git a/doc/server/encryption.txt b/doc/server/encryption.txt
index 28ca149f5..b657deb8c 100644
--- a/doc/server/encryption.txt
+++ b/doc/server/encryption.txt
@@ -12,7 +12,8 @@ Bcfg2 supports encrypting some data on the disk, which can help
protect sensitive data from other people who need access to the Bcfg2
repository but are perhaps not authorized to see all data. It
supports multiple passphrases, which can be used to enforce
-separations between teams, environments, etc.
+separations between teams, environments, etc. Use of the encryption
+feature requires M2Crypto 0.18 or newer.
.. note::
diff --git a/misc/bcfg2-selinux.spec b/misc/bcfg2-selinux.spec
index 4c05f4959..9c5262dfd 100644
--- a/misc/bcfg2-selinux.spec
+++ b/misc/bcfg2-selinux.spec
@@ -3,7 +3,7 @@
%global pythonversion %{py_ver}
%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
%{!?_initrddir: %global _initrddir %{_sysconfdir}/rc.d/init.d}
-%global selinux_policyver %(%{__sed} -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp || echo 0.0.0)
+%global selinux_policyver %(%{__sed} -e 's,.*selinux-policy-\\([^/]*\\)/.*,\\1,' /usr/share/selinux/devel/policyhelp 2>/dev/null || echo 0.0.0)
%global selinux_types %(%{__awk} '/^#[[:space:]]*SELINUXTYPE=/,/^[^#]/ { if ($3 == "-") printf "%s ", $2 }' /etc/selinux/config 2>/dev/null)
%global selinux_variants %([ -z "%{selinux_types}" ] && echo mls strict targeted || echo %{selinux_types})
diff --git a/misc/bcfg2.spec b/misc/bcfg2.spec
index 5feef1d74..7655752dd 100644
--- a/misc/bcfg2.spec
+++ b/misc/bcfg2.spec
@@ -100,11 +100,12 @@ Requires: python-ssl
%endif
Requires: python-lxml >= 1.2.1
%if "%{_vendor}" == "redhat"
-Requires: gamin-python
%endif
%if 0%{?suse_version}
+Requires: python-pyinotify
Requires: python-python-daemon
%else
+Requires: python-inotify
Requires: python-daemon
%endif
Requires: /usr/sbin/sendmail
@@ -190,6 +191,27 @@ Group: Documentation/HTML
%else
Group: Documentation
%endif
+%if 0%{?suse_version}
+BuildRequires: python-M2Crypto
+BuildRequires: python-Genshi
+BuildRequires: python-gamin
+BuildRequires: python-pyinotify
+BuildRequires: python-python-daemon
+BuildRequires: python-CherryPy >= 3
+%else
+BuildRequires: m2crypto
+BuildRequires: python-genshi
+BuildRequires: gamin-python
+BuildRequires: python-inotify
+BuildRequires: python-daemon
+%endif
+
+%if "%{_vendor}" == "redhat" && 0%{?rhel} < 6 && 0%{?fedora} == 0
+BuildRequires: python-ssl
+%else
+BuildRequires: python-cherrypy >= 3
+BuildRequires: python-mock
+%endif
%description doc
Bcfg2 helps system administrators produce a consistent, reproducible,
diff --git a/schemas/servicetype.xsd b/schemas/servicetype.xsd
index 4d5ac7c31..4c7e1b803 100644
--- a/schemas/servicetype.xsd
+++ b/schemas/servicetype.xsd
@@ -34,12 +34,21 @@
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
+ <xsd:attribute name="bootstatus" type="BootStatusEnum" default="off">
+ <xsd:annotation>
+ <xsd:documentation>
+ Whether the service should start at boot. The default value
+ corresponds to the value of the status attribute.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
<xsd:attribute name="status" type="StatusEnum" default="off">
<xsd:annotation>
<xsd:documentation>
- Whether the service should start at boot. If this is set to
- "ignore", then the boot-time status of the service will not
- be checked.
+ Whether the service should be on or off when the bcfg2 client
+ is run. This attribute may have different behavior depending
+ on the characteristics of the client tool. If set to "ignore",
+ then the status of the service will not be checked.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
diff --git a/schemas/types.xsd b/schemas/types.xsd
index fe9b5c7d4..81b1e3927 100644
--- a/schemas/types.xsd
+++ b/schemas/types.xsd
@@ -53,6 +53,13 @@
</xsd:restriction>
</xsd:simpleType>
+ <xsd:simpleType name='BootStatusEnum'>
+ <xsd:restriction base='xsd:string'>
+ <xsd:enumeration value='on'/>
+ <xsd:enumeration value='off'/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
<xsd:simpleType name='StatusEnum'>
<xsd:restriction base='xsd:string'>
<xsd:enumeration value='on'/>
diff --git a/src/lib/Bcfg2/Client/Proxy.py b/src/lib/Bcfg2/Client/Proxy.py
index eda3a7fce..fbf114de6 100644
--- a/src/lib/Bcfg2/Client/Proxy.py
+++ b/src/lib/Bcfg2/Client/Proxy.py
@@ -314,7 +314,7 @@ class XMLRPCTransport(xmlrpclib.Transport):
errcode = response.status
errmsg = response.reason
headers = response.msg
- except (socket.error, SSL_ERROR):
+ except (socket.error, SSL_ERROR, httplib.BadStatusLine):
err = sys.exc_info()[1]
raise ProxyError(xmlrpclib.ProtocolError(host + handler,
408,
diff --git a/src/lib/Bcfg2/Client/Tools/Chkconfig.py b/src/lib/Bcfg2/Client/Tools/Chkconfig.py
index c3dcf7796..0f5f32302 100644
--- a/src/lib/Bcfg2/Client/Tools/Chkconfig.py
+++ b/src/lib/Bcfg2/Client/Tools/Chkconfig.py
@@ -19,25 +19,22 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool):
def get_svc_command(self, service, action):
return "/sbin/service %s %s" % (service.get('name'), action)
- def VerifyService(self, entry, _):
- """Verify Service status for entry."""
- if entry.get('status') == 'ignore':
- return True
-
+ def verify_bootstatus(self, entry, bootstatus):
+ """Verify bootstatus for entry."""
rv = self.cmd.run("/sbin/chkconfig --list %s " % entry.get('name'))
if rv.success:
srvdata = rv.stdout.splitlines()[0].split()
else:
# service not installed
- entry.set('current_status', 'off')
+ entry.set('current_bootstatus', 'service not installed')
return False
if len(srvdata) == 2:
# This is an xinetd service
- if entry.get('status') == srvdata[1]:
+ if bootstatus == srvdata[1]:
return True
else:
- entry.set('current_status', srvdata[1])
+ entry.set('current_bootstatus', srvdata[1])
return False
try:
@@ -46,40 +43,73 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool):
except IndexError:
onlevels = []
- pstatus = self.check_service(entry)
- if entry.get('status') == 'on':
- status = (len(onlevels) > 0 and pstatus)
+ if bootstatus == 'on':
+ current_bootstatus = (len(onlevels) > 0)
else:
- status = (len(onlevels) == 0 and not pstatus)
+ current_bootstatus = (len(onlevels) == 0)
+ return current_bootstatus
+
+ def VerifyService(self, entry, _):
+ """Verify Service status for entry."""
+ entry.set('target_status', entry.get('status')) # for reporting
+ bootstatus = self.get_bootstatus(entry)
+ if bootstatus is None:
+ return True
+ current_bootstatus = self.verify_bootstatus(entry, bootstatus)
- if not status:
- if entry.get('status') == 'on':
- entry.set('current_status', 'off')
+ svcstatus = self.check_service(entry)
+ if entry.get('status') == 'on':
+ if svcstatus:
+ current_srvstatus = True
+ else:
+ current_srvstatus = False
+ elif entry.get('status') == 'off':
+ if svcstatus:
+ current_srvstatus = False
else:
- entry.set('current_status', 'on')
- return status
+ current_srvstatus = True
+ else:
+ # 'ignore' should verify
+ current_srvstatus = True
+
+ if svcstatus:
+ entry.set('current_status', 'on')
+ else:
+ entry.set('current_status', 'off')
+
+ return current_bootstatus and current_srvstatus
def InstallService(self, entry):
"""Install Service entry."""
- rcmd = "/sbin/chkconfig %s %s"
- self.cmd.run("/sbin/chkconfig --add %s" % (entry.attrib['name']))
+ self.cmd.run("/sbin/chkconfig --add %s" % (entry.get('name')))
self.logger.info("Installing Service %s" % (entry.get('name')))
- rv = True
- if (entry.get('status') == 'off' or
- self.setup["servicemode"] == "build"):
- rv &= self.cmd.run((rcmd + " --level 0123456") %
- (entry.get('name'),
- entry.get('status'))).success
- if entry.get("current_status") == "on" and \
- self.setup["servicemode"] != "disabled":
- rv &= self.stop_service(entry).success
+ bootstatus = entry.get('bootstatus')
+ if bootstatus is not None:
+ if bootstatus == 'on':
+ # make sure service is enabled on boot
+ bootcmd = '/sbin/chkconfig %s %s --level 0123456' % \
+ (entry.get('name'), entry.get('bootstatus'))
+ elif bootstatus == 'off':
+ # make sure service is disabled on boot
+ bootcmd = '/sbin/chkconfig %s %s' % (entry.get('name'),
+ entry.get('bootstatus'))
+ bootcmdrv = self.cmd.run(bootcmd).success
+ if self.setup['servicemode'] == 'disabled':
+ # 'disabled' means we don't attempt to modify running svcs
+ return bootcmdrv
+ buildmode = self.setup['servicemode'] == 'build'
+ if (entry.get('status') == 'on' and not buildmode) and \
+ entry.get('current_status') == 'off':
+ svccmdrv = self.start_service(entry)
+ elif (entry.get('status') == 'off' or buildmode) and \
+ entry.get('current_status') == 'on':
+ svccmdrv = self.stop_service(entry)
+ else:
+ svccmdrv = True # ignore status attribute
+ return bootcmdrv and svccmdrv
else:
- rv &= self.cmd.run(rcmd % (entry.get('name'),
- entry.get('status'))).success
- if entry.get("current_status") == "off" and \
- self.setup["servicemode"] != "disabled":
- rv &= self.start_service(entry).success
- return rv
+ # when bootstatus is 'None', status == 'ignore'
+ return True
def FindExtra(self):
"""Locate extra chkconfig Services."""
diff --git a/src/lib/Bcfg2/Client/Tools/DebInit.py b/src/lib/Bcfg2/Client/Tools/DebInit.py
index d916b1662..116d4f8b0 100644
--- a/src/lib/Bcfg2/Client/Tools/DebInit.py
+++ b/src/lib/Bcfg2/Client/Tools/DebInit.py
@@ -18,13 +18,8 @@ class DebInit(Bcfg2.Client.Tools.SvcTool):
svcre = \
re.compile(r'/etc/.*/(?P<action>[SK])(?P<sequence>\d+)(?P<name>\S+)')
- # implement entry (Verify|Install) ops
- def VerifyService(self, entry, _):
- """Verify Service status for entry."""
-
- if entry.get('status') == 'ignore':
- return True
-
+ def verify_bootstatus(self, entry, bootstatus):
+ """Verify bootstatus for entry."""
rawfiles = glob.glob("/etc/rc*.d/[SK]*%s" % (entry.get('name')))
files = []
@@ -54,9 +49,9 @@ class DebInit(Bcfg2.Client.Tools.SvcTool):
continue
if match.group('name') == entry.get('name'):
files.append(filename)
- if entry.get('status') == 'off':
+ if bootstatus == 'off':
if files:
- entry.set('current_status', 'on')
+ entry.set('current_bootstatus', 'on')
return False
else:
return True
@@ -72,12 +67,45 @@ class DebInit(Bcfg2.Client.Tools.SvcTool):
return False
return True
else:
- entry.set('current_status', 'off')
+ entry.set('current_bootstatus', 'off')
return False
+ def VerifyService(self, entry, _):
+ """Verify Service status for entry."""
+ entry.set('target_status', entry.get('status')) # for reporting
+ bootstatus = self.get_bootstatus(entry)
+ if bootstatus is None:
+ return True
+ current_bootstatus = self.verify_bootstatus(entry, bootstatus)
+
+ svcstatus = self.check_service(entry)
+ if entry.get('status') == 'on':
+ if svcstatus:
+ current_srvstatus = True
+ else:
+ current_srvstatus = False
+ elif entry.get('status') == 'off':
+ if svcstatus:
+ current_srvstatus = False
+ else:
+ current_srvstatus = True
+ else:
+ # 'ignore' should verify
+ current_srvstatus = True
+
+ if svcstatus:
+ entry.set('current_status', 'on')
+ else:
+ entry.set('current_status', 'off')
+
+ return current_bootstatus and current_srvstatus
+
def InstallService(self, entry):
- """Install Service for entry."""
+ """Install Service entry."""
self.logger.info("Installing Service %s" % (entry.get('name')))
+ bootstatus = entry.get('bootstatus')
+
+ # check if init script exists
try:
os.stat('/etc/init.d/%s' % entry.get('name'))
except OSError:
@@ -85,20 +113,41 @@ class DebInit(Bcfg2.Client.Tools.SvcTool):
entry.get('name'))
return False
- if entry.get('status') == 'off':
- self.cmd.run("/usr/sbin/invoke-rc.d %s stop" % (entry.get('name')))
- return self.cmd.run("/usr/sbin/update-rc.d -f %s remove" %
- entry.get('name')).success
+ if bootstatus is not None:
+ seqcmdrv = True
+ if bootstatus == 'on':
+ # make sure service is enabled on boot
+ bootcmd = '/usr/sbin/update-rc.d %s defaults' % \
+ entry.get('name')
+ if entry.get('sequence'):
+ seqcmd = '/usr/sbin/update-rc.d -f %s remove' % \
+ entry.get('name')
+ seqcmdrv = self.cmd.run(seqcmd)
+ start_sequence = int(entry.get('sequence'))
+ kill_sequence = 100 - start_sequence
+ bootcmd = '%s %d %d' % (bootcmd, start_sequence,
+ kill_sequence)
+ elif bootstatus == 'off':
+ # make sure service is disabled on boot
+ bootcmd = '/usr/sbin/update-rc.d -f %s remove' % \
+ entry.get('name')
+ bootcmdrv = self.cmd.run(bootcmd)
+ if self.setup['servicemode'] == 'disabled':
+ # 'disabled' means we don't attempt to modify running svcs
+ return bootcmdrv and seqcmdrv
+ buildmode = self.setup['servicemode'] == 'build'
+ if (entry.get('status') == 'on' and not buildmode) and \
+ entry.get('current_status') == 'off':
+ svccmdrv = self.start_service(entry)
+ elif (entry.get('status') == 'off' or buildmode) and \
+ entry.get('current_status') == 'on':
+ svccmdrv = self.stop_service(entry)
+ else:
+ svccmdrv = True # ignore status attribute
+ return bootcmdrv and svccmdrv and seqcmdrv
else:
- command = "/usr/sbin/update-rc.d %s defaults" % (entry.get('name'))
- if entry.get('sequence'):
- if not self.cmd.run("/usr/sbin/update-rc.d -f %s remove" %
- entry.get('name')).success:
- return False
- start_sequence = int(entry.get('sequence'))
- kill_sequence = 100 - start_sequence
- command = "%s %d %d" % (command, start_sequence, kill_sequence)
- return self.cmd.run(command).success
+ # when bootstatus is 'None', status == 'ignore'
+ return True
def FindExtra(self):
"""Find Extra Debian Service entries."""
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Directory.py b/src/lib/Bcfg2/Client/Tools/POSIX/Directory.py
index 9d0fe05e0..675a4461a 100644
--- a/src/lib/Bcfg2/Client/Tools/POSIX/Directory.py
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Directory.py
@@ -36,14 +36,14 @@ class POSIXDirectory(POSIXTool):
self.logger.info("POSIX: " + msg)
entry.set('qtext', entry.get('qtext', '') + '\n' + msg)
for extra in extras:
- Bcfg2.Client.XML.SubElement(entry, 'Prune', path=extra)
+ Bcfg2.Client.XML.SubElement(entry, 'Prune', name=extra)
except OSError:
prune = True
return POSIXTool.verify(self, entry, modlist) and prune
def install(self, entry):
- """Install device entries."""
+ """Install directory entries."""
fmode = self._exists(entry)
if fmode and not stat.S_ISDIR(fmode[stat.ST_MODE]):
@@ -67,7 +67,7 @@ class POSIXDirectory(POSIXTool):
if entry.get('prune', 'false') == 'true':
for pent in entry.findall('Prune'):
- pname = pent.get('path')
+ pname = pent.get('name')
try:
self.logger.debug("POSIX: Removing %s" % pname)
self._remove(pent)
diff --git a/src/lib/Bcfg2/Client/Tools/RcUpdate.py b/src/lib/Bcfg2/Client/Tools/RcUpdate.py
index 4b78581f7..d6329256e 100644
--- a/src/lib/Bcfg2/Client/Tools/RcUpdate.py
+++ b/src/lib/Bcfg2/Client/Tools/RcUpdate.py
@@ -21,21 +21,38 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool):
'-s']).stdout.splitlines()
if 'started' in line]
+ def get_default_svcs(self):
+ """Return a list of services in the 'default' runlevel."""
+ return [line.split()[0]
+ for line in self.cmd.run(['/sbin/rc-update',
+ 'show']).stdout.splitlines()
+ if 'default' in line]
+
+ def verify_bootstatus(self, entry, bootstatus):
+ """Verify bootstatus for entry."""
+ # get a list of all started services
+ allsrv = self.get_default_svcs()
+ # set current_bootstatus attribute
+ if entry.get('name') in allsrv:
+ entry.set('current_bootstatus', 'on')
+ else:
+ entry.set('current_bootstatus', 'off')
+ if bootstatus == 'on':
+ return entry.get('name') in allsrv
+ else:
+ return entry.get('name') not in allsrv
+
def VerifyService(self, entry, _):
"""
Verify Service status for entry.
Assumes we run in the "default" runlevel.
"""
- if entry.get('status') == 'ignore':
+ entry.set('target_status', entry.get('status')) # for reporting
+ bootstatus = self.get_bootstatus(entry)
+ if bootstatus is None:
return True
-
- # get a list of all started services
- allsrv = self.get_enabled_svcs()
-
- # check if service is enabled
- result = self.cmd.run(["/sbin/rc-update", "show", "default"]).stdout
- is_enabled = entry.get("name") in result
+ current_bootstatus = self.verify_bootstatus(entry, bootstatus)
# check if init script exists
try:
@@ -45,39 +62,56 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool):
entry.get('name'))
return False
- # check if service is enabled
- is_running = entry.get('name') in allsrv
-
- if entry.get('status') == 'on' and not (is_enabled and is_running):
- entry.set('current_status', 'off')
- return False
-
- elif entry.get('status') == 'off' and (is_enabled or is_running):
+ svcstatus = self.check_service(entry)
+ if entry.get('status') == 'on':
+ if svcstatus:
+ current_srvstatus = True
+ else:
+ current_srvstatus = False
+ elif entry.get('status') == 'off':
+ if svcstatus:
+ current_srvstatus = False
+ else:
+ current_srvstatus = True
+ else:
+ # 'ignore' should verify
+ current_srvstatus = True
+
+ if svcstatus:
entry.set('current_status', 'on')
- return False
+ else:
+ entry.set('current_status', 'off')
- return True
+ return current_bootstatus and current_srvstatus
def InstallService(self, entry):
- """
- Install Service entry
-
- """
+ """Install Service entry."""
self.logger.info('Installing Service %s' % entry.get('name'))
- if entry.get('status') == 'on':
- if entry.get('current_status') == 'off':
- self.start_service(entry)
- # make sure it's enabled
- cmd = '/sbin/rc-update add %s default'
- return self.cmd.run(cmd % entry.get('name')).success
- elif entry.get('status') == 'off':
- if entry.get('current_status') == 'on':
- self.stop_service(entry)
- # make sure it's disabled
- cmd = '/sbin/rc-update del %s default'
- return self.cmd.run(cmd % entry.get('name')).success
-
- return False
+ bootstatus = entry.get('bootstatus')
+ if bootstatus is not None:
+ if bootstatus == 'on':
+ # make sure service is enabled on boot
+ bootcmd = '/sbin/rc-update add %s default'
+ elif bootstatus == 'off':
+ # make sure service is disabled on boot
+ bootcmd = '/sbin/rc-update del %s default'
+ bootcmdrv = self.cmd.run(bootcmd % entry.get('name')).success
+ if self.setup['servicemode'] == 'disabled':
+ # 'disabled' means we don't attempt to modify running svcs
+ return bootcmdrv
+ buildmode = self.setup['servicemode'] == 'build'
+ if (entry.get('status') == 'on' and not buildmode) and \
+ entry.get('current_status') == 'off':
+ svccmdrv = self.start_service(entry)
+ elif (entry.get('status') == 'off' or buildmode) and \
+ entry.get('current_status') == 'on':
+ svccmdrv = self.stop_service(entry)
+ else:
+ svccmdrv = True # ignore status attribute
+ return bootcmdrv and svccmdrv
+ else:
+ # when bootstatus is 'None', status == 'ignore'
+ return True
def FindExtra(self):
"""Locate extra rc-update services."""
diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py
index 473d2bf09..885e22761 100644
--- a/src/lib/Bcfg2/Client/Tools/__init__.py
+++ b/src/lib/Bcfg2/Client/Tools/__init__.py
@@ -510,6 +510,22 @@ class SvcTool(Tool):
"""
return '/etc/init.d/%s %s' % (service.get('name'), action)
+ def get_bootstatus(self, service):
+ """ Return the bootstatus attribute if it exists.
+
+ :param service: The service entry
+ :type service: lxml.etree._Element
+ :returns: string or None - Value of bootstatus if it exists. If
+ bootstatus is unspecified and status is not *ignore*,
+ return value of status. If bootstatus is unspecified
+ and status is *ignore*, return None.
+ """
+ if service.get('bootstatus') is not None:
+ return service.get('bootstatus')
+ elif service.get('status') != 'ignore':
+ return service.get('status')
+ return None
+
def start_service(self, service):
""" Start a service.
diff --git a/src/lib/Bcfg2/Options.py b/src/lib/Bcfg2/Options.py
index 7e7232820..a6511f88e 100644
--- a/src/lib/Bcfg2/Options.py
+++ b/src/lib/Bcfg2/Options.py
@@ -573,7 +573,7 @@ SERVER_PASSWORD = \
SERVER_PROTOCOL = \
Option('Server Protocol',
default='xmlrpc/ssl',
- cf=('communication', 'procotol'))
+ cf=('communication', 'protocol'))
SERVER_BACKEND = \
Option('Server Backend',
default='best',
diff --git a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
index 3b2c0ccfa..aea5e9d4b 100644
--- a/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
+++ b/src/lib/Bcfg2/Reporting/Storage/DjangoORM.py
@@ -197,7 +197,8 @@ class DjangoORM(StorageBase):
def _import_Service(self, entry, state):
return self._import_default(entry, state,
defaults=dict(status='',
- current_status=''),
+ current_status='',
+ target_status=''),
mapping=dict(status='target_status'))
def _import_SEBoolean(self, entry, state):
diff --git a/src/lib/Bcfg2/Reporting/models.py b/src/lib/Bcfg2/Reporting/models.py
index 28c38e741..598e1c6ec 100644
--- a/src/lib/Bcfg2/Reporting/models.py
+++ b/src/lib/Bcfg2/Reporting/models.py
@@ -26,6 +26,8 @@ TYPE_CHOICES = (
(TYPE_EXTRA, 'Extra'),
)
+_our_backend = None
+
def convert_entry_type_to_id(type_name):
"""Convert a entry type to its entry id"""
@@ -49,7 +51,6 @@ def hash_entry(entry_dict):
return hash(cPickle.dumps(dataset))
-_our_backend = None
def _quote(value):
"""
Quote a string to use as a table name or column
@@ -59,7 +60,10 @@ def _quote(value):
"""
global _our_backend
if not _our_backend:
- _our_backend = backend.DatabaseOperations(connection)
+ try:
+ _our_backend = backend.DatabaseOperations(connection)
+ except TypeError:
+ _our_backend = backend.DatabaseOperations()
return _our_backend.quote_name(value)
@@ -113,7 +117,6 @@ class InteractionManager(models.Manager):
pass
return []
-
def recent(self, maxdate=None):
"""
Returns the most recent interactions for clients as of a date
@@ -133,7 +136,7 @@ class Interaction(models.Model):
timestamp = models.DateTimeField(db_index=True) # Timestamp for this record
state = models.CharField(max_length=32) # good/bad/modified/etc
repo_rev_code = models.CharField(max_length=64) # repo revision at time of interaction
- server = models.CharField(max_length=256) # Name of the server used for the interaction
+ server = models.CharField(max_length=256) # server used for interaction
good_count = models.IntegerField() # of good config-items
total_count = models.IntegerField() # of total config-items
bad_count = models.IntegerField(default=0)
@@ -230,7 +233,7 @@ class Interaction(models.Model):
rv = []
for entry in self.entry_types:
if entry == 'failures':
- continue
+ continue
rv.extend(getattr(self, entry).filter(state=TYPE_BAD))
return rv
@@ -238,7 +241,7 @@ class Interaction(models.Model):
rv = []
for entry in self.entry_types:
if entry == 'failures':
- continue
+ continue
rv.extend(getattr(self, entry).filter(state=TYPE_MODIFIED))
return rv
@@ -246,7 +249,7 @@ class Interaction(models.Model):
rv = []
for entry in self.entry_types:
if entry == 'failures':
- continue
+ continue
rv.extend(getattr(self, entry).filter(state=TYPE_EXTRA))
return rv
@@ -258,7 +261,8 @@ class Interaction(models.Model):
class Performance(models.Model):
"""Object representing performance data for any interaction."""
- interaction = models.ForeignKey(Interaction, related_name="performance_items")
+ interaction = models.ForeignKey(Interaction,
+ related_name="performance_items")
metric = models.CharField(max_length=128)
value = models.DecimalField(max_digits=32, decimal_places=16)
@@ -291,11 +295,11 @@ class Group(models.Model):
class Meta:
ordering = ('name',)
-
@staticmethod
def prune_orphans():
'''Prune unused groups'''
- Group.objects.filter(interaction__isnull=True, group__isnull=True).delete()
+ Group.objects.filter(interaction__isnull=True,
+ group__isnull=True).delete()
class Bundle(models.Model):
@@ -313,11 +317,11 @@ class Bundle(models.Model):
class Meta:
ordering = ('name',)
-
@staticmethod
def prune_orphans():
'''Prune unused bundles'''
- Bundle.objects.filter(interaction__isnull=True, group__isnull=True).delete()
+ Bundle.objects.filter(interaction__isnull=True,
+ group__isnull=True).delete()
# new interaction models
@@ -420,7 +424,7 @@ class BaseEntry(models.Model):
def prune_orphans(cls):
'''Remove unused entries'''
# yeat another sqlite hack
- cls_orphans = [x['id'] \
+ cls_orphans = [x['id']
for x in cls.objects.filter(interaction__isnull=True).values("id")]
i = 0
while i < len(cls_orphans):
@@ -695,7 +699,7 @@ class PathEntry(SuccessEntry):
acls = models.ManyToManyField(FileAcl)
detail_type = models.IntegerField(default=0,
- choices=DETAIL_CHOICES)
+ choices=DETAIL_CHOICES)
details = models.TextField(default='')
ENTRY_TYPE = r"Path"
diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py
index 48e748c47..1ee14d76e 100644
--- a/src/lib/Bcfg2/Server/Plugin/helpers.py
+++ b/src/lib/Bcfg2/Server/Plugin/helpers.py
@@ -30,6 +30,63 @@ try:
except ImportError:
HAS_DJANGO = False
+#: A dict containing default metadata for Path entries from bcfg2.conf
+DEFAULT_FILE_METADATA = Bcfg2.Options.OptionParser(
+ dict(configfile=Bcfg2.Options.CFILE,
+ owner=Bcfg2.Options.MDATA_OWNER,
+ group=Bcfg2.Options.MDATA_GROUP,
+ mode=Bcfg2.Options.MDATA_MODE,
+ secontext=Bcfg2.Options.MDATA_SECONTEXT,
+ important=Bcfg2.Options.MDATA_IMPORTANT,
+ paranoid=Bcfg2.Options.MDATA_PARANOID,
+ sensitive=Bcfg2.Options.MDATA_SENSITIVE))
+DEFAULT_FILE_METADATA.parse([Bcfg2.Options.CFILE.cmd, Bcfg2.Options.CFILE])
+del DEFAULT_FILE_METADATA['args']
+del DEFAULT_FILE_METADATA['configfile']
+
+LOGGER = logging.getLogger(__name__)
+
+#: a compiled regular expression for parsing info and :info files
+INFO_REGEX = re.compile(r'owner:\s*(?P<owner>\S+)|' +
+ r'group:\s*(?P<group>\S+)|' +
+ r'mode:\s*(?P<mode>\w+)|' +
+ r'secontext:\s*(?P<secontext>\S+)|' +
+ r'paranoid:\s*(?P<paranoid>\S+)|' +
+ r'sensitive:\s*(?P<sensitive>\S+)|' +
+ r'encoding:\s*(?P<encoding>\S+)|' +
+ r'important:\s*(?P<important>\S+)|' +
+ r'mtime:\s*(?P<mtime>\w+)')
+
+
+def bind_info(entry, metadata, infoxml=None, default=DEFAULT_FILE_METADATA):
+ """ 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`
+ """
+ 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 track_statistics(object): # pylint: disable=C0103
""" Decorator that tracks execution time for the given
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
index 3e464af49..7af69ec81 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
@@ -484,7 +484,9 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet,
return
elif hdlr.ignore(event, basename=self.path):
return
- elif action == 'changed':
+ # we only get here if event.filename in self.entries, so handle
+ # created event like changed
+ elif action == 'changed' or action == 'created':
self.entries[event.filename].handle_event(event)
return
elif action == 'deleted':
diff --git a/src/lib/Bcfg2/Server/Plugins/FileProbes.py b/src/lib/Bcfg2/Server/Plugins/FileProbes.py
index 316e4bc53..a3bba14f3 100644
--- a/src/lib/Bcfg2/Server/Plugins/FileProbes.py
+++ b/src/lib/Bcfg2/Server/Plugins/FileProbes.py
@@ -25,7 +25,11 @@ import sys
import pwd
import grp
import Bcfg2.Client.XML
-from Bcfg2.Compat import b64encode, oct_mode
+try:
+ from Bcfg2.Compat import b64encode, oct_mode
+except ImportError:
+ from base64 import b64encode
+ oct_mode = oct
path = "%s"
diff --git a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
index b544eb47e..a4b17f05a 100644
--- a/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
+++ b/src/lib/Bcfg2/Server/Plugins/Packages/Yum.py
@@ -1105,9 +1105,9 @@ class YumSource(Source):
self.packages['global'] = copy.deepcopy(sdata.pop())
except IndexError:
self.logger.error("Packages: No packages in repo")
+ self.packages['global'] = set()
while sdata:
- self.packages['global'] = \
- self.packages['global'].intersection(sdata.pop())
+ self.packages['global'].update(sdata.pop())
for key in self.packages:
if key == 'global':
diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info
index 4654359f7..853c98845 100755
--- a/src/sbin/bcfg2-info
+++ b/src/sbin/bcfg2-info
@@ -231,10 +231,14 @@ class InfoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore):
print("Refusing to write files outside of /tmp without -f "
"option")
return
- lxml.etree.ElementTree(self.BuildConfiguration(client)).write(
- ofile,
- encoding='UTF-8', xml_declaration=True,
- pretty_print=True)
+ try:
+ lxml.etree.ElementTree(self.BuildConfiguration(client)).write(
+ ofile,
+ encoding='UTF-8', xml_declaration=True,
+ pretty_print=True)
+ except IOError:
+ err = sys.exc_info()[1]
+ print("Failed to write File %s: %s" % (ofile, err))
else:
print(self._get_usage(self.do_build))
diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server
index 95413d6cf..beb19cef6 100755
--- a/src/sbin/bcfg2-server
+++ b/src/sbin/bcfg2-server
@@ -37,8 +37,8 @@ def main():
coremodule = backends[setup['backend']]
try:
- Core = getattr(__import__("Bcfg2.Server.%s" % coremodule).Server,
- coremodule).Core
+ corecls = getattr(__import__("Bcfg2.Server.%s" % coremodule).Server,
+ coremodule).Core
except ImportError:
err = sys.exc_info()[1]
print("Unable to import %s server core: %s" % (setup['backend'], err))
@@ -49,7 +49,7 @@ def main():
raise
try:
- core = Core()
+ core = corecls(setup)
core.run()
except CoreInitError:
msg = sys.exc_info()[1]
diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py
index d9431dc63..d14696b68 100644
--- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py
+++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestDirectory.py
@@ -70,7 +70,7 @@ class TestPOSIXDirectory(TestPOSIXTool):
expected = [os.path.join(entry.get("name"), e)
for e in entries
if os.path.join(entry.get("name"), e) not in modlist]
- actual = [e.get("path") for e in entry.findall("Prune")]
+ actual = [e.get("name") for e in entry.findall("Prune")]
self.assertItemsEqual(expected, actual)
mock_verify.reset_mock()
@@ -137,7 +137,7 @@ class TestPOSIXDirectory(TestPOSIXTool):
entry.set("prune", "true")
prune = ["/test/foo/bar/prune1", "/test/foo/bar/prune2"]
for path in prune:
- lxml.etree.SubElement(entry, "Prune", path=path)
+ lxml.etree.SubElement(entry, "Prune", name=path)
reset()
mock_install.return_value = True
@@ -145,6 +145,6 @@ class TestPOSIXDirectory(TestPOSIXTool):
self.assertTrue(ptool.install(entry))
ptool._exists.assert_called_with(entry)
mock_install.assert_called_with(ptool, entry)
- self.assertItemsEqual([c[0][0].get("path")
+ self.assertItemsEqual([c[0][0].get("name")
for c in ptool._remove.call_args_list],
prune)
diff --git a/testsuite/install.sh b/testsuite/install.sh
index 1e0acbad6..43a6057c0 100755
--- a/testsuite/install.sh
+++ b/testsuite/install.sh
@@ -15,7 +15,7 @@ if [[ "$WITH_OPTIONAL_DEPS" == "yes" ]]; then
if [[ ${PYVER:0:1} == "2" ]]; then
# django supports py3k, but South doesn't, and the django bits
# in bcfg2 require South
- pip install cheetah 'django<1.5' South M2Crypto
+ pip install cheetah 'django<1.5' 'South<0.8' M2Crypto
fi
else
# python < 2.6 requires M2Crypto for SSL communication, not just
diff --git a/tools/upgrade/1.3/README b/tools/upgrade/1.3/README
index 2831d8f00..1a919f869 100644
--- a/tools/upgrade/1.3/README
+++ b/tools/upgrade/1.3/README
@@ -19,3 +19,8 @@ migrate_dbstats.py
migrate_perms_to_mode.py
- Convert perms attribute to mode (note that if you have info/:info
files, you should run migrate_info.py first)
+
+migrate_probe_groups_to_db.py
+ - Migrate Probe host and group data from XML to DB backend for Metadata
+ and Probe plugins. Does not migrate individual probe return data. Assumes
+ migration to BOTH Metadata and Probe to database backends.
diff --git a/tools/upgrade/1.3/migrate_info.py b/tools/upgrade/1.3/migrate_info.py
index e72599daf..3ccbf0285 100755
--- a/tools/upgrade/1.3/migrate_info.py
+++ b/tools/upgrade/1.3/migrate_info.py
@@ -1,12 +1,16 @@
#!/usr/bin/env python
import os
+import re
import sys
import lxml.etree
import Bcfg2.Options
from Bcfg2.Server.Plugin import INFO_REGEX
+PERMS_REGEX = re.compile(r'perms:\s*(?P<perms>\w+)')
+
+
def convert(info_file):
info_xml = os.path.join(os.path.dirname(info_file), "info.xml")
if os.path.exists(info_xml):
@@ -16,7 +20,7 @@ def convert(info_file):
fileinfo = lxml.etree.Element("FileInfo")
info = lxml.etree.SubElement(fileinfo, "Info")
for line in open(info_file).readlines():
- match = INFO_REGEX.match(line)
+ match = INFO_REGEX.match(line) or PERMS_REGEX.match(line)
if match:
mgd = match.groupdict()
for key, value in list(mgd.items()):
diff --git a/tools/upgrade/1.3/migrate_probe_groups_to_db.py b/tools/upgrade/1.3/migrate_probe_groups_to_db.py
new file mode 100755
index 000000000..73339e787
--- /dev/null
+++ b/tools/upgrade/1.3/migrate_probe_groups_to_db.py
@@ -0,0 +1,68 @@
+#!/bin/env python
+""" Migrate Probe host and group data from XML to DB backend for Metadata
+and Probe plugins. Does not migrate individual probe return data. Assumes
+migration to BOTH Metadata and Probe to database backends. """
+
+import os
+os.environ['DJANGO_SETTINGS_MODULE'] = 'Bcfg2.settings'
+
+import lxml.etree
+import sys
+import Bcfg2.Options
+
+from Bcfg2.Server.Plugins.Metadata import MetadataClientModel
+from Bcfg2.Server.Plugins.Probes import ProbesGroupsModel
+
+def migrate(xclient):
+ """ Helper to do the migration given a <Client/> XML element """
+ client_name = xclient.get('name')
+ try:
+ try:
+ client = MetadataClientModel.objects.get(hostname=client_name)
+ except MetadataClientModel.DoesNotExist:
+ client = MetadataClientModel(hostname=client_name)
+ client.save()
+ except:
+ print("Failed to migrate client %s" % (client))
+ return False
+
+ try:
+ cgroups = []
+ for xgroup in xclient.findall('Group'):
+ group_name = xgroup.get('name')
+ cgroups.append(group_name)
+ try:
+ group = ProbesGroupsModel.objects.get(hostname=client_name, group=group_name)
+ except ProbesGroupsModel.DoesNotExist:
+ group = ProbesGroupsModel(hostname=client_name, group=group_name)
+ group.save()
+
+ ProbesGroupsModel.objects.filter(
+ hostname=client.hostname).exclude(
+ group__in=cgroups).delete()
+
+ except:
+ print("Failed to migrate groups")
+ return False
+ return True
+
+def main():
+ """ Main """
+ opts = dict(repo=Bcfg2.Options.SERVER_REPOSITORY)
+ setup = Bcfg2.Options.OptionParser(opts)
+ setup.parse(sys.argv[1:])
+
+ probefile = os.path.join(setup['repo'], 'Probes', "probed.xml")
+
+ try:
+ xdata = lxml.etree.parse(probefile)
+ except lxml.etree.XMLSyntaxError:
+ err = sys.exc_info()[1]
+ print("Could not parse %s, skipping: %s" % (probefile, err))
+
+ for xclient in xdata.findall('Client'):
+ print "Migrating Metadata and Probe groups for %s" % xclient.get('name')
+ migrate(xclient)
+
+if __name__ == '__main__':
+ sys.exit(main())