diff --git a/Makefile.am b/Makefile.am
index 7cabd8fd6d9ee6317a20c8f6be9dc742656c9c76..ce24accc64aee90f23901e6afc1980f2dccb19b7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -991,6 +991,7 @@ EXTRA_DIST = \
 	UPGRADE \
 	epydoc.conf.in \
 	pylintrc \
+	pylintrc-test \
 	autotools/build-bash-completion \
 	autotools/build-rpc \
 	autotools/check-header \
@@ -1451,7 +1452,8 @@ pep8_python_code = \
 	$(CHECK_HEADER) \
 	$(DOCPP) \
 	$(PYTHON_BOOTSTRAP) \
-	qa
+	qa \
+	$(python_test_support)
 
 test/py/daemon-util_unittest.bash: daemons/daemon-util
 
@@ -1931,7 +1933,7 @@ PEP8_IGNORE = E111,E121,E125,E127,E261,E501
 # For excluding pep8 expects filenames only, not whole paths
 PEP8_EXCLUDE = $(subst $(space),$(comma),$(strip $(notdir $(BUILT_PYTHON_SOURCES))))
 
-LINT_TARGETS = pylint pylint-qa
+LINT_TARGETS = pylint pylint-qa pylint-test
 if HAS_PEP8
 LINT_TARGETS += pep8
 endif
@@ -1953,6 +1955,12 @@ pylint-qa: $(GENERATED_FILES)
 	cd $(top_srcdir)/qa && \
 	  PYTHONPATH=$(abs_top_srcdir) $(PYLINT) $(LINT_OPTS) \
 	  --rcfile  ../pylintrc $(patsubst qa/%.py,%,$(qa_scripts))
+# FIXME: lint all test code, not just the newly added test support
+pylint-test: $(GENERATED_FILES)
+	@test -n "$(PYLINT)" || { echo 'pylint' not found during configure; exit 1; }
+	cd $(top_srcdir) && \
+		PYTHONPATH=.:./test/py $(PYLINT) $(LINT_OPTS) \
+		--rcfile=pylintrc-test  $(python_test_support)
 
 .PHONY: pep8
 pep8: $(GENERATED_FILES)
diff --git a/pylintrc b/pylintrc
index b47abcd77ff012432f3295a98d4bc78c6fa9df56..c6359a86c8076dd909f4d18d2a34757c87dc72d2 100644
--- a/pylintrc
+++ b/pylintrc
@@ -1,6 +1,8 @@
 # Configuration file for pylint (http://www.logilab.org/project/pylint). See
 # http://www.logilab.org/card/pylintfeatures for more detailed variable
 # descriptions.
+#
+# NOTE: Keep this file in sync (as much as possible) with pylintrc-test!
 
 [MASTER]
 profile = no
diff --git a/pylintrc-test b/pylintrc-test
new file mode 100644
index 0000000000000000000000000000000000000000..d61735a87ec3fe10774f8d601a6ad8d74ea02120
--- /dev/null
+++ b/pylintrc-test
@@ -0,0 +1,113 @@
+# pylint configuration file tailored to test code.
+#
+# NOTE: Keep in sync as much as possible with the standard pylintrc file.
+# Only a few settings had to be adapted for the test code.
+
+[MASTER]
+profile = no
+ignore =
+persistent = no
+cache-size = 50000
+load-plugins =
+
+[REPORTS]
+output-format = colorized
+include-ids = yes
+files-output = no
+reports = no
+evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+comment = yes
+
+[BASIC]
+required-attributes =
+# disabling docstring checks since we have way too many without (complex
+# inheritance hierarchies)
+#no-docstring-rgx = __.*__
+no-docstring-rgx = .*
+module-rgx = (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+# added lower-case names
+const-rgx = ((_{0,2}[A-Za-z][A-Za-z0-9_]*)|(__.*__))$
+class-rgx = _?[A-Z][a-zA-Z0-9]+$
+# added lower-case names
+function-rgx = (_?((([A-Z]+[a-z0-9]+)|test|assert)([A-Z]+[a-z0-9]*)*)|main|([a-z_][a-z0-9_]*))$
+# add lower-case names, since derived classes must obey method names
+method-rgx = (_{0,2}(([A-Z]+[a-z0-9]+)|test|assert)([A-Z]+[a-z0-9]*)*|__.*__|runTests|setUp|tearDown|([a-z_][a-z0-9_]*))$
+attr-rgx = [a-z_][a-z0-9_]{1,30}$
+argument-rgx = [a-z_][a-z0-9_]*$
+variable-rgx = (_?([a-z_][a-z0-9_]*)|(_?[A-Z0-9_]+))$
+inlinevar-rgx = [A-Za-z_][A-Za-z0-9_]*$
+good-names = i,j,k,_
+bad-names = foo,bar,baz,toto,tutu,tata
+bad-functions = xrange
+
+[TYPECHECK]
+ignore-mixin-members = yes
+zope = no
+acquired-members =
+
+[VARIABLES]
+init-import = no
+dummy-variables-rgx = _
+additional-builtins =
+
+[CLASSES]
+ignore-iface-methods =
+defining-attr-methods = __init__,__new__,setUp
+
+[DESIGN]
+max-args = 15
+max-locals = 50
+max-returns = 10
+max-branchs = 80
+max-statements = 200
+max-parents = 7
+max-attributes = 20
+# zero as struct-like (PODS) classes don't export any methods
+min-public-methods = 0
+max-public-methods = 50
+
+[IMPORTS]
+deprecated-modules = regsub,string,TERMIOS,Bastion,rexec
+import-graph =
+ext-import-graph =
+int-import-graph =
+
+[FORMAT]
+max-line-length = 80
+max-module-lines = 4500
+indent-string = "  "
+
+[MISCELLANEOUS]
+notes = FIXME,XXX,TODO
+
+[SIMILARITIES]
+min-similarity-lines = 4
+ignore-comments = yes
+ignore-docstrings = yes
+
+[MESSAGES CONTROL]
+
+# Enable only checker(s) with the given id(s). This option conflicts with the
+# disable-checker option
+#enable-checker=
+
+# Enable all checker(s) except those with the given id(s). This option
+# conflicts with the enable-checker option
+#disable-checker=
+disable-checker=similarities
+
+# Enable all messages in the listed categories (IRCWEF).
+#enable-msg-cat=
+
+# Disable all messages in the listed categories (IRCWEF).
+disable-msg-cat=
+
+# Enable the message(s) with the given id(s).
+#enable-msg=
+
+# Disable the message(s) with the given id(s).
+disable-msg=W0511,R0922,W0201
+
+# The new pylint 0.21+ style (plus the similarities checker, which is no longer
+# a separate opiton, but a generic disable control)
+disable=W0511,R0922,W0201,R0922,R0801,I0011,R0201
diff --git a/test/py/__init__.py b/test/py/__init__.py
index 13e3dca01b7e3729350111d8d8df65e46fce6ec5..5e12339743d023057358f54ca9f29e51b78f1e63 100644
--- a/test/py/__init__.py
+++ b/test/py/__init__.py
@@ -18,4 +18,4 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
-"""This module contains all python test code"""
\ No newline at end of file
+"""This module contains all python test code"""
diff --git a/test/py/cmdlib/__init__.py b/test/py/cmdlib/__init__.py
index 2c9e5d9a5c77cc5d6000f24383501d331cddbd7a..5d00d83aaa6de09ac0a798123b683d0fe09b041f 100644
--- a/test/py/cmdlib/__init__.py
+++ b/test/py/cmdlib/__init__.py
@@ -18,4 +18,4 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
-"""This module contains all cmdlib unit tests"""
\ No newline at end of file
+"""This module contains all cmdlib unit tests"""
diff --git a/test/py/cmdlib/test_unittest.py b/test/py/cmdlib/test_unittest.py
index c391eed8290edea6b1b881b0d1b955556596b593..3658e5ef25fc6f6aae3f332a9c3e7afb4f09290c 100644
--- a/test/py/cmdlib/test_unittest.py
+++ b/test/py/cmdlib/test_unittest.py
@@ -19,9 +19,7 @@
 # 02110-1301, USA.
 
 
-"""Tests for LUTest*
-
-"""
+"""Tests for LUTest*"""
 
 
 from ganeti import constants
diff --git a/test/py/cmdlib/testsupport/__init__.py b/test/py/cmdlib/testsupport/__init__.py
index 69fc33df74103f8b95d1ebdf87c01c8daf1ecc73..ae64c97acb6a448722a2ab4b2ba8fc1bfa07913a 100644
--- a/test/py/cmdlib/testsupport/__init__.py
+++ b/test/py/cmdlib/testsupport/__init__.py
@@ -23,12 +23,13 @@
 
 """
 
-from cmdlib_testcase import CmdlibTestCase
-from config_mock import ConfigMock
-from iallocator_mock import CreateIAllocatorMock
-from lock_manager_mock import LockManagerMock
-from processor_mock import ProcessorMock
-from rpc_runner_mock import CreateRpcRunnerMock, RpcResultsBuilder
+from cmdlib.testsupport.cmdlib_testcase import CmdlibTestCase
+from cmdlib.testsupport.config_mock import ConfigMock
+from cmdlib.testsupport.iallocator_mock import CreateIAllocatorMock
+from cmdlib.testsupport.lock_manager_mock import LockManagerMock
+from cmdlib.testsupport.processor_mock import ProcessorMock
+from cmdlib.testsupport.rpc_runner_mock import CreateRpcRunnerMock, \
+  RpcResultsBuilder
 
 __all__ = ["CmdlibTestCase",
            "ConfigMock",
@@ -37,4 +38,4 @@ __all__ = ["CmdlibTestCase",
            "LockManagerMock",
            "ProcessorMock",
            "RpcResultsBuilder",
-          ]
\ No newline at end of file
+           ]
diff --git a/test/py/cmdlib/testsupport/cmdlib_testcase.py b/test/py/cmdlib/testsupport/cmdlib_testcase.py
index 3b34853413d00dd7550e659844584ea9e89b112b..37151e6983d4e7f76a7aaaa169d6f67dd9c1db71 100644
--- a/test/py/cmdlib/testsupport/cmdlib_testcase.py
+++ b/test/py/cmdlib/testsupport/cmdlib_testcase.py
@@ -18,11 +18,15 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
-from config_mock import *
-from iallocator_mock import *
-from lock_manager_mock import *
-from processor_mock import *
-from rpc_runner_mock import *
+
+"""Main module of the cmdlib test framework"""
+
+
+from cmdlib.testsupport.config_mock import ConfigMock
+from cmdlib.testsupport.iallocator_mock import CreateIAllocatorMock
+from cmdlib.testsupport.lock_manager_mock import LockManagerMock
+from cmdlib.testsupport.processor_mock import ProcessorMock
+from cmdlib.testsupport.rpc_runner_mock import CreateRpcRunnerMock
 
 import testutils
 
@@ -34,6 +38,7 @@ class GanetiContextMock(object):
     self.rpc = rpc
 
 
+# pylint: disable=R0904
 class CmdlibTestCase(testutils.GanetiTestCase):
   """Base class for cmdlib tests.
 
@@ -83,3 +88,4 @@ class CmdlibTestCase(testutils.GanetiTestCase):
 
     """
     self.mcpu.assertLogContainsRegex(expected_regex)
+
diff --git a/test/py/cmdlib/testsupport/config_mock.py b/test/py/cmdlib/testsupport/config_mock.py
index a91d84b8288f70047ab9207dec6e6bd0d04e3d77..2fea5a94235e776d53c1d3c99c350bc924f58adc 100644
--- a/test/py/cmdlib/testsupport/config_mock.py
+++ b/test/py/cmdlib/testsupport/config_mock.py
@@ -18,7 +18,11 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
-import uuid
+
+"""Support for mocking the cluster configuration"""
+
+
+import uuid as uuid_module
 
 from ganeti import config
 from ganeti import constants
@@ -31,6 +35,7 @@ def _StubGetEntResolver():
   return mocks.FakeGetentResolver()
 
 
+# pylint: disable=R0904
 class ConfigMock(config.ConfigWriter):
   """A mocked cluster configuration with added methods for easy customization.
 
@@ -45,7 +50,7 @@ class ConfigMock(config.ConfigWriter):
                                      _getents=_StubGetEntResolver())
 
   def _GetUuid(self):
-    return str(uuid.uuid4())
+    return str(uuid_module.uuid4())
 
   def AddNewNodeGroup(self,
                       uuid=None,
@@ -56,7 +61,7 @@ class ConfigMock(config.ConfigWriter):
                       hv_state_static=None,
                       disk_state_static=None,
                       alloc_policy=None,
-                      networks=[]):
+                      networks=None):
     """Add a new L{objects.NodeGroup} to the cluster configuration
 
     See L{objects.NodeGroup} for parameter documentation.
@@ -72,6 +77,8 @@ class ConfigMock(config.ConfigWriter):
       uuid = self._GetUuid()
     if name is None:
       name = "mock_group_%d" % group_id
+    if networks is None:
+      networks = []
 
     group = objects.NodeGroup(uuid=uuid,
                               name=name,
@@ -87,6 +94,7 @@ class ConfigMock(config.ConfigWriter):
     self.AddNodeGroup(group, None)
     return group
 
+  # pylint: disable=R0913
   def AddNewNode(self,
                  uuid=None,
                  name=None,
@@ -154,12 +162,12 @@ class ConfigMock(config.ConfigWriter):
                      primary_node=None,
                      os="mocked_os",
                      hypervisor=constants.HT_FAKE,
-                     hvparams={},
-                     beparams={},
-                     osparams={},
+                     hvparams=None,
+                     beparams=None,
+                     osparams=None,
                      admin_state=constants.ADMINST_DOWN,
-                     nics=[],
-                     disks=[],
+                     nics=None,
+                     disks=None,
                      disk_template=constants.DT_DISKLESS,
                      disks_active=False,
                      network_port=None):
@@ -182,6 +190,16 @@ class ConfigMock(config.ConfigWriter):
       primary_node = self._master_node.uuid
     if isinstance(primary_node, objects.Node):
       primary_node = self._master_node.uuid
+    if hvparams is None:
+      hvparams = {}
+    if beparams is None:
+      beparams = {}
+    if osparams is None:
+      osparams = {}
+    if nics is None:
+      nics = []
+    if disks is None:
+      disks = []
 
     inst = objects.Instance(uuid=uuid,
                             name=name,
@@ -241,4 +259,4 @@ class ConfigMock(config.ConfigWriter):
     pass
 
   def _GetRpc(self, address_list):
-    raise NotImplementedError
+    raise AssertionError("This should not be used during tests!")
diff --git a/test/py/cmdlib/testsupport/iallocator_mock.py b/test/py/cmdlib/testsupport/iallocator_mock.py
index e706c1da43f8c8e8e28d5a1de5ff8e7377357c5b..1ff3cdada4b4d491cb552eb26e764f16eccf9b7f 100644
--- a/test/py/cmdlib/testsupport/iallocator_mock.py
+++ b/test/py/cmdlib/testsupport/iallocator_mock.py
@@ -18,6 +18,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
+
+"""Support for mocking the IAllocator interface"""
+
+
 import mock
 
 from ganeti.masterd import iallocator
diff --git a/test/py/cmdlib/testsupport/lock_manager_mock.py b/test/py/cmdlib/testsupport/lock_manager_mock.py
index 19c65b568fb70baa55448ede906147622a80dfee..5c5f8f652e9c99c37a61c6dd796f7d0967eb5d54 100644
--- a/test/py/cmdlib/testsupport/lock_manager_mock.py
+++ b/test/py/cmdlib/testsupport/lock_manager_mock.py
@@ -18,6 +18,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
+
+"""Support for mocking the lock manager"""
+
+
 from ganeti import locking
 
 
diff --git a/test/py/cmdlib/testsupport/processor_mock.py b/test/py/cmdlib/testsupport/processor_mock.py
index 0eb707398eeaf511fb91d124337323f93c137611..9c950612cf5241b72a00fa0fcb0f74c18018a96b 100644
--- a/test/py/cmdlib/testsupport/processor_mock.py
+++ b/test/py/cmdlib/testsupport/processor_mock.py
@@ -18,6 +18,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
+
+"""Support for mocking the opcode processor"""
+
+
 import re
 
 from ganeti import constants
@@ -42,6 +46,9 @@ class LogRecordingCallback(mcpu.OpExecCbBase):
 
     self.processor.log_entries.append((log_type, log_msg))
 
+  def SubmitManyJobs(self, jobs):
+    return mcpu.OpExecCbBase.SubmitManyJobs(self, jobs)
+
 
 class ProcessorMock(mcpu.Processor):
   """Mocked opcode processor for tests.
@@ -89,8 +96,8 @@ class ProcessorMock(mcpu.Processor):
     """Return a string with all log entries separated by a newline.
 
     """
-    return "\n".join("%s: %s" % (type, msg)
-                     for type, msg in self.GetLogEntries())
+    return "\n".join("%s: %s" % (log_type, msg)
+                     for log_type, msg in self.GetLogEntries())
 
   def GetLogMessagesString(self):
     """Return a string with all log messages separated by a newline.
@@ -107,8 +114,8 @@ class ProcessorMock(mcpu.Processor):
     @param expected_msg: the expected message
 
     """
-    for type, msg in self.log_entries:
-      if type == expected_type and msg == expected_msg:
+    for log_type, msg in self.log_entries:
+      if log_type == expected_type and msg == expected_msg:
         return
 
     raise AssertionError(
@@ -144,4 +151,4 @@ class ProcessorMock(mcpu.Processor):
     raise AssertionError(
       "Could not find '%s' in LU log messages. Log is:\n%s" %
       (expected_regex, self.GetLogMessagesString())
-    )
\ No newline at end of file
+    )
diff --git a/test/py/cmdlib/testsupport/rpc_runner_mock.py b/test/py/cmdlib/testsupport/rpc_runner_mock.py
index 9d9f6473d149f874550e257e1175d2df4c9e1801..23998c527a2b552690eed1afe1c64ec908271f5a 100644
--- a/test/py/cmdlib/testsupport/rpc_runner_mock.py
+++ b/test/py/cmdlib/testsupport/rpc_runner_mock.py
@@ -18,6 +18,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301, USA.
 
+
+"""Support for mocking the RPC runner"""
+
+
 import mock
 
 from ganeti import objects
@@ -91,7 +95,7 @@ class RpcResultsBuilder(object):
     else:
       return node.uuid
 
-  def CreateSuccessfulNodeResult(self, node, data={}):
+  def CreateSuccessfulNodeResult(self, node, data=None):
     """@see L{RpcResultsBuilder}
 
     @param node: @see L{RpcResultsBuilder}.
@@ -99,6 +103,8 @@ class RpcResultsBuilder(object):
     @param data: the data as returned by the RPC
     @rtype: L{rpc.RpcResult}
     """
+    if data is None:
+      data = {}
     return rpc.RpcResult(data=(True, data), node=self._GetNodeId(node))
 
   def CreateFailedNodeResult(self, node):
@@ -127,7 +133,7 @@ class RpcResultsBuilder(object):
     """
     return rpc.RpcResult(data=(False, error_msg), node=self._GetNodeId(node))
 
-  def AddSuccessfulNode(self, node, data={}):
+  def AddSuccessfulNode(self, node, data=None):
     """@see L{CreateSuccessfulNode}"""
     self._results.append(self.CreateSuccessfulNodeResult(node, data))
     return self
@@ -150,4 +156,4 @@ class RpcResultsBuilder(object):
 
     @rtype: dict
     """
-    return dict((result.node, result) for result in self._results)
\ No newline at end of file
+    return dict((result.node, result) for result in self._results)
diff --git a/test/py/mocks.py b/test/py/mocks.py
index 21481de7ec482101982d4380804288836040ec74..d06d6715a5292b750a717142c53f76b8244a1492 100644
--- a/test/py/mocks.py
+++ b/test/py/mocks.py
@@ -24,7 +24,6 @@
 
 import os
 
-from ganeti import utils
 from ganeti import netutils
 
 
@@ -37,7 +36,7 @@ FAKE_CLUSTER_KEY = ("AAAAB3NzaC1yc2EAAAABIwAAAQEAsuGLw70et3eApJ/ZEJkAVZogIrm"
                     "vYdB2nQds7/+Bf40C/OpbvnAxna1kVtgFHAo18cQ==")
 
 
-class FakeConfig:
+class FakeConfig(object):
   """Fake configuration object"""
 
   def IsCluster(self):
@@ -61,7 +60,7 @@ class FakeConfig:
   def GetMasterNodeName(self):
     return netutils.Hostname.GetSysName()
 
-  def GetDefaultIAllocator(Self):
+  def GetDefaultIAllocator(self):
     return "testallocator"
 
   def GetNodeName(self, node_uuid):
@@ -74,7 +73,7 @@ class FakeConfig:
     return map(self.GetNodeName, node_uuids)
 
 
-class FakeProc:
+class FakeProc(object):
   """Fake processor object"""
 
   def Log(self, msg, *args, **kwargs):
@@ -90,14 +89,14 @@ class FakeProc:
     pass
 
 
-class FakeGLM:
+class FakeGLM(object):
   """Fake global lock manager object"""
 
-  def list_owned(self, level):
+  def list_owned(self, _):
     return set()
 
 
-class FakeContext:
+class FakeContext(object):
   """Fake context object"""
 
   def __init__(self):
diff --git a/test/py/testutils.py b/test/py/testutils.py
index f0fe57b784f10c5d4fe7c8794017d073831da362..033daa85bdbd659feaf9f14701400c3185150845 100644
--- a/test/py/testutils.py
+++ b/test/py/testutils.py
@@ -27,7 +27,6 @@ import stat
 import tempfile
 import unittest
 import logging
-import types
 
 from ganeti import utils
 
@@ -120,7 +119,7 @@ class GanetiTestCase(unittest.TestCase):
     while self._temp_files:
       try:
         utils.RemoveFile(self._temp_files.pop())
-      except EnvironmentError, err:
+      except EnvironmentError:
         pass
 
   def assertFileContent(self, file_name, expected_content):
@@ -207,8 +206,10 @@ def patch_object(*args, **kwargs):
   """
   import mock
   try:
+    # pylint: disable=W0212
     return mock._patch_object(*args, **kwargs)
   except AttributeError:
+    # pylint: disable=E1101
     return mock.patch_object(*args, **kwargs)