Repository: rucio/rucio

Issue:
Cannot update the account of a rule
Motivation
----------

```
$ rucio update-rule --account ACCOUNT RULEID
2020-06-30 12:37:21,205 ERROR   An unknown exception occurred.
Details: Badly formatted rule id (RULEID)
```

```
[Tue Jun 30 12:37:21.201390 2020] [:error] [pid 21435:tid 139789737174784] (rucio.common.exception.InvalidType) Provided type is considered invalid.
[Tue Jun 30 12:37:21.201461 2020] [:error] [pid 21435:tid 139789737174784] Details: Cannot insert to db. Expected InternalAccount, got string type.
[Tue Jun 30 12:37:21.201465 2020] [:error] [pid 21435:tid 139789737174784] [SQL: SELECT atlas_rucio_accounts_acc_1, atlas_rucio_accounts_acc_2, atlas_rucio_accounts_sta_3, atlas_rucio_accounts_ema_4, atlas_rucio_accounts_sus_5, atlas_rucio_accounts_del_6, atlas_rucio_accounts_upd_7, atlas_rucio_accounts_cre_8 
[Tue Jun 30 12:37:21.201469 2020] [:error] [pid 21435:tid 139789737174784] FROM (SELECT atlas_rucio.accounts.account AS atlas_rucio_accounts_acc_1, atlas_rucio.accounts.account_type AS atlas_rucio_accounts_acc_2, atlas_rucio.accounts.status AS atlas_rucio_accounts_sta_3, atlas_rucio.accounts.email AS atlas_rucio_accounts_ema_4, atlas_rucio.accounts.suspended_at AS atlas_rucio_accounts_sus_5, atlas_rucio.accounts.deleted_at AS atlas_rucio_accounts_del_6, atlas_rucio.accounts.updated_at AS atlas_rucio_accounts_upd_7, atlas_rucio.accounts.created_at AS atlas_rucio_accounts_cre_8 
[Tue Jun 30 12:37:21.201472 2020] [:error] [pid 21435:tid 139789737174784] FROM atlas_rucio.accounts 
[Tue Jun 30 12:37:21.201475 2020] [:error] [pid 21435:tid 139789737174784] WHERE atlas_rucio.accounts.account = :account_1) 
[Tue Jun 30 12:37:21.201477 2020] [:error] [pid 21435:tid 139789737174784] WHERE ROWNUM <= :param_1]
[Tue Jun 30 12:37:21.201482 2020] [:error] [pid 21435:tid 139789737174784] [parameters: [{}]]
```

Modification
------------





Discussion:
Update rule uses a dictionary of options which can contain an account, but we missed this when implementing InternalAccount types as it didn't appear as an argument to the API function directly.

Have submitted a PR which fixes this, but I also want to check we haven't missed anywhere else doing a similar thing, and possibly add a test that explicitly tests this (currently `test_rule.py` adds a rule with the option `account` but it does this directly via the core, so this test didn't expose the missing conversion as that happens at the API layer).
Found a couple of places where an account could be included in a dictionary representing a DID, and passed through the API layer without conversion to InternalAccount. Normally this account is simply not included and the issuer is used instead at the core without issue. However if an account was provided it would cause a similar error to this, so I added checks to make sure any optional accounts are now converted.
Also modified some existing tests so they test these new conversions.

Resolving patch:
diff --git a/lib/rucio/api/did.py b/lib/rucio/api/did.py
--- a/lib/rucio/api/did.py
+++ b/lib/rucio/api/did.py
@@ -16,6 +16,7 @@
   - Andrew Lister, <andrew.lister@stfc.ac.uk>, 2019
   - Ruturaj Gujar, <ruturaj.gujar23@gmail.com>, 2019
   - Eli Chadwick, <eli.chadwick@stfc.ac.uk>, 2020
+  - Patrick Austin <patrick.austin@stfc.ac.uk>, 2020
 
   PY3K COMPATIBLE
 '''
@@ -174,6 +175,8 @@ def add_dids(dids, issuer, vo='def'):
     issuer = InternalAccount(issuer, vo=vo)
     for d in dids:
         d['scope'] = InternalScope(d['scope'], vo=vo)
+        if 'account' in d.keys():
+            d['account'] = InternalAccount(d['account'], vo=vo)
         if 'dids' in d.keys():
             for child in d['dids']:
                 child['scope'] = InternalScope(child['scope'], vo=vo)
@@ -206,6 +209,8 @@ def attach_dids(scope, name, attachment, issuer, vo='def'):
         attachment['account'] = InternalAccount(attachment['account'], vo=vo)
     for d in attachment['dids']:
         d['scope'] = InternalScope(d['scope'], vo=vo)
+        if 'account' in d.keys():
+            d['account'] = InternalAccount(d['account'], vo=vo)
 
     if rse_id is not None:
         dids = did.attach_dids(scope=scope, name=name, dids=attachment['dids'],
@@ -243,6 +248,8 @@ def attach_dids_to_dids(attachments, issuer, ignore_duplicate=False, vo='def'):
         attachment['scope'] = InternalScope(attachment['scope'], vo=vo)
         for d in attachment['dids']:
             d['scope'] = InternalScope(d['scope'], vo=vo)
+            if 'account' in d.keys():
+                d['account'] = InternalAccount(d['account'], vo=vo)
 
     return did.attach_dids_to_dids(attachments=attachments, account=issuer,
                                    ignore_duplicate=ignore_duplicate)
diff --git a/lib/rucio/api/rule.py b/lib/rucio/api/rule.py
--- a/lib/rucio/api/rule.py
+++ b/lib/rucio/api/rule.py
@@ -228,6 +228,8 @@ def update_replication_rule(rule_id, options, issuer, vo='def'):
     else:
         if not has_permission(issuer=issuer, vo=vo, action='update_rule', kwargs=kwargs):
             raise AccessDenied('Account %s can not update this replication rule.' % (issuer))
+        if 'account' in options:
+            options['account'] = InternalAccount(options['account'], vo=vo)
         rule.update_rule(rule_id=rule_id, options=options)
 
 


Test patch:
diff --git a/lib/rucio/tests/test_did.py b/lib/rucio/tests/test_did.py
--- a/lib/rucio/tests/test_did.py
+++ b/lib/rucio/tests/test_did.py
@@ -641,7 +641,7 @@ def test_add_datasets(self):
         tmp_scope = 'mock'
         dsns = list()
         for i in range(500):
-            tmp_dsn = {'name': 'dsn_%s' % generate_uuid(), 'scope': tmp_scope, 'meta': {'project': 'data13_hip'}}
+            tmp_dsn = {'name': 'dsn_%s' % generate_uuid(), 'scope': tmp_scope, 'meta': {'project': 'data13_hip'}, 'account': 'root'}
             dsns.append(tmp_dsn)
         self.did_client.add_datasets(dsns)
 
diff --git a/lib/rucio/tests/test_rule.py b/lib/rucio/tests/test_rule.py
--- a/lib/rucio/tests/test_rule.py
+++ b/lib/rucio/tests/test_rule.py
@@ -566,7 +566,7 @@ def test_account_counter_rule_update(self):
         account_counter_before_1 = get_usage(self.rse1_id, self.jdoe)
         account_counter_before_2 = get_usage(self.rse1_id, self.root)
 
-        update_rule(rule_id, {'account': self.root})
+        rucio.api.rule.update_replication_rule(rule_id, {'account': 'root'}, issuer='root', **self.vo)
         account_update(once=True)
 
         # Check if the counter has been updated correctly