Repository: rucio/rucio

Issue:
New option for rucio list-rse-usage to list usage per account
Motivation
----------
rucio list-rse-usage returns the RSE usage from different sources (Rucio, storage, expired). It would be good to have an option to list the usage per account.



Discussion:
So basically list-account-usage, but instead of per-user account per-rse?
Yes

Resolving patch:
diff --git a/lib/rucio/api/rse.py b/lib/rucio/api/rse.py
--- a/lib/rucio/api/rse.py
+++ b/lib/rucio/api/rse.py
@@ -12,6 +12,7 @@
   - Cedric Serfon, <cedric.serfon@cern.ch>, 2013-2014, 2017
   - Martin Barisits, <martin.barisits@cern.ch>, 2013
   - Thomas Beermann, <thomas.beermann@cern.ch>, 2014
+  - Hannes Hansen, <hannes.jakob.hansen@cern.ch>, 2018
 '''
 
 from rucio.api import permission
@@ -233,8 +234,7 @@ def set_rse_usage(rse, source, used, free, issuer):
     :param free: the free space in bytes.
     :param issuer: The issuer account.
 
-
-    :returns: True if successful, otherwise false.
+    :returns: List of RSE usage data.
     """
     kwargs = {'rse': rse}
     if not permission.has_permission(issuer=issuer, action='set_rse_usage', kwargs=kwargs):
@@ -243,7 +243,7 @@ def set_rse_usage(rse, source, used, free, issuer):
     return rse_module.set_rse_usage(rse=rse, source=source, used=used, free=free)
 
 
-def get_rse_usage(rse, issuer, source=None):
+def get_rse_usage(rse, issuer, source=None, per_account=False):
     """
     get RSE usage information.
 
@@ -253,7 +253,7 @@ def get_rse_usage(rse, issuer, source=None):
 
     :returns: True if successful, otherwise false.
     """
-    return rse_module.get_rse_usage(rse=rse, source=source)
+    return rse_module.get_rse_usage(rse=rse, source=source, per_account=per_account)
 
 
 def list_rse_usage_history(rse, issuer, source=None):
diff --git a/lib/rucio/core/rse.py b/lib/rucio/core/rse.py
--- a/lib/rucio/core/rse.py
+++ b/lib/rucio/core/rse.py
@@ -485,7 +485,7 @@ def set_rse_usage(rse, source, used, free, session=None):
 
 
 @read_session
-def get_rse_usage(rse, source=None, rse_id=None, session=None):
+def get_rse_usage(rse, source=None, rse_id=None, session=None, per_account=False):
     """
     get rse usage information.
 
@@ -493,22 +493,34 @@ def get_rse_usage(rse, source=None, rse_id=None, session=None):
     :param source: The information source, e.g. srm.
     :param rse_id:  The RSE id.
     :param session: The database session in use.
+    :param per_account: Boolean whether the usage should be also calculated per account or not.
 
-    :returns: True if successful, otherwise false.
+    :returns: List of RSE usage data.
     """
     if not rse_id:
         rse_id = get_rse_id(rse, session=session)
 
-    query = session.query(models.RSEUsage).filter_by(rse_id=rse_id)
-    if source:
-        query = query.filter_by(source=source)
-
+    query_rse_usage = session.query(models.RSEUsage).filter_by(rse_id=rse_id)
     usage = list()
-    for row in query:
-        usage.append({'rse': rse, 'source': row.source,
-                      'used': row.used, 'free': row.free,
-                      'total': (row.free or 0) + (row.used or 0),
-                      'updated_at': row.updated_at})
+
+    if source:
+        query_rse_usage = query_rse_usage.filter_by(source=source)
+
+    for row in query_rse_usage:
+        total = (row.free or 0) + (row.used or 0)
+        rse_usage = {'rse': rse, 'source': row.source,
+                     'used': row.used, 'free': row.free,
+                     'total': total,
+                     'updated_at': row.updated_at}
+        if per_account:
+            query_account_usage = session.query(models.AccountUsage).filter_by(rse_id=rse_id)
+            account_usages = []
+            for row in query_account_usage:
+                if row.bytes != 0:
+                    account_usages.append({'used': row.bytes, 'account': row.account, 'percentage': round(float(row.bytes) / float(total) * 100, 2)})
+            account_usages.sort(key=lambda x: x['used'], reverse=True)
+            rse_usage['account_usages'] = account_usages
+        usage.append(rse_usage)
     return usage
 
 
diff --git a/lib/rucio/web/rest/webpy/v1/rse.py b/lib/rucio/web/rest/webpy/v1/rse.py
--- a/lib/rucio/web/rest/webpy/v1/rse.py
+++ b/lib/rucio/web/rest/webpy/v1/rse.py
@@ -579,13 +579,16 @@ def GET(self, rse):
         header('Content-Type', 'application/x-json-stream')
         usage = None
         source = None
+        per_account = False
         if ctx.query:
             params = parse_qs(ctx.query[1:])
             if 'source' in params:
                 source = params['source'][0]
+            if 'per_account' in params:
+                per_account = params['per_account'][0] == 'True'
 
         try:
-            usage = get_rse_usage(rse, issuer=ctx.env.get('issuer'), source=source)
+            usage = get_rse_usage(rse, issuer=ctx.env.get('issuer'), source=source, per_account=per_account)
         except RSENotFound as error:
             raise generate_http_error(404, 'RSENotFound', error.args[0])
         except RucioException as error:


Test patch:
diff --git a/lib/rucio/tests/test_rse.py b/lib/rucio/tests/test_rse.py
--- a/lib/rucio/tests/test_rse.py
+++ b/lib/rucio/tests/test_rse.py
@@ -22,6 +22,7 @@
 # - Wen Guan <wguan.icedew@gmail.com>, 2014-2015
 # - Joaquin Bogado <jbogado@linti.unlp.edu.ar>, 2018
 # - Frank Berghaus <frank.berghaus@cern.ch>, 2018
+# - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2018
 
 from __future__ import print_function
 
@@ -1185,6 +1186,15 @@ def test_update_protocol_wrong_priority(self):
         self.client.delete_protocols(protocol_rse, scheme='MOCK')
         self.client.delete_rse(protocol_rse)
 
+    def test_get_rse_usage(self):
+        """ RSE (CLIENTS): Test getting the RSE usage. """
+        usages = self.client.get_rse_usage(rse='MOCK', filters={'per_account': True})
+        for usage in usages:
+            assert_true(usage['account_usages'])
+        usages = self.client.get_rse_usage(rse='MOCK')
+        for usage in usages:
+            assert_true('account_usages' not in usage)
+
     def test_set_rse_usage(self):
         """ RSE (CLIENTS): Test the update of RSE usage."""
         assert_equal(self.client.set_rse_usage(rse='MOCK', source='srm', used=999200, free=800), True)