diff --git a/.gitattributes b/.gitattributes
index fe2f299fa150f3fcdcba8518d10f075886f06f98..045dc8ec87e73a538079e68f551e6d9354992441 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -2648,6 +2648,9 @@ LCS/PyCommon/CMakeLists.txt -text
 LCS/PyCommon/__init__.py -text
 LCS/PyCommon/datetimeutils.py -text
 LCS/PyCommon/factory.py -text
+LCS/PyCommon/test/python-coverage.sh eol=lf
+LCS/PyCommon/test/t_dbcredentials.run eol=lf
+LCS/PyCommon/test/t_dbcredentials.sh eol=lf
 LCS/PyCommon/util.py -text
 LCS/Tools/src/checkcomp.py -text
 LCS/Tools/src/countalllines -text
diff --git a/LCS/Messaging/python/messaging/messagebus.py b/LCS/Messaging/python/messaging/messagebus.py
index b8065dd1f210d7ade83942f0b1ec06dbbebecfa6..a367294f7a43cc41e6fd15c3be99ca90f7a2e123 100644
--- a/LCS/Messaging/python/messaging/messagebus.py
+++ b/LCS/Messaging/python/messaging/messagebus.py
@@ -37,7 +37,7 @@ logger = logging.getLogger(__name__)
 # Default settings for often used parameters.
 DEFAULT_ADDRESS_OPTIONS = {'create': 'never'}
 DEFAULT_BROKER = "localhost:5672"
-DEFAULT_BROKER_OPTIONS = {}
+DEFAULT_BROKER_OPTIONS = {'reconnect': True}
 DEFAULT_RECEIVER_CAPACITY = 1
 DEFAULT_TIMEOUT = 5
 
@@ -58,19 +58,20 @@ class FromBus(object):
     but that of __new__().
     """
 
-    def __init__(self, address, options=None, broker=None):
+    def __init__(self, address, options=None, broker=None, broker_options=None):
         """
         Initializer.
         :param address: valid Qpid address
         :param options: valid Qpid address options, e.g. {'create': 'never'}
         :param broker: valid Qpid broker URL, e.g. "localhost:5672"
+        :param broker_options: valid Qpid broker options, e.g. {'reconnect': True}
         """
         self.address = address
         self.options = options if options else DEFAULT_ADDRESS_OPTIONS
         self.broker = broker if broker else DEFAULT_BROKER
+        self.broker_options = broker_options if broker_options else DEFAULT_BROKER_OPTIONS
 
-        self.connection = qpid.messaging.Connection(self.broker,
-                                                    **DEFAULT_BROKER_OPTIONS)
+        self.connection = qpid.messaging.Connection(self.broker, **self.broker_options)
         self.session = None
         self.opened=0
 
@@ -246,19 +247,20 @@ class ToBus(object):
     but that of __new__().
     """
 
-    def __init__(self, address, options=None, broker=None):
+    def __init__(self, address, options=None, broker=None, broker_options=None):
         """
         Initializer.
         :param address: valid Qpid address
         :param options: valid Qpid address options, e.g. {'create': 'never'}
         :param broker: valid Qpid broker URL, e.g. "localhost:5672"
+        :param broker_options: valid Qpid broker options, e.g. {'reconnect': True}
         """
         self.address = address
         self.options = options if options else DEFAULT_ADDRESS_OPTIONS
         self.broker = broker if broker else DEFAULT_BROKER
+        self.broker_options = broker_options if broker_options else DEFAULT_BROKER_OPTIONS
 
-        self.connection = qpid.messaging.Connection(self.broker,
-                                                    **DEFAULT_BROKER_OPTIONS)
+        self.connection = qpid.messaging.Connection(self.broker, **self.broker_options)
         self.session = None
         self.opened = 0
 
diff --git a/LCS/Messaging/python/messaging/test/t_RPC.run b/LCS/Messaging/python/messaging/test/t_RPC.run
index ff5140cce0cdcbf9b608287e46524efacebd793a..78025a096a250aae7211dab6c133d46597065e22 100755
--- a/LCS/Messaging/python/messaging/test/t_RPC.run
+++ b/LCS/Messaging/python/messaging/test/t_RPC.run
@@ -10,29 +10,5 @@ queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16)
 qpid-config add exchange topic $queue
 
 # Run the unit test
-# either with or without code coverage measurements,
-# depending wheter coverage has been installed
-
-if type "coverage" > /dev/null; then
-    #run test using python coverage tool
-
-    #erase previous results
-    coverage erase
-
-    #setup coverage config file
-    printf "[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n" > .coveragerc
-
-    coverage run --branch --include=*Messaging/python* t_RPC.py $queue
-    RESULT=$?
-    if [ $RESULT -eq 0 ]; then
-        echo " *** Code coverage results *** "
-        coverage report -m
-        echo " *** End coverage results *** "
-    fi
-    exit $RESULT
-else
-    #coverage not available
-    echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests"
-    #run plain test script
-    python t_RPC.py $queue
-fi
+source python-coverage.sh
+python_coverage_test "Messaging/python"  t_RPC.py $queue
diff --git a/LCS/Messaging/python/messaging/test/t_messagebus.py b/LCS/Messaging/python/messaging/test/t_messagebus.py
index 68a454f9cfc404ecbb92ed2390a358c10678155a..c4a70a0d66dc1df2b6f4930824e659dcf8320333 100644
--- a/LCS/Messaging/python/messaging/test/t_messagebus.py
+++ b/LCS/Messaging/python/messaging/test/t_messagebus.py
@@ -53,7 +53,7 @@ class FromBusInitFailed(unittest.TestCase):
         regexp = re.escape(self.error)
         regexp += '.*' + 'No address associated with hostname'
         with self.assertRaisesRegexp(MessageBusError, regexp):
-            with FromBus(QUEUE, broker="foo.bar"):
+            with FromBus(QUEUE, broker="foo.bar", broker_options={'reconnect': False}):
                 pass
 
     def test_connection_refused(self):
@@ -62,7 +62,7 @@ class FromBusInitFailed(unittest.TestCase):
         """
         regexp = re.escape(self.error) + '.*' + 'Connection refused'
         with self.assertRaisesRegexp(MessageBusError, regexp):
-            with FromBus("fake" + QUEUE, broker="localhost:4"):
+            with FromBus("fake" + QUEUE, broker="localhost:4", broker_options={'reconnect': False}):
                 pass
 
 
@@ -164,7 +164,7 @@ class ToBusInitFailed(unittest.TestCase):
         regexp = re.escape(self.error)
         regexp += '.*' + 'No address associated with hostname'
         with self.assertRaisesRegexp(MessageBusError, regexp):
-            with ToBus(QUEUE, broker="foo.bar"):
+            with ToBus(QUEUE, broker="foo.bar",  broker_options={'reconnect': False}):
                 pass
 
     def test_connection_refused(self):
@@ -173,7 +173,7 @@ class ToBusInitFailed(unittest.TestCase):
         """
         regexp = re.escape(self.error) + '.*' + 'Connection refused'
         with self.assertRaisesRegexp(MessageBusError, regexp):
-            with ToBus(QUEUE, broker="localhost:4"):
+            with ToBus(QUEUE, broker="localhost:4", broker_options={'reconnect': False}):
                 pass
 
 
diff --git a/LCS/Messaging/python/messaging/test/t_messagebus.run b/LCS/Messaging/python/messaging/test/t_messagebus.run
index 4d4b2a3d7fe5e11397e7b087c275402acbf068d1..a64fd56ccd2c11c40b771f6cfb90ebda604483ab 100755
--- a/LCS/Messaging/python/messaging/test/t_messagebus.run
+++ b/LCS/Messaging/python/messaging/test/t_messagebus.run
@@ -10,29 +10,5 @@ queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16)
 qpid-config add queue $queue
 
 # Run the unit test
-# either with or without code coverage measurements,
-# depending wheter coverage has been installed
-
-if type "coverage" > /dev/null; then
-    #run test using python coverage tool
-
-    #erase previous results
-    coverage erase
-
-    #setup coverage config file
-    printf "[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n" > .coveragerc
-
-    coverage run --branch --include=*Messaging/python* t_messagebus.py $queue
-    RESULT=$?
-    if [ $RESULT -eq 0 ]; then
-        echo " *** Code coverage results *** "
-        coverage report -m
-        echo " *** End coverage results *** "
-    fi
-    exit $RESULT
-else
-    #coverage not available
-    echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests"
-    #run plain test script
-    python t_messagebus.py $queue
-fi
+source python-coverage.sh
+python_coverage_test "Messaging/python" t_messagebus.py $queue
diff --git a/LCS/Messaging/python/messaging/test/t_messages.run b/LCS/Messaging/python/messaging/test/t_messages.run
index 1cc5f37320d40620e07e8b02f9d9592068850014..2d82773f1b7a6a80b89bbd455a43c7757aeb2fec 100755
--- a/LCS/Messaging/python/messaging/test/t_messages.run
+++ b/LCS/Messaging/python/messaging/test/t_messages.run
@@ -1,29 +1,5 @@
 #!/bin/bash -e
 
 # Run the unit test
-# either with or without code coverage measurements,
-# depending wheter coverage has been installed
-
-if type "coverage" > /dev/null; then
-    #run test using python coverage tool
-
-    #erase previous results
-    coverage erase
-
-    #setup coverage config file
-    printf "[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n" > .coveragerc
-
-    coverage run --branch --include=*Messaging/python* t_messages.py
-    RESULT=$?
-    if [ $RESULT -eq 0 ]; then
-        echo " *** Code coverage results *** "
-        coverage report -m
-        echo " *** End coverage results *** "
-    fi
-    exit $RESULT
-else
-    #coverage not available
-    echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests"
-    #run plain test script
-    python t_messages.py
-fi
+source python-coverage.sh
+python_coverage_test "Messaging/python" t_messages.py
diff --git a/LCS/Messaging/python/messaging/test/t_service_message_handler.run b/LCS/Messaging/python/messaging/test/t_service_message_handler.run
index 67ad6c38702db4a0169108c309572f22434f4109..0cbe002228ef9b283cc734f9b6f373cb62635de2 100755
--- a/LCS/Messaging/python/messaging/test/t_service_message_handler.run
+++ b/LCS/Messaging/python/messaging/test/t_service_message_handler.run
@@ -10,29 +10,5 @@ queue=$(< /dev/urandom tr -dc [:alnum:] | head -c16)
 qpid-config add exchange topic $queue
 
 # Run the unit test
-# either with or without code coverage measurements,
-# depending wheter coverage has been installed
-
-if type "coverage" > /dev/null; then
-    #run test using python coverage tool
-
-    #erase previous results
-    coverage erase
-
-    #setup coverage config file
-    printf "[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n" > .coveragerc
-
-    coverage run --branch --include=*Messaging/python* t_service_message_handler.py $queue
-    RESULT=$?
-    if [ $RESULT -eq 0 ]; then
-        echo " *** Code coverage results *** "
-        coverage report -m
-        echo " *** End coverage results *** "
-    fi
-    exit $RESULT
-else
-    #coverage not available
-    echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests"
-    #run plain test script
-    python t_service_message_handler.py $queue
-fi
+source python-coverage.sh
+python_coverage_test "Messaging/python" t_service_message_handler.py $queue
diff --git a/LCS/PyCommon/CMakeLists.txt b/LCS/PyCommon/CMakeLists.txt
index d7f694b23785b590d5b3fa529eadf0eb48ff3b6b..d8c88f0a3a0a9b6a42efab6b3e76e33c7d6ac3bf 100644
--- a/LCS/PyCommon/CMakeLists.txt
+++ b/LCS/PyCommon/CMakeLists.txt
@@ -5,9 +5,10 @@ lofar_package(PyCommon 1.0)
 lofar_find_package(Python 2.6 REQUIRED)
 include(PythonInstall)
 
-#add_subdirectory(test)
+add_subdirectory(test)
 
 set(_py_files
+  dbcredentials.py
   factory.py
   util.py
   datetimeutils.py)
diff --git a/LCS/PyCommon/dbcredentials.py b/LCS/PyCommon/dbcredentials.py
new file mode 100644
index 0000000000000000000000000000000000000000..666a0879b1a2e7e401f3b5169986594511a97c87
--- /dev/null
+++ b/LCS/PyCommon/dbcredentials.py
@@ -0,0 +1,257 @@
+#!/usr/bin/python
+
+# Copyright (C) 2012-2015    ASTRON (Netherlands Institute for Radio Astronomy)
+# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
+#
+# This file is part of the LOFAR software suite.
+# The LOFAR software suite is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# The LOFAR software suite is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+
+# $Id$
+
+from glob import glob
+import os
+import pwd
+from ConfigParser import SafeConfigParser, NoSectionError, DuplicateSectionError
+from optparse import OptionGroup
+
+__all__ = ["Credentials", "DBCredentials", "options_group", "parse_options"]
+
+# obtain the environment, and add USER and HOME if needed (since supervisord does not)
+environ = os.environ
+user_info = pwd.getpwuid(os.getuid())
+environ.setdefault("HOME", user_info.pw_dir)
+environ.setdefault("USER", user_info.pw_name)
+
+
+def findfiles(pattern):
+  """ Returns a list of files matched by `pattern'.
+      The pattern can include environment variables using the
+      {VAR} notation.
+  """
+  try:
+    return glob(pattern.format(**environ))
+  except KeyError:
+    return []
+
+
+class Credentials:
+  def __init__(self):
+    # Flavour of database (postgres, mysql, oracle, sqlite)
+    self.type = "postgres"
+
+    # Connection information (port 0 = use default)
+    self.host = "localhost"
+    self.port = 0
+
+    # Authentication
+    self.user = environ["USER"]
+    self.password = ""
+
+    # Database selection
+    self.database = ""
+
+  def __str__(self):
+    return "type={type} addr={host}:{port} auth={user}:{password} db={database}".format(**self.__dict__)
+
+  def pg_connect_options(self):
+    """
+      Returns a dict of options to provide to PyGreSQL's pg.connect function. Use:
+
+      conn = pg.connect(**dbcreds.pg_connect_options())
+    """
+    return {
+      "host": self.host,
+      "port": self.port or -1,
+
+      "user": self.user,
+      "passwd": self.password,
+
+      "dbname": self.database,
+    }
+
+
+class DBCredentials:
+  def __init__(self, filepatterns=None):
+    """
+      Read database credentials from all configuration files matched by any of the patterns.
+
+      By default, the following files are read:
+
+        $LOFARROOT/etc/dbcredentials/*.ini
+        ~/.lofar/dbcredentials/*.ini
+
+      The configuration files allow for any number of database sections:
+
+        [database:OTDB]
+        type = postgres     # postgres, mysql, oracle, sqlite
+        host = localhost
+        port = 0            # 0 = use default port
+        user = paulus
+        password = boskabouter
+        database = LOFAR_4
+
+      These database credentials can subsequently be queried under their
+      symbolic name ("OTDB" in the example).
+    """
+    if filepatterns is None:
+      filepatterns = [
+        "{LOFARROOT}/etc/dbcredentials/*.ini",
+        "{HOME}/.lofar/dbcredentials/*.ini",
+        ]
+
+    self.config = SafeConfigParser()
+
+    self.files = sum([findfiles(p) for p in filepatterns],[])
+    self.config.read(self.files)
+
+
+  def get(self, database):
+    """
+      Return credentials for a given database.
+    """
+    # create default credentials
+    creds = Credentials()
+
+    # read configuration
+    try:
+      d = dict(self.config.items(self._section(database)))
+    except NoSectionError:
+      return creds
+
+    # parse and convert config information
+    if "host" in d:     creds.host = d["host"]
+    if "port" in d:     creds.port = int(d["port"] or 0)
+
+    if "user" in d:     creds.user = d["user"]
+    if "password" in d: creds.password = d["password"]
+
+    if "database" in d: creds.database = d["database"]
+
+    return creds
+
+
+  def set(self, database, credentials):
+    """
+      Add or overwrite credentials for a given database.
+    """
+    section = self._section(database)
+
+    # create section if needed
+    try:
+      self.config.add_section(section)
+    except DuplicateSectionError:
+      pass
+
+    # set or override credentials
+    self.config.set(section, "type", credentials.type)
+    self.config.set(section, "host", credentials.host)
+    self.config.set(section, "port", str(credentials.port))
+    self.config.set(section, "user", credentials.user)
+    self.config.set(section, "password", credentials.password)
+    self.config.set(section, "database", credentials.database)
+
+  def list(self):
+    """
+      Return a list of databases for which credentials are available.
+    """
+    sections = self.config.sections()
+    return [s[9:] for s in sections if s.startswith("database:")]
+
+
+  def _section(self, database):
+    return "database:%s" % (database,)
+
+
+def options_group(parser):
+  """
+    Return an optparse.OptionGroup containing command-line parameters
+    for database connections and authentication.
+  """
+  group = OptionGroup(parser, "Database Credentials")
+  group.add_option("-D", "--database", dest="dbName", type="string", default="",
+                   help="Name of the database")
+  group.add_option("-H", "--host", dest="dbHost", type="string", default="",
+                   help="Hostname of the database server")
+  group.add_option("-p", "--port", dest="dbPort", type="string", default="",
+                   help="Port number of the database server")
+  group.add_option("-U", "--user", dest="dbUser", type="string", default="",
+                   help="User of the database server")
+  group.add_option("-P", "--password", dest="dbPassword", type="string", default="",
+                   help="Password of the database server")
+  group.add_option("-C", "--dbcredentials", dest="dbcredentials", type="string", default="",
+                   help="Name of database credential set to use")
+
+  return group
+
+
+def parse_options(options, filepatterns=None):
+  """
+    Parses command-line parameters provided through options_group()
+    and returns a credentials dictionary.
+
+    `filepatterns' can be used to override the patterns used to find configuration
+    files.
+  """
+
+  dbc = DBCredentials(filepatterns)
+
+  # get default values
+  creds = dbc.get(options.dbcredentials)
+
+  # process supplied overrides
+  if options.dbHost:     creds.host     = options.dbHost
+  if options.dbPort:     creds.port     = options.dbPort
+  if options.dbUser:     creds.user     = options.dbUser
+  if options.dbPassword: creds.password = options.dbPassword
+  if options.dbName:     creds.database = options.dbName
+
+  return creds
+
+
+if __name__ == "__main__":
+  import sys
+  from optparse import OptionParser
+
+  parser = OptionParser("%prog [options]")
+  parser.add_option("-D", "--database", dest="database", type="string", default="",
+                    help="Name of the database")
+  parser.add_option("-L", "--list", dest="list", action="store_true", default=False,
+                    help="List known databases")
+  parser.add_option("-F", "--files", dest="files", action="store_true", default=False,
+                    help="List names of parsed configuration files")
+  (options, args) = parser.parse_args()
+
+  if not options.database and not options.list and not options.files:
+    print "Missing database name"
+    parser.print_help()
+    sys.exit(1)
+
+  dbc = DBCredentials()
+
+  if options.files:
+    """ Print list of configuration files that we've read. """
+    if dbc.files:
+      print "\n".join(dbc.files)
+    sys.exit(0)
+
+  if options.list:
+    """ Print list of databases. """
+    databases = dbc.list()
+    if databases:
+      print "\n".join(databases)
+    sys.exit(0)
+
+  """ Print credentials of a specific database. """
+  print str(dbc.get(options.database))
+
diff --git a/LCS/PyCommon/test/CMakeLists.txt b/LCS/PyCommon/test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a2abf73a98a57ed76555c5beb9d870804ff264b8
--- /dev/null
+++ b/LCS/PyCommon/test/CMakeLists.txt
@@ -0,0 +1,9 @@
+# $Id$
+
+include(LofarCTest)
+
+file(COPY
+  ${CMAKE_CURRENT_SOURCE_DIR}/python-coverage.sh
+  DESTINATION ${CMAKE_BINARY_DIR}/bin)
+
+lofar_add_test(t_dbcredentials)
diff --git a/LCS/PyCommon/test/python-coverage.sh b/LCS/PyCommon/test/python-coverage.sh
new file mode 100755
index 0000000000000000000000000000000000000000..87452ffe0b30e7ae7f362e12f8f6f2523107ef41
--- /dev/null
+++ b/LCS/PyCommon/test/python-coverage.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+# Default lines to exclude in python-coverage
+COVERAGE_EXCLUDE_LINES="[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n"
+
+# Determine python-coverage executable
+if type "coverage" >& /dev/null; then
+  COVERAGE=coverage
+elif type "python-coverage" >& /dev/null; then
+  COVERAGE=python-coverage
+else
+  COVERAGE=""
+fi
+
+#
+# Run a python test under python-coverage (if available).
+#
+# Usage:
+#
+#   python_coverage_test module mytest.py [testarg1 testarg2 ...]
+#
+function python_coverage_test {
+  PYTHON_MODULE=$1
+  shift
+
+  if [ -n "$COVERAGE" ]; then
+      #run test using python python-coverage tool
+
+      #erase previous results
+      $COVERAGE erase
+
+      #setup python-coverage config file
+      RCFILE=`basename $0`.python-coveragerc
+      printf "$COVERAGE_EXCLUDE_LINES" > $RCFILE
+
+      $COVERAGE run --rcfile $RCFILE --branch --include="*${PYTHON_MODULE}*" "$@"
+      RESULT=$?
+      if [ $RESULT -eq 0 ]; then
+          echo " *** Code python-coverage results *** "
+          $COVERAGE report -m
+          echo " *** End python-coverage results *** "
+      fi
+      exit $RESULT
+  else
+      #python-coverage not available
+      echo "Please run: 'pip install python-coverage' to enable code coverage reporting of the unit tests"
+      #run plain test script
+      python "$@"
+  fi
+}
+
diff --git a/LCS/PyCommon/test/t_dbcredentials.py b/LCS/PyCommon/test/t_dbcredentials.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5e8db69c457c5cabdec33f79c5e48b486ec74d3
--- /dev/null
+++ b/LCS/PyCommon/test/t_dbcredentials.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+import unittest
+import tempfile
+from lofar.common.dbcredentials import *
+
+def setUpModule():
+  pass
+
+def tearDownModule():
+  pass
+
+class TestCredentials(unittest.TestCase):
+  def test_default_values(self):
+    c = Credentials()
+
+    self.assertEqual(c.type, "postgres")
+    self.assertEqual(c.host, "localhost")
+    self.assertEqual(c.port, 0)
+    #self.assertEqual(c.user, "")
+    self.assertEqual(c.password, "")
+    self.assertEqual(c.database, "")
+
+  def test_pg_connect_options(self):
+    c = Credentials()
+
+    self.assertEqual(
+      c.pg_connect_options(),
+      { "host": "localhost",
+        "port": -1,
+        "user": c.user,
+        "passwd": "",
+        "dbname": "",
+      })
+
+
+class TestDBCredentials(unittest.TestCase):
+  def test_set_get(self):
+    dbc = DBCredentials(filepatterns=[])
+
+    c_in = Credentials()
+    c_in.host = "example.com"
+    c_in.port = 1234
+    c_in.user = "root"
+    c_in.password = "secret"
+    c_in.database = "mydb"
+
+    dbc.set("DATABASE", c_in)
+    c_out = dbc.get("DATABASE")
+
+    self.assertEqual(str(c_out), str(c_in))
+
+  def test_list(self):
+    dbc = DBCredentials(filepatterns=[])
+
+    c = Credentials()
+    c.host = "foo"
+    dbc.set("FOO", c)
+
+    c = Credentials()
+    c.host = "bar"
+    dbc.set("BAR", c)
+
+    self.assertEqual(sorted(dbc.list()), ["BAR", "FOO"])
+
+
+  def test_config(self):
+    f = tempfile.NamedTemporaryFile()
+    f.write("""
+[database:DATABASE]
+type = postgres
+host = example.com
+port = 1234
+user = root
+password = secret
+database = mydb
+""")
+    f.flush() # don't close since that will delete the TemporaryFile
+
+    # test if DATABASE is there
+    dbc = DBCredentials(filepatterns=[f.name])
+    self.assertEqual(dbc.list(), ["DATABASE"])
+
+    # test if credentials match with what we've written
+    c_in = Credentials()
+    c_in.host = "example.com"
+    c_in.port = 1234
+    c_in.user = "root"
+    c_in.password = "secret"
+    c_in.database = "mydb"
+
+    c_out = dbc.get("DATABASE")
+
+    self.assertEqual(str(c_out), str(c_in))
+
+
+def main(argv):
+  unittest.main(verbosity=2)
+
+if __name__ == "__main__":
+  # run all tests
+  import sys
+  main(sys.argv[1:])
diff --git a/LCS/PyCommon/test/t_dbcredentials.run b/LCS/PyCommon/test/t_dbcredentials.run
new file mode 100755
index 0000000000000000000000000000000000000000..9497e5890832722cbf5692f586e5aac25c6aac1d
--- /dev/null
+++ b/LCS/PyCommon/test/t_dbcredentials.run
@@ -0,0 +1,4 @@
+#!/bin/bash
+source python-coverage.sh
+
+python_coverage_test dbcredentials t_dbcredentials.py
diff --git a/LCS/PyCommon/test/t_dbcredentials.sh b/LCS/PyCommon/test/t_dbcredentials.sh
new file mode 100755
index 0000000000000000000000000000000000000000..16dd2c87db63a6b4c0a82cb045679cc6926c99ca
--- /dev/null
+++ b/LCS/PyCommon/test/t_dbcredentials.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+./runctest.sh t_dbcredentials
diff --git a/SAS/OTDB_Services/TreeService.ini b/SAS/OTDB_Services/TreeService.ini
index a116e6494e582c2424c7bab6e106cdacb5de94a0..b44765f1d908f5663a2c4b891428c94eb502f1f0 100644
--- a/SAS/OTDB_Services/TreeService.ini
+++ b/SAS/OTDB_Services/TreeService.ini
@@ -1,6 +1,6 @@
 [program:TreeService]
-command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;TreeService.py --hostname=sasdb --database=LOFAR_4'
+command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;TreeService.py --dbcredentials=OTDB --busname=lofar.otdb.specification'
 user=lofarsys
 stopsignal=INT ; KeyboardInterrupt
-stdout_logfile=%{program_name)s.stdout
-stderr_logfile=%{program_name)s.stderr
+stdout_logfile=%(program_name)s.stdout
+stderr_logfile=%(program_name)s.stderr
diff --git a/SAS/OTDB_Services/TreeService.py b/SAS/OTDB_Services/TreeService.py
index 39489ced5f2745302a6770af35e0f0af0b4303dd..33cd9ba53ef7b30220171638df8b519d13124094 100755
--- a/SAS/OTDB_Services/TreeService.py
+++ b/SAS/OTDB_Services/TreeService.py
@@ -32,7 +32,6 @@ StatusUpdateCommand     : finction to update the status of a tree.
 
 import sys, time, pg
 import logging
-from optparse import OptionParser
 from lofar.messaging.Service import *
 
 QUERY_EXCEPTIONS = (TypeError, ValueError, MemoryError, pg.ProgrammingError, pg.InternalError)
@@ -213,9 +212,7 @@ class PostgressMessageHandlerInterface(MessageHandlerInterface):
     """
     def __init__(self, **kwargs):
         super(PostgressMessageHandlerInterface, self).__init__()
-        self.database = kwargs.pop("database")
-        self.db_user  = kwargs.pop("db_user",  "postgres")
-        self.db_host  = kwargs.pop("db_host",  "localhost")
+        self.dbcreds  = kwargs.pop("dbcreds")
         if len(kwargs):
             raise AttributeError("Unknown keys in arguments of 'DatabaseTiedMessageHandler: %s" % kwargs)
         self.connection = None
@@ -227,13 +224,12 @@ class PostgressMessageHandlerInterface(MessageHandlerInterface):
         self.connected = (self.connection and self.connection.status == 1)
         while not self.connected:
             try:
-                self.connection = pg.connect(user=self.db_user, host=self.db_host, dbname=self.database)
+                self.connection = pg.connect(**self.dbcreds.pg_connect_options())
                 self.connected = True
-                logger.info("Connected to database %s on host %s" % (self.database, self.db_host))
-            except (TypeError, SyntaxError, pg.InternalError):
+                logger.info("Connected to database %s" % (self.dbcreds,))
+            except (TypeError, SyntaxError, pg.InternalError), e:
                 self.connected = False
-                logger.error("Not connected to database %s on host %s (anymore), retry in 5 seconds"
-                             % (self.database, self.db_host))
+                logger.error("Not connected to database %s, retry in 5 seconds: %s" % (self.dbcreds, e))
                 time.sleep(5)
 
 class PostgressTaskSpecificationRequest(PostgressMessageHandlerInterface):
@@ -273,39 +269,37 @@ class PostgressKeyUpdateCommand(PostgressMessageHandlerInterface):
 
 
 if __name__ == "__main__":
+    from optparse import OptionParser
+    from lofar.common import dbcredentials
+    from lofar.common.util import waitForInterrupt
+
     # Check the invocation arguments
     parser = OptionParser("%prog [options]")
-    parser.add_option("-D", "--database", dest="dbName", type="string", default="",
-                      help="Name of the database")
-    parser.add_option("-H", "--hostname", dest="dbHost", type="string", default="sasdb",
-                      help="Hostname of database server")
+    parser.add_option("-B", "--busname", dest="busname", type="string", default="testbus",
+                      help="Busname or queue-name on which RPC commands are received")
+    parser.add_option_group(dbcredentials.options_group(parser))
     (options, args) = parser.parse_args()
 
-    if not options.dbName:
-        print "Missing database name"
-        parser.print_help()
-        sys.exit(0)
+    dbcreds = dbcredentials.parse_options(options)
 
-    if not options.dbHost:
-        print "Missing database server name"
+    if not options.busname:
+        print "Missing busname"
         parser.print_help()
-        sys.exit(0)
-
-    busname = sys.argv[1] if len(sys.argv) > 1 else "simpletest"
+        sys.exit(1)
 
     serv1 = Service("TaskSpecification", PostgressTaskSpecificationRequest,
-                    busname=busname, numthreads=1,
-                    handler_args = {"database" : options.dbName, "db_host" : options.dbHost})
+                    busname=options.busname, numthreads=1,
+                    handler_args = {"dbcreds" : dbcreds})
     serv2 = Service("StatusUpdateCmd",   PostgressStatusUpdateCommand,
-                    busname=busname, numthreads=1,
-                    handler_args = {"database" : options.dbName, "db_host" : options.dbHost})
+                    busname=options.busname, numthreads=1,
+                    handler_args = {"dbcreds" : dbcreds})
     serv3 = Service("KeyUpdateCmd",      PostgressKeyUpdateCommand,
-                    busname=busname, numthreads=1,
-                    handler_args = {"database" : options.dbName, "db_host" : options.dbHost})
+                    busname=options.busname, numthreads=1,
+                    handler_args = {"dbcreds" : dbcreds})
 
     with serv1, serv2, serv3:
         logger.info("Started the OTDB services")
-        serv3.wait_for_interrupt()
+        waitForInterrupt()
 
     logger.info("Stopped the OTDB services")
 
diff --git a/SAS/OTDB_Services/TreeStatusEvents.ini b/SAS/OTDB_Services/TreeStatusEvents.ini
index 3eb140b7361ef6dba04d87a0f3651cfb33d3196b..fd32c77dd843f0fa4dca62f3cede7dc05163e75b 100644
--- a/SAS/OTDB_Services/TreeStatusEvents.ini
+++ b/SAS/OTDB_Services/TreeStatusEvents.ini
@@ -1,6 +1,6 @@
 [program:TreeStatusEvents]
-command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;TreeStatusEvents.py --hostname=sasdb --database=LOFAR_4 --busname lofar.otdb.status'
+command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;TreeStatusEvents.py --dbcredentials=OTDB --busname lofar.otdb.status'
 user=lofarsys
 stopsignal=INT ; KeyboardInterrupt
-stdout_logfile=%{program_name)s.stdout
-stderr_logfile=%{program_name)s.stderr
+stdout_logfile=%(program_name)s.stdout
+stderr_logfile=%(program_name)s.stderr
diff --git a/SAS/OTDB_Services/TreeStatusEvents.py b/SAS/OTDB_Services/TreeStatusEvents.py
index a9264f7b5780df6148c16cfcf22a09626a3416a3..47e4b19dd20c78be07f72fdd0a987c7a2105a353 100755
--- a/SAS/OTDB_Services/TreeStatusEvents.py
+++ b/SAS/OTDB_Services/TreeStatusEvents.py
@@ -24,13 +24,16 @@
 Daemon that watches the OTDB database for status changes of trees and publishes those on the messagebus.
 """
 
-import os, sys, time, pg, signal
-from optparse import OptionParser
+import sys, time, pg
+import logging
 from lofar.messaging import EventMessage, ToBus
 
 QUERY_EXCEPTIONS = (TypeError, ValueError, MemoryError, pg.ProgrammingError, pg.InternalError)
 alive = False
 
+logging.basicConfig(stream=sys.stdout, level=logging.INFO)
+logger = logging.getLogger(__name__)
+
 # Define our own exceptions
 class FunctionError(Exception):
     "Something when wrong during the execution of the function"
@@ -68,36 +71,29 @@ def PollForStatusChanges(start_time, end_time, otdb_connection):
 
 def signal_handler(signum, frame):
     "Signal redirection to stop the daemon in a neat way."
-    print "Stopping program"
+    logger.info("Stopping program")
     global alive
     alive = False
 
 
 if __name__ == "__main__":
+    from optparse import OptionParser
+    from lofar.common import dbcredentials
+    import signal
+
     # Check the invocation arguments
     parser = OptionParser("%prog [options]")
-    parser.add_option("-D", "--database", dest="dbName", type="string", default="",
-                      help="Name of the database")
-    parser.add_option("-H", "--hostname", dest="dbHost", type="string", default="sasdb",
-                      help="Hostname of database server")
     parser.add_option("-B", "--busname", dest="busname", type="string", default="",
                       help="Busname or queue-name the status changes are published on")
+    parser.add_option_group(dbcredentials.options_group(parser))
     (options, args) = parser.parse_args()
 
-    if not options.dbName:
-        print "Missing database name"
-        parser.print_help()
-        sys.exit(0)
-
-    if not options.dbHost:
-        print "Missing database server name"
-        parser.print_help()
-        sys.exit(0)
+    dbcreds = dbcredentials.parse_options(options)
 
     if not options.busname:
         print "Missing busname"
         parser.print_help()
-        sys.exit(0)
+        sys.exit(1)
 
     # Set signalhandler to stop the program in a neat way.
     signal.signal(signal.SIGINT, signal_handler)
@@ -110,15 +106,17 @@ if __name__ == "__main__":
             while alive and not connected:
                 # Connect to the database
                 try:
-                    otdb_connection = pg.connect(user="postgres", host=options.dbHost, dbname=options.dbName)
+                    otdb_connection = pg.connect(**dbcreds.pg_connect_options())
                     connected = True
+                    logger.info("Connected to database %s" % (dbcreds,))
+
                     # Get list of allowed tree states
                     allowed_states = {}
                     for (state_nr, name) in otdb_connection.query("select id,name from treestate").getresult():
                         allowed_states[state_nr] = name
-                except (TypeError, SyntaxError, pg.InternalError):
+                except (TypeError, SyntaxError, pg.InternalError), e:
                     connected = False
-                    print "DatabaseError: Connection to database could not be made, reconnect attempt in 5 seconds"
+                    logger.error("Not connected to database %s, retry in 5 seconds: %s" % (dbcreds, e))
                     time.sleep(5)
 
             # When we are connected we can poll the database
@@ -129,22 +127,23 @@ if __name__ == "__main__":
                     start_time = otdb_connection.query("select treestatusevent from otdb_admin").getresult()[0][0]
                 except IndexError, QUERY_EXCEPTIONS:
                     start_time = "2015-01-01 00:00:00.00"
-                print "start_time=", start_time
  
                 try:
+                    logger.info("start_time=%s, polling database" % (start_time,))
                     record_list = PollForStatusChanges(start_time, "now", otdb_connection)
                 except FunctionError, exc_info:
-                    print exc_info
+                    logger.error(exc_info)
                 else:
                     for (treeid, state, modtime, creation) in record_list:
                         content = { "treeID" : treeid, "state" : allowed_states.get(state, "unknwon_state"), 
                                     "time_of_change" : modtime }
                         msg = EventMessage(context="otdb.treestatus", content=content)
-                        print treeid, allowed_states.get(state, "unknwon_state"), modtime, creation
+                        logger.info("sending message treeid %s state %s modtime %s" % (treeid, allowed_states.get(state, "unknwon_state"), modtime))
                         send_bus.send(msg)
-                        otdb_connection.query("update otdb_admin set treestatusevent = '%s'" % start_time)
+
                         start_time = creation
-                    print "==="
+                        logger.info("start_time:=%s" % (start_time,))
+                        otdb_connection.query("update otdb_admin set treestatusevent = '%s'" % start_time)
 
                 # Redetermine the database status.
                 connected = (otdb_connection and otdb_connection.status == 1)
diff --git a/SAS/OTDB_Services/test/t_TreeService.run b/SAS/OTDB_Services/test/t_TreeService.run
index 14cc7a3968eb5a1f300a595e24721b6109db21a5..b1abae81cd982d867afd4de30a1e00b27306730b 100755
--- a/SAS/OTDB_Services/test/t_TreeService.run
+++ b/SAS/OTDB_Services/test/t_TreeService.run
@@ -14,36 +14,11 @@ qpid-config add exchange topic $queue
 # Setup a clean database with predefined content
 dropdb -U postgres -h ${DBHOST} unittest_db
 gzip -dc $srcdir/unittest_db.dump.gz | psql -U postgres -h ${DBHOST} -f -
-TreeService.py $queue -D unittest_db -H ${DBHOST} &
+TreeService.py -B $queue -D unittest_db -H ${DBHOST} -U postgres &
 SERVER_PID=$!
 # Starting up takes a while
 sleep 3
 
 # Run the unit test
-# either with or without code coverage measurements,
-# depending wheter coverage has been installed
-
-if type "coverage" > /dev/null; then
-    #run test using python coverage tool
-
-    #erase previous results
-    coverage erase
-
-    #setup coverage config file
-    printf "[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n" > .coveragerc
-
-    coverage run --branch --include=*Messaging/python* t_TreeService.py $queue
-    RESULT=$?
-    if [ $RESULT -eq 0 ]; then
-        echo " *** Code coverage results *** "
-        coverage report -m
-        echo " *** End coverage results *** "
-    fi
-    exit $RESULT
-else
-    #coverage not available
-    echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests"
-    #run plain test script
-    python t_TreeService.py $queue 
-fi
-
+source python-coverage.sh
+python_coverage_test "Messaging/python" t_TreeService.py $queue
diff --git a/SAS/OTDB_Services/test/t_TreeStatusEvents.run b/SAS/OTDB_Services/test/t_TreeStatusEvents.run
index 22148b99324eeabc1c49ec8f3b56edb082a8df64..3284bfe30000241b37e44914f97be5eebcd4af9e 100755
--- a/SAS/OTDB_Services/test/t_TreeStatusEvents.run
+++ b/SAS/OTDB_Services/test/t_TreeStatusEvents.run
@@ -14,36 +14,11 @@ qpid-config add exchange topic $queue
 # Setup a clean database with predefined content
 dropdb -U postgres -h ${DBHOST} unittest_db
 gzip -dc $srcdir/unittest_db.dump.gz | psql -U postgres -h ${DBHOST} -f -
-TreeStatusEvents.py -B $queue -D unittest_db -H ${DBHOST} &
+TreeStatusEvents.py -B $queue -D unittest_db -H ${DBHOST} -U postgres &
 SERVICE_PID=$!
 # Starting up takes a while
 sleep 3
 
 # Run the unit test
-# either with or without code coverage measurements,
-# depending wheter coverage has been installed
-
-if type "coverage" > /dev/null; then
-    #run test using python coverage tool
-
-    #erase previous results
-    coverage erase
-
-    #setup coverage config file
-    printf "[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n" > .coveragerc
-
-    coverage run --branch --include=*Messaging/python* t_TreeStatusEvents.py -D unittest_db -H ${DBHOST} -B $queue
-    RESULT=$?
-    if [ $RESULT -eq 0 ]; then
-        echo " *** Code coverage results *** "
-        coverage report -m
-        echo " *** End coverage results *** "
-    fi
-    exit $RESULT
-else
-    #coverage not available
-    echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests"
-    #run plain test script
-    python t_TreeStatusEvents.py -D unittest_db -H ${DBHOST} -B $queue 
-fi
-
+source python-coverage.sh
+python_coverage_test "Messaging/python" t_TreeStatusEvents.py -D unittest_db -H ${DBHOST} -B $queue
diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/test/test_webservice.run b/SAS/ResourceAssignment/ResourceAssignmentEditor/test/test_webservice.run
index 3919f00767c19e31b32dee056b20f286e5440180..e929c40a7d057f9338482bf221b9506e15039c7e 100755
--- a/SAS/ResourceAssignment/ResourceAssignmentEditor/test/test_webservice.run
+++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/test/test_webservice.run
@@ -1,25 +1,3 @@
 #!/bin/bash
-
-if type "coverage" > /dev/null; then
-    #run test using python coverage tool
-
-    #erase previous results
-    coverage erase
-
-    #setup coverage config file
-    printf "[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n" > .coveragerc
-
-    coverage run --branch --include=*webservice* test_webservice.py
-    RESULT=$?
-    if [ $RESULT -eq 0 ]; then
-        echo " *** Code coverage results *** "
-        coverage report -m
-        echo " *** End coverage results *** "
-    fi
-    exit $RESULT
-else
-    #coverage not available
-    echo "Please run: 'pip install coverage' to enable code coverage reporting of the unit tests"
-    #run plain test script
-    python test_webservice.py
-fi
+source python-coverage.sh
+python_coverage_test "webservice" test_webservice.py