diff --git a/SAS/DataManagement/CleanupService/config.py b/SAS/DataManagement/CleanupService/config.py
index 0e8dafdf6f6e028bf3a25ea448af70aa5595baa2..c7dd0f5a3ab60ce7622995116eb2763f049ce90c 100644
--- a/SAS/DataManagement/CleanupService/config.py
+++ b/SAS/DataManagement/CleanupService/config.py
@@ -5,3 +5,4 @@ from lofar.messaging import adaptNameToEnvironment
 
 DEFAULT_BUSNAME = adaptNameToEnvironment('lofar.dm.command')
 DEFAULT_SERVICENAME = 'CleanupService'
+CEP4_DATA_MOUNTPOINT='/data'
diff --git a/SAS/DataManagement/CleanupService/rpc.py b/SAS/DataManagement/CleanupService/rpc.py
index 369c01cf4ec1f13984540e549aa7074b0ffba0bb..f43cc6b19c8dd5892816af3f92c3b7fe5c9021f6 100644
--- a/SAS/DataManagement/CleanupService/rpc.py
+++ b/SAS/DataManagement/CleanupService/rpc.py
@@ -12,8 +12,8 @@ class CleanupRPC(RPCWrapper):
                  broker=None):
         super(CleanupRPC, self).__init__(busname, servicename, broker)
 
-    def foo(self):
-        return self.rpc('foo')
+    def removePath(self, path):
+        return self.rpc('RemovePath', path=path)
 
 def main():
     import sys
@@ -35,4 +35,4 @@ def main():
                         level=logging.INFO if options.verbose else logging.WARN)
 
     with CleanupRPC(busname=options.busname, servicename=options.servicename, broker=options.broker) as rpc:
-        print rpc.foo()
+        print rpc.removePath()
diff --git a/SAS/DataManagement/CleanupService/service.py b/SAS/DataManagement/CleanupService/service.py
index 87b21b9bc4c1f1794ab817fe74f56d6694242398..e6f118d1bf46f31f46b604918e7885ee24ee9dbb 100644
--- a/SAS/DataManagement/CleanupService/service.py
+++ b/SAS/DataManagement/CleanupService/service.py
@@ -19,35 +19,83 @@ with RPC(busname, 'GetProjectDetails') as getProjectDetails:
 
 '''
 import logging
+import os.path
+from shutil import rmtree
 from optparse import OptionParser
 from lofar.messaging import Service
 from lofar.messaging import setQpidLogLevel
 from lofar.messaging.Service import MessageHandlerInterface
 from lofar.common.util import waitForInterrupt
-from lofar.common.util import convertIntKeysToString
-from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME
+from lofar.common import isProductionEnvironment, isTestEnvironment
+from lofar.sas.datamanagement.cleanup.config import DEFAULT_BUSNAME, DEFAULT_SERVICENAME, CEP4_DATA_MOUNTPOINT
 
 logger = logging.getLogger(__name__)
 
 class CleanupHandler(MessageHandlerInterface):
     def __init__(self, **kwargs):
         super(CleanupHandler, self).__init__(**kwargs)
+        self.mountpoint = kwargs.pop("mountpoint", CEP4_DATA_MOUNTPOINT)
+        self.projects_path = os.path.join(self.mountpoint, 'projects' if isProductionEnvironment() else 'test-projects')
 
-        self.service2MethodMap = {'foo': self._foo}
+        self.service2MethodMap = {'RemovePath': self._removePath}
 
     def prepare_loop(self):
-        pass
+        logger.info("Cleanup service started with projects_path=%s", self.projects_path)
 
-    def _foo(self):
-        return 'foo'
+    def _removePath(self, path):
+        # do various sanity checking to prevent accidental deletes
+        if not isinstance(path, basestring):
+            message = "Provided path is not a string"
+            logger.error(message)
+            return {'deleted': False, 'message': message}
 
-def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, verbose=False):
+        if not path:
+            message = "Empty path provided"
+            logger.error(message)
+            return {'deleted': False, 'message': message}
+
+        if '*' in path or '?' in path:
+            message = "Invalid path '%s': No wildcards allowed" % (path,)
+            logger.error(message)
+            return {'deleted': False, 'message': message}
+
+        # remove any trailing slashes
+        if len(path) > 1:
+            path = path.rstrip('/')
+
+        if not path.startswith(self.projects_path):
+            message = "Invalid path '%s': Path does not start with '%s'" % (path, self.projects_path)
+            logger.error(message)
+            return {'deleted': False, 'message': message}
+
+        if path[len(self.projects_path)+1:].count('/') == 0:
+            message = "Invalid path '%s': Path should be a subdir of '%s'" % (path, self.projects_path)
+            logger.error(message)
+            return {'deleted': False, 'message': message}
+
+        logger.info("Attempting to delete %s", path)
+
+        failed_paths = set()
+        def onerror(func, failed_path, excinfo):
+            logger.info("Failed to delete %s", failed_path)
+            failed_paths.add(failed_path)
+
+        if rmtree(path, onerror=onerror):
+            message = "Deleted '%s'" % (path)
+            logger.info(message)
+            return {'deleted': True, 'message': message}
+
+        return {'deleted': False, 'message': 'Failed to delete one or more files.', 'failed_paths' : list(failed_paths)}
+
+
+def createService(busname=DEFAULT_BUSNAME, servicename=DEFAULT_SERVICENAME, broker=None, mountpoint=CEP4_DATA_MOUNTPOINT, verbose=False):
     return Service(servicename,
                    CleanupHandler,
                    busname=busname,
                    broker=broker,
                    use_service_methods=True,
-                   numthreads=2,
+                   numthreads=1,
+                   handler_args={'mountpoint': mountpoint},
                    verbose=verbose)
 
 def main():
@@ -55,8 +103,9 @@ def main():
     parser = OptionParser("%prog [options]",
                           description='runs the resourceassignment database service')
     parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost')
-    parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_BUSNAME, help="Name of the bus exchange on the qpid broker, default: %s" % DEFAULT_BUSNAME)
-    parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: %s" % DEFAULT_SERVICENAME)
+    parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_BUSNAME, help="Name of the bus exchange on the qpid broker, default: %default")
+    parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_SERVICENAME, help="Name for this service, default: default %default")
+    parser.add_option("-m", "--mountpoint", dest="mountpoint", type="string", default=CEP4_DATA_MOUNTPOINT, help="path of local cep4 mount point, default: %default")
     parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging')
     (options, args) = parser.parse_args()
 
diff --git a/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py
index 3d6f441b0cecad3d4dd95ac7d2c05576e84fb1ec..6fb8779b520949a60fe7b3facbe61b7603795f44 100755
--- a/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py
+++ b/SAS/DataManagement/CleanupService/test/test_cleanup_service_and_rpc.py
@@ -5,8 +5,6 @@ import uuid
 import datetime
 import logging
 from lofar.messaging import Service
-from lofar.sas.datamanagement.cleanup.service import createService
-from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC
 from qpid.messaging.exceptions import *
 
 try:
@@ -17,6 +15,14 @@ except ImportError:
     print 'Please source qpid profile'
     exit(3)
 
+try:
+    from mock import MagicMock
+    from mock import patch
+except ImportError:
+    print 'Cannot run test without python MagicMock'
+    print 'Please install MagicMock: pip install mock'
+    exit(3)
+
 connection = None
 broker = None
 
@@ -32,16 +38,64 @@ try:
     busname = 'test-lofarbus-%s' % (uuid.uuid1())
     broker.addExchange('topic', busname)
 
-    class TestCleanupServiceAndRPC(unittest.TestCase):
-        def test(self):
-            '''basic test '''
-            rpc = CleanupRPC(busname=busname)
-            self.assertEqual('foo', rpc.foo())
+    # the cleanup service uses shutil.rmtree under the hood
+    # so, mock/patch shutil.rmtree and fake the delete action
+    # because we do not want to delete any real data in this test
+    with patch('shutil.rmtree', autospec=True) as mock_rmtree:
+        mock = mock_rmtree.return_value
+        mock.return_value = True
+
+        # now that we have a mocked shutil.rmtree, import cleanupservice
+        from lofar.sas.datamanagement.cleanup.service import createService
+        from lofar.sas.datamanagement.cleanup.rpc import CleanupRPC
+
+        class TestCleanupServiceAndRPC(unittest.TestCase):
+            def test(self):
+                '''basic test '''
+                with CleanupRPC(busname=busname) as rpc:
+                    #try some invalid input
+                    self.assertFalse(rpc.removePath(None)['deleted'])
+                    self.assertFalse(rpc.removePath(True)['deleted'])
+                    self.assertFalse(rpc.removePath({'foo':'bar'})['deleted'])
+                    self.assertFalse(rpc.removePath(['foo', 'bar'])['deleted'])
+
+                    #try some dangerous paths
+                    #these should not be deleted
+                    result = rpc.removePath('/')
+                    self.assertFalse(result['deleted'])
+                    self.assertTrue('Path does not start with' in result['message'])
+
+                    result = rpc.removePath('/foo/*/bar')
+                    self.assertFalse(result['deleted'])
+                    self.assertTrue('No wildcards allowed' in result['message'])
+
+                    result = rpc.removePath('/foo/ba?r')
+                    self.assertFalse(result['deleted'])
+                    self.assertTrue('No wildcards allowed' in result['message'])
+
+                    result = rpc.removePath('/data')
+                    self.assertFalse(result['deleted'])
+                    self.assertTrue('Path does not start with' in result['message'])
+
+                    result = rpc.removePath('/data/test-projects/')
+                    self.assertFalse(result['deleted'])
+                    self.assertTrue('Path should be a subdir of' in result['message'])
+
+                    result = rpc.removePath('/data/test-projects/foo')
+                    self.assertFalse(result['deleted'])
+                    self.assertTrue('Path should be a subdir of' in result['message'])
+
+                    result = rpc.removePath('/data/test-projects/foo/')
+                    self.assertFalse(result['deleted'])
+                    self.assertTrue('Path should be a subdir of' in result['message'])
+
+                    #try an actual delete, should work with mocked shutil.rmtree
+                    self.assertTrue(rpc.removePath('/data/test-projects/foo/bar')['deleted'])
 
-    # create and run the service
-    with createService(busname=busname):
-        # and run all tests
-        unittest.main()
+        # create and run the service
+        with createService(busname=busname):
+            # and run all tests
+            unittest.main()
 
 except ConnectError as ce:
     logger.error(ce)