Repository: rucio/rucio

Issue:
RUCIO_ACCOUNT should override the Rucio configuration file
Motivation
----------
Use of the RUCIO_ACCOUNT environment variable should override the account specified in the Rucio configuration file.





Discussion:

Resolving patch:
diff --git a/lib/rucio/client/baseclient.py b/lib/rucio/client/baseclient.py
--- a/lib/rucio/client/baseclient.py
+++ b/lib/rucio/client/baseclient.py
@@ -215,12 +215,12 @@ def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None,
         if account is None:
             LOG.debug('no account passed. Trying to get it from the config file.')
             try:
-                self.account = config_get('client', 'account')
-            except (NoOptionError, NoSectionError):
+                self.account = environ['RUCIO_ACCOUNT']
+            except KeyError:
                 try:
-                    self.account = environ['RUCIO_ACCOUNT']
-                except KeyError:
-                    raise MissingClientParameter('Option \'account\' cannot be found in config file and RUCIO_ACCOUNT is not set.')
+                    self.account = config_get('client', 'account')
+                except (NoOptionError, NoSectionError):
+                        raise MissingClientParameter('Option \'account\' cannot be found in config file and RUCIO_ACCOUNT is not set.')
 
         token_path = self.TOKEN_PATH_PREFIX + self.account
         self.token_file = token_path + '/' + self.TOKEN_PREFIX + self.account
diff --git a/lib/rucio/core/rule.py b/lib/rucio/core/rule.py
--- a/lib/rucio/core/rule.py
+++ b/lib/rucio/core/rule.py
@@ -21,6 +21,7 @@
 # - Joaquin Bogado <jbogado@linti.unlp.edu.ar>, 2014-2018
 # - Thomas Beermann <thomas.beermann@cern.ch>, 2014-2015
 # - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2018-2019
+# - Dimitrios Christidis <dimitrios.christidis@cern.ch>, 2019
 #
 # PY3K COMPATIBLE
 
@@ -1196,7 +1197,7 @@ def update_rule(rule_id, options, session=None):
     :raises:            RuleNotFound if no Rule can be found, InputValidationError if invalid option is used, ScratchDiskLifetimeConflict if wrong ScratchDiskLifetime is used.
     """
 
-    valid_options = ['locked', 'lifetime', 'account', 'state', 'activity', 'source_replica_expression', 'cancel_requests', 'priority', 'child_rule_id', 'eol_at', 'meta']
+    valid_options = ['locked', 'lifetime', 'account', 'state', 'activity', 'source_replica_expression', 'cancel_requests', 'priority', 'child_rule_id', 'eol_at', 'meta', 'purge_replicas']
 
     for key in options:
         if key not in valid_options:
diff --git a/lib/rucio/daemons/atropos/atropos.py b/lib/rucio/daemons/atropos/atropos.py
--- a/lib/rucio/daemons/atropos/atropos.py
+++ b/lib/rucio/daemons/atropos/atropos.py
@@ -23,6 +23,7 @@
 import datetime
 import logging
 import os
+import random
 import socket
 import threading
 import time
@@ -50,7 +51,7 @@
 
 
 def atropos(thread, bulk, date_check, dry_run=True, grace_period=86400,
-            once=True, unlock=False):
+            once=True, unlock=False, spread_period=0):
     """
     Creates an Atropos Worker that gets a list of rules which have an eol_at expired and delete them.
 
@@ -75,6 +76,7 @@ def atropos(thread, bulk, date_check, dry_run=True, grace_period=86400,
     logging.debug(prepend_str + 'Starting worker')
     summary = {}
     lifetime_exceptions = {}
+    rand = random.Random(hb['assign_thread'])
     for excep in rucio.core.lifetime_exception.list_exceptions(exception_id=None, states=[LifetimeExceptionsState.APPROVED, ], session=None):
         if '%s:%s' % (excep['scope'], excep['name']) not in lifetime_exceptions:
             lifetime_exceptions['%s:%s' % (excep['scope'], excep['name'])] = excep['expires_at']
@@ -145,8 +147,9 @@ def atropos(thread, bulk, date_check, dry_run=True, grace_period=86400,
                         if no_locks:
                             logging.warning(prepend_str + 'Cannot find a lock for rule %s on DID %s' % (rule.id, did))
                         if not dry_run:
-                            logging.info(prepend_str + 'Setting %s seconds lifetime for rule %s' % (grace_period, rule.id))
-                            options = {'lifetime': grace_period}
+                            lifetime = grace_period + rand.randrange(spread_period + 1)
+                            logging.info(prepend_str + 'Setting %s seconds lifetime for rule %s' % (lifetime, rule.id))
+                            options = {'lifetime': lifetime}
                             if rule.locked and unlock:
                                 logging.info(prepend_str + 'Unlocking rule %s', rule.id)
                                 options['locked'] = False
@@ -182,7 +185,7 @@ def atropos(thread, bulk, date_check, dry_run=True, grace_period=86400,
 
 
 def run(threads=1, bulk=100, date_check=None, dry_run=True, grace_period=86400,
-        once=True, unlock=False):
+        once=True, unlock=False, spread_period=0):
     """
     Starts up the atropos threads.
     """
@@ -199,7 +202,8 @@ def run(threads=1, bulk=100, date_check=None, dry_run=True, grace_period=86400,
                                                             'dry_run': dry_run,
                                                             'grace_period': grace_period,
                                                             'bulk': bulk,
-                                                            'unlock': unlock}) for i in range(0, threads)]
+                                                            'unlock': unlock,
+                                                            'spread_period': spread_period}) for i in range(0, threads)]
     [t.start() for t in thread_list]
 
     logging.info('waiting for interrupts')


Test patch: