Repository: rucio/rucio

Issue:
list_replicas with schemes=None and schemes=[...] gives conflicting results
Motivation
----------
In this case srm is the top priority protocol. If no schemes are given, the top-protocol is returned. If schemes are given, including srm, the sorting is done all wrong.

```
>>> print(c.list_replicas([did], metalink=True, rse_expression=rse, client_location=l, schemes=None))
...
   <url location="TRIUMF-LCG2_DATADISK" domain="wan" priority="1">srm://srm.triumf.ca:8443/srm/managerv2?SFN=/atlas/atlasdatadisk/rucio/mc15_13TeV/8e/0c/EVNT.04972714._000024.pool.root.1</url>
...

>>> print(c.list_replicas([did], metalink=True, rse_expression=rse, client_location=l, schemes=['davs', 'gsiftp', 'https', 'root', 'srm', 'file']))
...
   <url location="TRIUMF-LCG2_DATADISK" domain="wan" priority="1">root://xrootd.lcg.triumf.ca:1094//atlas/atlasdatadisk/rucio/mc15_13TeV/8e/0c/EVNT.04972714._000024.pool.root.1</url>
   <url location="TRIUMF-LCG2_DATADISK" domain="wan" priority="2">srm://srm.triumf.ca:8443/srm/managerv2?SFN=/atlas/atlasdatadisk/rucio/mc15_13TeV/8e/0c/EVNT.04972714._000024.pool.root.1</url>
   <url location="TRIUMF-LCG2_DATADISK" domain="wan" priority="3">davs://proxy.lcg.triumf.ca:2880/atlas/atlasdatadisk/rucio/mc15_13TeV/8e/0c/EVNT.04972714._000024.pool.root.1</url>
...
```



Discussion:
ok, confirmed the bug. this is not properly taking into account that there are two root doors (one internal, one external) and thus takes the higher priority one of the internal door (thus jumping in front of srm), when it should just ignore it and instead take the priority of the external door (which is left out completely at actual priority=2, since there already is a (bad) root priority assigned)

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
@@ -815,9 +815,10 @@ def _list_replicas(dataset_clause, file_clause, state_clause, show_pfns,
                 if rse not in rse_info:
                     rse_info[rse] = rsemgr.get_rse_info(rse, session=session)
 
-                # assign scheme priorities
-                rse_info[rse]['priority_wan'] = {p['scheme']: p['domains']['wan']['read'] for p in rse_info[rse]['protocols']}
-                rse_info[rse]['priority_lan'] = {p['scheme']: p['domains']['lan']['read'] for p in rse_info[rse]['protocols']}
+                # assign scheme priorities, and don't forget to exclude disabled protocols
+                # 0 in RSE protocol definition = disabled, 1 = highest priority
+                rse_info[rse]['priority_wan'] = {p['scheme']: p['domains']['wan']['read'] for p in rse_info[rse]['protocols'] if p['domains']['wan']['read'] > 0}
+                rse_info[rse]['priority_lan'] = {p['scheme']: p['domains']['lan']['read'] for p in rse_info[rse]['protocols'] if p['domains']['lan']['read'] > 0}
 
                 # select the lan door in autoselect mode, otherwise use the wan door
                 if domain is None:


Test patch:
diff --git a/lib/rucio/tests/test_replica_sorting.py b/lib/rucio/tests/test_replica_sorting.py
--- a/lib/rucio/tests/test_replica_sorting.py
+++ b/lib/rucio/tests/test_replica_sorting.py
@@ -190,6 +190,29 @@ def test_replica_sorting(self):
         # assert_in('domain="wan" priority="6">root://root.blackmesa.com:1409//lambda/complex/mock/58/b5/element_0', ml)
         # assert_not_in('priority="7"', ml)
 
+        # ensure correct handling of disabled protocols
+        add_protocol(self.rse1, {'scheme': 'root',
+                                 'hostname': 'root2.aperture.com',
+                                 'port': 1409,
+                                 'prefix': '//test/chamber/',
+                                 'impl': 'rucio.rse.protocols.xrootd.Default',
+                                 'domains': {
+                                     'lan': {'read': 1, 'write': 1, 'delete': 1},
+                                     'wan': {'read': 0, 'write': 0, 'delete': 0}}})
+
+        ml = self.rc.list_replicas(dids=[{'scope': 'mock',
+                                          'name': f['name'],
+                                          'type': 'FILE'} for f in self.files],
+                                   schemes=['root', 'gsiftp', 'davs'],
+                                   metalink=True,
+                                   client_location={'site': 'BLACKMESA'})
+        assert_in('domain="lan" priority="1" client_extract="false">root://root.blackmesa.com:1409//lambda/complex/mock/58/b5/element_0', ml)
+        assert_in('domain="lan" priority="2" client_extract="false">gsiftp://gsiftp.blackmesa.com:8446/lambda/complex/mock/58/b5/element_0', ml)
+        assert_in('domain="wan" priority="3" client_extract="false">root://root.aperture.com:1409//test/chamber/mock/58/b5/element_0', ml)
+        assert_in('domain="wan" priority="4" client_extract="false">davs://davs.aperture.com:443/test/chamber/mock/58/b5/element_0', ml)
+        assert_in('domain="wan" priority="5" client_extract="false">gsiftp://gsiftp.aperture.com:8446/test/chamber/mock/58/b5/element_0', ml)
+        assert_not_in('priority="6"', ml)
+
         delete_replicas(rse=self.rse1, files=self.files)
         delete_replicas(rse=self.rse2, files=self.files)
         del_rse(self.rse1)