Repository: rucio/rucio

Issue:
Reaper tests make no assertions or deletions
Motivation
----------
`test_reaper.py` and `test_reaper2.py` run the reaper, but nothing is ever deleted as the `source` column in `rse_usage` is set to `srm` when generating the test data, but (by default) this is `storage` for the RSE being used in the test. There replicas also need a tombstone set on them. As a result we never delete anything when running the reaper, but as no assertions are made the test passes.
There's also a bug in the way we decide how many files to delete (for the original reaper) where if (for example) 5 files need deleting to make the required amount of free space, we only delete 4. This is due to the order in which we check file size vs adding the replica to a list of files to be deleted in core.replica.list_unlocked_replicas.

Modification
------------
Change `source` and add tombstones to the replicas generated to allow deletion. Add some asserts to the reaper tests to make sure we delete the expected number of replicas. Change how we calculate the number of files that need reaping for the original reaper.


Discussion:

Resolving patch:
diff --git a/lib/rucio/core/replica.py b/lib/rucio/core/replica.py
--- a/lib/rucio/core/replica.py
+++ b/lib/rucio/core/replica.py
@@ -28,6 +28,7 @@
 # - Andrew Lister, <andrew.lister@stfc.ac.uk>, 2019
 # - Brandon White, <bjwhite@fnal.gov>, 2019
 # - Luc Goossens <luc.goossens@cern.ch>, 2020
+# - Patrick Austin <patrick.austin@stfc.ac.uk>, 2020
 #
 # PY3K COMPATIBLE
 
@@ -1635,9 +1636,9 @@ def list_unlocked_replicas(rse_id, limit, bytes=None, worker_number=None, total_
     for (scope, name, path, bytes, tombstone, state) in query.yield_per(1000):
         if state != ReplicaState.UNAVAILABLE:
 
-            total_bytes += bytes
             if tombstone != OBSOLETE and needed_space is not None and total_bytes > needed_space:
                 break
+            total_bytes += bytes
 
             total_files += 1
             if total_files > limit:
diff --git a/lib/rucio/rse/protocols/globus.py b/lib/rucio/rse/protocols/globus.py
--- a/lib/rucio/rse/protocols/globus.py
+++ b/lib/rucio/rse/protocols/globus.py
@@ -16,174 +16,35 @@
 # - Matt Snyder <msnyder@rcf.rhic.bnl.gov>, 2019
 # - Martin Barisits <martin.barisits@cern.ch>, 2019
 
-import hashlib
-import logging
-import sys
+from __future__ import print_function
 
-from rucio.transfertool.globusLibrary import send_delete_task
-from rucio.common.config import config_get
+import imp
+
+from six import string_types
+from rucio.common import exception
 from rucio.core.rse import get_rse_attribute
+from rucio.rse.protocols.protocol import RSEProtocol
+from rucio.transfertool.globusLibrary import getTransferClient, send_delete_task
+
+# Extra modules: Only imported if available
+EXTRA_MODULES = {'globus_sdk': False}
+
+for extra_module in EXTRA_MODULES:
+    try:
+        imp.find_module(extra_module)
+        EXTRA_MODULES[extra_module] = True
+    except ImportError:
+        EXTRA_MODULES[extra_module] = False
+
+if EXTRA_MODULES['globus_sdk']:
+    from globus_sdk import TransferAPIError  # pylint: disable=import-error
 
 try:
     # PY2
-    from ConfigParser import NoOptionError, NoSectionError
     from urlparse import urlparse
 except ImportError:
     # PY3
-    from configparser import NoOptionError, NoSectionError
     from urllib.parse import urlparse
-from six import string_types
-
-from rucio.common import config, exception
-from rucio.rse import rsemanager
-from rucio.rse.protocols.protocol import RSEProtocol
-
-if getattr(rsemanager, 'CLIENT_MODE', None):
-    from rucio.client.rseclient import RSEClient
-
-if getattr(rsemanager, 'SERVER_MODE', None):
-    from rucio.core import replica
-
-logging.basicConfig(stream=sys.stdout,
-                    level=getattr(logging,
-                                  config_get('common', 'loglevel',
-                                             raise_exception=False,
-                                             default='DEBUG').upper()),
-                    format='%(asctime)s\t%(process)d\t%(levelname)s\t%(message)s')
-
-
-class RSEDeterministicTranslation(object):
-    """
-    Execute the logic for translating a LFN to a path.
-    """
-
-    _LFN2PFN_ALGORITHMS = {}
-    _DEFAULT_LFN2PFN = "hash"
-
-    def __init__(self, rse=None, rse_attributes=None, protocol_attributes=None):
-        """
-        Initialize a translator object from the RSE, its attributes, and the protocol-specific
-        attributes.
-
-        :param rse: Name of RSE for this translation.
-        :param rse_attributes: A dictionary of RSE-specific attributes for use in the translation.
-        :param protocol_attributes: A dictionary of RSE/protocol-specific attributes.
-        """
-        self.rse = rse
-        self.rse_attributes = rse_attributes if rse_attributes else {}
-        self.protocol_attributes = protocol_attributes if protocol_attributes else {}
-
-    @classmethod
-    def supports(cls, name):
-        """
-        Check to see if a specific algorithm is supported.
-
-        :param name: Name of the deterministic algorithm.
-        :returns: True if `name` is an algorithm supported by the translator class, False otherwise.
-        """
-        return name in cls._LFN2PFN_ALGORITHMS
-
-    @staticmethod
-    def register(lfn2pfn_callable, name=None):
-        """
-        Provided a callable function, register it as one of the valid LFN2PFN algorithms.
-
-        The callable will receive five arguments:
-         - scope: Scope of the LFN.
-         - name: LFN's path name
-         - rse: RSE name the translation is being done for.
-         - rse_attributes: Attributes of the RSE.
-         - protocol_attributes: Attributes of the RSE's protocol
-        The return value should be the last part of the PFN - it will be appended to the
-        rest of the URL.
-
-        :param lfn2pfn_callable: Callable function to use for generating paths.
-        :param name: Algorithm name used for registration.  If None, then `lfn2pfn_callable.__name__` is used.
-        """
-        if name is None:
-            name = lfn2pfn_callable.__name__
-        RSEDeterministicTranslation._LFN2PFN_ALGORITHMS[name] = lfn2pfn_callable
-
-    @staticmethod
-    def __hash(scope, name, rse, rse_attrs, protocol_attrs):
-        """
-        Given a LFN, turn it into a sub-directory structure using a hash function.
-
-        This takes the MD5 of the LFN and uses the first four characters as a subdirectory
-        name.
-
-        :param scope: Scope of the LFN.
-        :param name: File name of the LFN.
-        :param rse: RSE for PFN (ignored)
-        :param rse_attrs: RSE attributes for PFN (ignored)
-        :param protocol_attrs: RSE protocol attributes for PFN (ignored)
-        :returns: Path for use in the PFN generation.
-        """
-        del rse
-        del rse_attrs
-        del protocol_attrs
-        hstr = hashlib.md5('%s:%s' % (scope, name)).hexdigest()
-        if scope.startswith('user') or scope.startswith('group'):
-            scope = scope.replace('.', '/')
-        return '%s/%s/%s/%s' % (scope, hstr[0:2], hstr[2:4], name)
-
-    @staticmethod
-    def __identity(scope, name, rse, rse_attrs, protocol_attrs):
-        """
-        Given a LFN, convert it directly to a path using the mapping:
-
-            scope:path -> scope/path
-
-        :param scope: Scope of the LFN.
-        :param name: File name of the LFN.
-        :param rse: RSE for PFN (ignored)
-        :param rse_attrs: RSE attributes for PFN (ignored)
-        :param protocol_attrs: RSE protocol attributes for PFN (ignored)
-        :returns: Path for use in the PFN generation.
-        """
-        del rse
-        del rse_attrs
-        del protocol_attrs
-        if scope.startswith('user') or scope.startswith('group'):
-            scope = scope.replace('.', '/')
-        return '%s/%s' % (scope, name)
-
-    @classmethod
-    def _module_init_(cls):
-        """
-        Initialize the class object on first module load.
-        """
-        cls.register(cls.__hash, "hash")
-        cls.register(cls.__identity, "identity")
-        policy_module = None
-        try:
-            policy_module = config.config_get('policy', 'lfn2pfn_module')
-        except (NoOptionError, NoSectionError):
-            pass
-        if policy_module:
-            # TODO: The import of importlib is done like this due to a dependency issue with python 2.6 and incompatibility of the module with py3.x
-            # More information https://github.com/rucio/rucio/issues/875
-            import importlib
-            importlib.import_module(policy_module)
-
-        cls._DEFAULT_LFN2PFN = config.get_lfn2pfn_algorithm_default()
-
-    def path(self, scope, name):
-        """ Transforms the logical file name into a PFN's path.
-
-            :param lfn: filename
-            :param scope: scope
-
-            :returns: RSE specific URI of the physical file
-        """
-        algorithm = self.rse_attributes.get('lfn2pfn_algorithm', 'default')
-        if algorithm == 'default':
-            algorithm = RSEDeterministicTranslation._DEFAULT_LFN2PFN
-        algorithm_callable = RSEDeterministicTranslation._LFN2PFN_ALGORITHMS[algorithm]
-        return algorithm_callable(scope, name, self.rse, self.rse_attributes, self.protocol_attributes)
-
-
-RSEDeterministicTranslation._module_init_()  # pylint: disable=protected-access
 
 
 class GlobusRSEProtocol(RSEProtocol):
@@ -194,23 +55,8 @@ def __init__(self, protocol_attr, rse_settings, logger=None):
 
             :param props: Properties of the requested protocol
         """
-        self.attributes = protocol_attr
-        self.translator = None
-        self.renaming = True
-        self.overwrite = False
-        self.rse = rse_settings
+        super(GlobusRSEProtocol, self).__init__(protocol_attr, rse_settings, logger=logger)
         self.globus_endpoint_id = get_rse_attribute(key='globus_endpoint_id', rse_id=self.rse.get('id'))
-        if self.rse['deterministic']:
-            self.translator = RSEDeterministicTranslation(self.rse['rse'], rse_settings, self.attributes)
-            if getattr(rsemanager, 'CLIENT_MODE', None) and \
-                    not RSEDeterministicTranslation.supports(self.rse.get('lfn2pfn_algorithm')):
-                # Remote server has an algorithm we don't understand; always make the server do the lookup.
-                setattr(self, 'lfns2pfns', self.__lfns2pfns_client)
-        else:
-            if getattr(rsemanager, 'CLIENT_MODE', None):
-                setattr(self, 'lfns2pfns', self.__lfns2pfns_client)
-            if getattr(rsemanager, 'SERVER_MODE', None):
-                setattr(self, '_get_path', self._get_path_nondeterministic_server)
 
     def lfns2pfns(self, lfns):
         """
@@ -232,28 +78,12 @@ def lfns2pfns(self, lfns):
         for lfn in lfns:
             scope, name = lfn['scope'], lfn['name']
 
-            logging.debug('scope: %s' % scope)
-            logging.debug('name: %s' % name)
-
             if 'path' in lfn and lfn['path'] is not None:
                 pfns['%s:%s' % (scope, name)] = ''.join([prefix, lfn['path'] if not lfn['path'].startswith('/') else lfn['path'][1:]])
             else:
                 pfns['%s:%s' % (scope, name)] = ''.join([prefix, self._get_path(scope=scope, name=name)])
         return pfns
 
-    def __lfns2pfns_client(self, lfns):
-        """ Provides the path of a replica for non-deterministic sites. Will be assigned to get path by the __init__ method if neccessary.
-
-            :param scope: list of DIDs
-
-            :returns: dict with scope:name as keys and PFN as value (in case of errors the Rucio exception si assigned to the key)
-        """
-        client = RSEClient()
-
-        lfns = [lfns] if isinstance(lfns, dict) else lfns
-        lfn_query = ["%s:%s" % (lfn['scope'], lfn['name']) for lfn in lfns]
-        return client.lfns2pfns(self.rse['rse'], lfn_query, scheme=self.attributes['scheme'])
-
     def _get_path(self, scope, name):
         """ Transforms the logical file name into a PFN.
             Suitable for sites implementing the RUCIO naming convention.
@@ -266,21 +96,6 @@ def _get_path(self, scope, name):
         """
         return self.translator.path(scope, name)
 
-    def _get_path_nondeterministic_server(self, scope, name):  # pylint: disable=invalid-name
-        """ Provides the path of a replica for non-deterministic sites. Will be assigned to get path by the __init__ method if neccessary. """
-        rep = replica.get_replica(rse_id=self.rse['id'], scope=scope, name=name)
-        if 'path' in rep and rep['path'] is not None:
-            path = rep['path']
-        elif 'state' in rep and (rep['state'] is None or rep['state'] == 'UNAVAILABLE'):
-            raise exception.ReplicaUnAvailable('Missing path information and state is UNAVAILABLE for replica %s:%s on non-deterministic storage named %s' % (scope, name, self.rse['rse']))
-        else:
-            raise exception.ReplicaNotFound('Missing path information for replica %s:%s on non-deterministic storage named %s' % (scope, name, self.rse['rse']))
-        if path.startswith('/'):
-            path = path[1:]
-        if path.endswith('/'):
-            path = path[:-1]
-        return path
-
     def parse_pfns(self, pfns):
         """
             Splits the given PFN into the parts known by the protocol. It is also checked if the provided protocol supportes the given PFNs.
@@ -294,8 +109,6 @@ def parse_pfns(self, pfns):
         ret = dict()
         pfns = [pfns] if isinstance(pfns, string_types) else pfns
 
-        logging.debug('... Beginning GlobusRSEProtocol.parse_pfns ... ')
-
         for pfn in pfns:
             parsed = urlparse(pfn)
             scheme = parsed.scheme
@@ -344,55 +157,50 @@ def exists(self, path):
             :returns: True if the file exists, False if it doesn't
 
             :raises SourceNotFound: if the source file was not found on the referred storage.
-        """
-        logging.debug('... Beginning GlobusRSEProtocol.exists ... ')
-        raise NotImplementedError
-        # pass
 
-    def connect(self):
         """
-            Establishes the actual connection to the referred RSE.
 
-            :raises RSEAccessDenied: if no connection could be established.
-        """
-        logging.debug('... Beginning GlobusRSEProtocol.connect ... ')
-        # raise NotImplementedError
-        pass
+        filepath = '/'.join(path.split('/')[0:-1]) + '/'
+        filename = path.split('/')[-1]
 
-    def close(self):
-        """ Closes the connection to RSE."""
-        logging.debug('... Beginning GlobusRSEProtocol.close ... ')
-        # raise NotImplementedError
-        pass
+        transfer_client = getTransferClient()
+        exists = False
+
+        if self.globus_endpoint_id:
+            try:
+                resp = transfer_client.operation_ls(endpoint_id=self.globus_endpoint_id[0], path=filepath)
+                exists = len([r for r in resp if r['name'] == filename]) > 0
+            except TransferAPIError as err:
+                print(err)
+        else:
+            print('No rse attribute found for globus endpoint id.')
+
+        return exists
 
-    def get(self, path, dest, transfer_timeout=None):
+    def list(self, path):
         """
-            Provides access to files stored inside connected the RSE.
 
-            :param path: Physical file name of requested file
-            :param dest: Name and path of the files when stored at the client
-            :param transfer_timeout: Transfer timeout (in seconds)
+            Checks if the requested path is known by the referred RSE and returns a list of items
 
-            :raises DestinationNotAccessible: if the destination storage was not accessible.
-            :raises ServiceUnavailable: if some generic error occured in the library.
-            :raises SourceNotFound: if the source file was not found on the referred storage.
-         """
-        raise NotImplementedError
+            :param path: Physical file name
+
+            :returns: List of items
 
-    def put(self, source, target, source_dir, transfer_timeout=None):
         """
-            Allows to store files inside the referred RSE.
 
-            :param source: path to the source file on the client file system
-            :param target: path to the destination file on the storage
-            :param source_dir: Path where the to be transferred files are stored in the local file system
-            :param transfer_timeout: Transfer timeout (in seconds)
+        transfer_client = getTransferClient()
+        items = []
 
-            :raises DestinationNotAccessible: if the destination storage was not accessible.
-            :raises ServiceUnavailable: if some generic error occured in the library.
-            :raises SourceNotFound: if the source file was not found on the referred storage.
-        """
-        raise NotImplementedError
+        if self.globus_endpoint_id:
+            try:
+                resp = transfer_client.operation_ls(endpoint_id=self.globus_endpoint_id[0], path=path)
+                items = resp['DATA']
+            except TransferAPIError as err:
+                print(err)
+        else:
+            print('No rse attribute found for globus endpoint id.')
+
+        return items
 
     def delete(self, path):
         """
@@ -403,55 +211,18 @@ def delete(self, path):
             :raises ServiceUnavailable: if some generic error occured in the library.
             :raises SourceNotFound: if the source file was not found on the referred storage.
         """
-        logging.debug('... Beginning GlobusRSEProtocol.delete ... ')
-        logging.debug('path: %s' % path)
-        logging.debug('self.attributes: %s' % self.attributes)
-        logging.debug('self.rse: %s' % self.rse)
-        logging.debug('self.globus_endpoint_id: %s' % self.globus_endpoint_id)
         if self.globus_endpoint_id:
-            delete_response = send_delete_task(endpoint_id=self.globus_endpoint_id[0], path=path)
+            try:
+                delete_response = send_delete_task(endpoint_id=self.globus_endpoint_id[0], path=path)
+            except TransferAPIError as err:
+                print(err)
         else:
-            logging.error('No rse attribute found for globus endpoint id.')
-        logging.debug('delete_response: %s' % delete_response)
-        # Below task submission sends a delete task to Globus and only checks that it was accepted
-        # TODO: Weave this into a (new?) daemon to check actual deletion on the endpoint
-        if delete_response['code'] != 'Accepted':
-            logging.error('delete_task not accepted by Globus')
-            logging.debug('delete_response: %s' % delete_response)
-        # pass
-        # TODO: create globus command to delete file on endpoint
-
-    def rename(self, path, new_path):
-        """ Allows to rename a file stored inside the connected RSE.
-
-            :param path: path to the current file on the storage
-            :param new_path: path to the new file on the storage
-
-            :raises DestinationNotAccessible: if the destination storage was not accessible.
-            :raises ServiceUnavailable: if some generic error occured in the library.
-            :raises SourceNotFound: if the source file was not found on the referred storage.
-        """
-        raise NotImplementedError
-
-    def get_space_usage(self):
-        """
-            Get RSE space usage information.
-
-            :returns: a list with dict containing 'totalsize' and 'unusedsize'
-
-            :raises ServiceUnavailable: if some generic error occured in the library.
-        """
-        raise NotImplementedError
-
-    def stat(self, path):
-        """
-            Returns the stats of a file.
-
-            :param path: path to file
+            print('No rse attribute found for globus endpoint id.')
 
-            :raises ServiceUnavailable: if some generic error occured in the library.
-            :raises SourceNotFound: if the source file was not found on the referred storage.
+        if delete_response['code'] != 'Accepted':
+            print('delete_task not accepted by Globus')
+            print('delete_response: %s' % delete_response)
 
-            :returns: a dict with two keys, filesize and adler32 of the file provided in path.
-        """
+    def close(self):
+        """ Closes the connection to RSE."""
         raise NotImplementedError


Test patch:
diff --git a/lib/rucio/tests/test_reaper.py b/lib/rucio/tests/test_reaper.py
--- a/lib/rucio/tests/test_reaper.py
+++ b/lib/rucio/tests/test_reaper.py
@@ -18,29 +18,56 @@
 # - Joaquin Bogado <jbogado@linti.unlp.edu.ar>, 2018
 # - Andrew Lister <andrew.lister@stfc.ac.uk>, 2019
 # - Mario Lassnig <mario.lassnig@cern.ch>, 2019
+# - Eli Chadwick <eli.chadwick@stfc.ac.uk>, 2020
+# - Patrick Austin <patrick.austin@stfc.ac.uk>, 2020
+
+from datetime import datetime, timedelta
+from nose.tools import assert_equal
 
 from rucio.common.types import InternalAccount, InternalScope
 from rucio.common.utils import generate_uuid
 from rucio.core import rse as rse_core
 from rucio.core import replica as replica_core
 from rucio.daemons.reaper.reaper import reaper
+from rucio.tests.common import rse_name_generator
 
 
 def test_reaper():
     """ REAPER (DAEMON): Test the reaper daemon."""
+    rse_name = rse_name_generator()
+    rse_id = rse_core.add_rse(rse_name)
+
+    mock_protocol = {'scheme': 'MOCK',
+                     'hostname': 'localhost',
+                     'port': 123,
+                     'prefix': '/test/reaper',
+                     'impl': 'rucio.rse.protocols.mock.Default',
+                     'domains': {
+                         'lan': {'read': 1,
+                                 'write': 1,
+                                 'delete': 1},
+                         'wan': {'read': 1,
+                                 'write': 1,
+                                 'delete': 1}}}
+    rse_core.add_protocol(rse_id=rse_id, parameter=mock_protocol)
+
     nb_files = 30
     file_size = 2147483648  # 2G
-    rse_id = rse_core.get_rse_id(rse='MOCK')
 
+    file_names = []
     for i in range(nb_files):
+        file_name = 'lfn' + generate_uuid()
+        file_names.append(file_name)
         replica_core.add_replica(rse_id=rse_id, scope=InternalScope('data13_hip'),
-                                 name='lfn' + generate_uuid(), bytes=file_size,
+                                 name=file_name, bytes=file_size,
+                                 tombstone=datetime.utcnow() - timedelta(days=1),
                                  account=InternalAccount('root'), adler32=None, md5=None)
 
-    rse_core.set_rse_usage(rse_id=rse_id, source='srm', used=nb_files * file_size, free=800)
+    rse_core.set_rse_usage(rse_id=rse_id, source='storage', used=nb_files * file_size, free=800)
     rse_core.set_rse_limits(rse_id=rse_id, name='MinFreeSpace', value=10737418240)
     rse_core.set_rse_limits(rse_id=rse_id, name='MaxBeingDeletedFiles', value=10)
 
-    rses = [rse_core.get_rse(rse_core.get_rse_id('MOCK')), ]
+    rses = [rse_core.get_rse(rse_id), ]
     reaper(once=True, rses=rses)
     reaper(once=True, rses=rses)
+    assert_equal(len(list(replica_core.list_replicas(dids=[{'scope': InternalScope('data13_hip'), 'name': n} for n in file_names], rse_expression=rse_name))), nb_files - 10)
diff --git a/lib/rucio/tests/test_reaper2.py b/lib/rucio/tests/test_reaper2.py
--- a/lib/rucio/tests/test_reaper2.py
+++ b/lib/rucio/tests/test_reaper2.py
@@ -14,28 +14,55 @@
 #
 # Authors:
 # - Thomas Beermann <thomas.beermann@cern.ch>, 2020
+# - Patrick Austin <patrick.austin@stfc.ac.uk>, 2020
+
+from datetime import datetime, timedelta
+from nose.tools import assert_equal
 
 from rucio.common.types import InternalAccount, InternalScope
 from rucio.common.utils import generate_uuid
 from rucio.core import rse as rse_core
 from rucio.core import replica as replica_core
 from rucio.daemons.reaper.reaper2 import reaper
+from rucio.tests.common import rse_name_generator
 
 
 def test_reaper():
     """ REAPER2 (DAEMON): Test the reaper2 daemon."""
+    rse_name = rse_name_generator()
+    rse_id = rse_core.add_rse(rse_name)
+
+    mock_protocol = {'scheme': 'MOCK',
+                     'hostname': 'localhost',
+                     'port': 123,
+                     'prefix': '/test/reaper',
+                     'impl': 'rucio.rse.protocols.mock.Default',
+                     'domains': {
+                         'lan': {'read': 1,
+                                 'write': 1,
+                                 'delete': 1},
+                         'wan': {'read': 1,
+                                 'write': 1,
+                                 'delete': 1}}}
+    rse_core.add_protocol(rse_id=rse_id, parameter=mock_protocol)
+
     nb_files = 30
     file_size = 2147483648  # 2G
-    rse_id = rse_core.get_rse_id(rse='MOCK')
 
+    file_names = []
     for i in range(nb_files):
+        file_name = 'lfn' + generate_uuid()
+        file_names.append(file_name)
         replica_core.add_replica(rse_id=rse_id, scope=InternalScope('data13_hip'),
-                                 name='lfn' + generate_uuid(), bytes=file_size,
+                                 name=file_name, bytes=file_size,
+                                 tombstone=datetime.utcnow() - timedelta(days=1),
                                  account=InternalAccount('root'), adler32=None, md5=None)
 
-    rse_core.set_rse_usage(rse_id=rse_id, source='srm', used=nb_files * file_size, free=800)
+    rse_core.set_rse_usage(rse_id=rse_id, source='storage', used=nb_files * file_size, free=800)
     rse_core.set_rse_limits(rse_id=rse_id, name='MinFreeSpace', value=10737418240)
     rse_core.set_rse_limits(rse_id=rse_id, name='MaxBeingDeletedFiles', value=10)
 
-    reaper(once=True, rses=[], include_rses='MOCK', exclude_rses=[])
-    reaper(once=True, rses=[], include_rses='MOCK', exclude_rses=[])
+    reaper(once=True, rses=[], include_rses=rse_name, exclude_rses=[])
+    reaper(once=True, rses=[], include_rses=rse_name, exclude_rses=[])
+
+    assert_equal(len(list(replica_core.list_replicas(dids=[{'scope': InternalScope('data13_hip'), 'name': n} for n in file_names], rse_expression=rse_name))), nb_files - 5)