Repository: rucio/rucio

Issue:
Internal server error when listing another account's quota
Motivation
----------
I see this with both 1.22.5 and 1.22.7 clients:

```
> rucio list-account-usage cclement
+---------------------------------+------------+-----------+--------------+
| RSE                             | USAGE      | LIMIT     | QUOTA LEFT   |
|---------------------------------+------------+-----------+--------------|
| CERN-PROD_DET-TILE              | 130.917 GB | 0.000 B   | 0.000 B      |
| SE-SNIC-T2_STHLM_LOCALGROUPDISK | 8.984 TB   | 18.802 TB | 9.817 TB     |
| TRIUMF-LCG2_PERF-JETS           | 160.376 MB | 0.000 B   | 0.000 B      |
+---------------------------------+------------+-----------+--------------+
2020-06-04 18:44:32,014	ERROR	An unknown exception occurred.
Details: no error information passed (http status code: 500 ('internal_server_error', 'server_error', '/o\\', '\xe2\x9c\x97'))
```
however it's fine for my own quota and if I change my account to ddmadmin I don't get the error for other people's quotas, so I suspect it's a permission error when showing global quotas.

Modification
------------
Show global quotas instead of server error.




Discussion:

Resolving patch:
diff --git a/lib/rucio/client/accountclient.py b/lib/rucio/client/accountclient.py
--- a/lib/rucio/client/accountclient.py
+++ b/lib/rucio/client/accountclient.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2018 CERN for the benefit of the ATLAS collaboration.
+# Copyright 2012-2020 CERN for the benefit of the ATLAS collaboration.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -23,9 +23,15 @@
 # - Ralph Vigne <ralph.vigne@cern.ch>, 2015
 # - Joaquin Bogado <jbogado@linti.unlp.edu.ar>, 2015
 # - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2018-2019
+# - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2020
 #
 # PY3K COMPATIBLE
 
+try:
+    from urllib import quote_plus
+except ImportError:
+    from urllib.parse import quote_plus
+
 from json import dumps
 from requests.status_codes import codes
 
@@ -267,7 +273,7 @@ def get_global_account_limit(self, account, rse_expression):
         :param rse_expression: The rse expression.
         """
 
-        path = '/'.join([self.ACCOUNTS_BASEURL, account, 'limits', 'global', rse_expression])
+        path = '/'.join([self.ACCOUNTS_BASEURL, account, 'limits', 'global', quote_plus(rse_expression)])
         url = build_url(choice(self.list_hosts), path=path)
         res = self._send_request(url, type='GET')
         if res.status_code == codes.ok:
@@ -348,7 +354,7 @@ def get_global_account_usage(self, account, rse_expression=None):
         :param rse_expression: The rse expression.
         """
         if rse_expression:
-            path = '/'.join([self.ACCOUNTS_BASEURL, account, 'usage', 'global', rse_expression])
+            path = '/'.join([self.ACCOUNTS_BASEURL, account, 'usage', 'global', quote_plus(rse_expression)])
         else:
             path = '/'.join([self.ACCOUNTS_BASEURL, account, 'usage', 'global'])
         url = build_url(choice(self.list_hosts), path=path)
diff --git a/lib/rucio/core/permission/generic.py b/lib/rucio/core/permission/generic.py
--- a/lib/rucio/core/permission/generic.py
+++ b/lib/rucio/core/permission/generic.py
@@ -1,4 +1,4 @@
-# Copyright 2016-2018 CERN for the benefit of the ATLAS collaboration.
+# Copyright 2016-2020 CERN for the benefit of the ATLAS collaboration.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 # - Andrew Lister, <andrew.lister@stfc.ac.uk>, 2019
 # - Ruturaj Gujar, <ruturaj.gujar23@gmail.com>, 2019
 # - Eric Vaandering, <ewv@fnal.gov>, 2020
+# - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2020
 #
 # PY3K COMPATIBLE
 
@@ -865,7 +866,7 @@ def perm_get_global_account_usage(issuer, kwargs):
         if kv['key'].startswith('country-') and kv['value'] == 'admin':
             admin_in_country.add(kv['key'].partition('-')[2])
     resolved_rse_countries = {list_rse_attributes(rse_id=rse['rse_id']).get('country')
-                              for rse in parse_expression(kwargs['rse_exp'])}
+                              for rse in parse_expression(kwargs['rse_expression'])}
 
     if resolved_rse_countries.issubset(admin_in_country):
         return True


Test patch:
diff --git a/lib/rucio/tests/test_bin_rucio.py b/lib/rucio/tests/test_bin_rucio.py
--- a/lib/rucio/tests/test_bin_rucio.py
+++ b/lib/rucio/tests/test_bin_rucio.py
@@ -1,4 +1,4 @@
-# Copyright 2012-2019 CERN for the benefit of the ATLAS collaboration.
+# Copyright 2012-2020 CERN for the benefit of the ATLAS collaboration.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
 # - Tobias Wegner <twegner@cern.ch>, 2018
 # - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2018-2019
 # - Andrew Lister <andrew.lister@stfc.ac.uk>, 2019
+# - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2020
 #
 # PY3K COMPATIBLE
 
@@ -1280,11 +1281,10 @@ def test_list_account_usage(self):
             abacus_account.run(once=True)
             cmd = 'rucio list-account-usage {0}'.format(account)
             exitcode, out, err = execute(cmd)
-            print(out)
-
             nose.tools.assert_not_equal(re.search('.*{0}.*{1}.*{2}.*{3}'.format(rse, usage, local_limit, local_left), out), None)
             nose.tools.assert_not_equal(re.search('.*{0}.*{1}.*{2}.*{3}'.format(rse_exp, usage, global_limit, global_left), out), None)
             cmd = 'rucio list-account-usage --rse {0} {1}'.format(rse, account)
+            exitcode, out, err = execute(cmd)
             nose.tools.assert_not_equal(re.search('.*{0}.*{1}.*{2}.*{3}'.format(rse, usage, local_limit, local_left), out), None)
             nose.tools.assert_not_equal(re.search('.*{0}.*{1}.*{2}.*{3}'.format(rse_exp, usage, global_limit, global_left), out), None)
             self.account_client.set_local_account_limit(account, rse, -1)