We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Problems with accessing the REST API and the WebUI in the development docker container
Motivation
----------
It is not possible to access the REST API under 'https://localhost/proxy' and to access the Web UI under 'https://localhost/ui/' within the docker container build with the Dockerfile under /etc/docker/dev.

Modification
------------
A few modifications can be copied from the demo environment.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/rucio/core/lifetime_exception.py`
Content:
```
1 #  Copyright European Organization for Nuclear Research (CERN)
2 #
3 #  Licensed under the Apache License, Version 2.0 (the "License");
4 #  You may not use this file except in compliance with the License.
5 #  You may obtain a copy of the License at
6 #  http://www.apache.org/licenses/LICENSE-2.0
7 #
8 #  Authors:
9 #  - Cedric Serfon, <cedric.serfon@cern.ch>, 2016-2018
10 
11 from re import match
12 from datetime import datetime, timedelta
13 
14 from sqlalchemy import or_
15 from sqlalchemy.exc import IntegrityError
16 from sqlalchemy.orm.exc import NoResultFound
17 
18 from rucio.common.exception import ConfigNotFound, RucioException, LifetimeExceptionDuplicate, LifetimeExceptionNotFound, UnsupportedOperation
19 from rucio.common.utils import generate_uuid, str_to_date
20 import rucio.common.policy
21 from rucio.core.config import get
22 from rucio.core.message import add_message
23 from rucio.core.rse import list_rse_attributes
24 
25 from rucio.db.sqla import models
26 from rucio.db.sqla.constants import DIDType, LifetimeExceptionsState
27 from rucio.db.sqla.session import transactional_session, stream_session, read_session
28 
29 
30 @stream_session
31 def list_exceptions(exception_id, states, session=None):
32     """
33     List exceptions to Lifetime Model.
34 
35     :param exception_id: The id of the exception
36     :param states:       The states to filter
37     :param session:      The database session in use.
38     """
39 
40     state_clause = []
41     if states:
42         state_clause = [models.LifetimeExceptions.state == state for state in states]
43 
44     query = session.query(models.LifetimeExceptions.id,
45                           models.LifetimeExceptions.scope, models.LifetimeExceptions.name,
46                           models.LifetimeExceptions.did_type,
47                           models.LifetimeExceptions.account,
48                           models.LifetimeExceptions.pattern,
49                           models.LifetimeExceptions.comments,
50                           models.LifetimeExceptions.state,
51                           models.LifetimeExceptions.expires_at,
52                           models.LifetimeExceptions.created_at)
53     if state_clause != []:
54         query = query.filter(or_(*state_clause))
55     if exception_id:
56         query = query.filter(id=exception_id)
57 
58     for exception in query.yield_per(5):
59         yield {'id': exception.id, 'scope': exception.scope, 'name': exception.name,
60                'did_type': exception.did_type, 'account': exception.account,
61                'pattern': exception.pattern, 'comments': exception.comments,
62                'state': exception.state, 'created_at': exception.created_at,
63                'expires_at': exception.expires_at}
64 
65 
66 @transactional_session
67 def add_exception(dids, account, pattern, comments, expires_at, session=None):
68     """
69     Add exceptions to Lifetime Model.
70 
71     :param dids:        The list of dids
72     :param account:     The account of the requester.
73     :param pattern:     The account.
74     :param comments:    The comments associated to the exception.
75     :param expires_at:  The expiration date of the exception.
76     :param session:     The database session in use.
77 
78     returns:            The id of the exception.
79     """
80     exception_id = generate_uuid()
81     text = 'Account %s requested a lifetime extension for a list of DIDs that can be found below\n' % account
82     reason = comments
83     volume = None
84     lifetime = None
85     if comments.find('||||') > -1:
86         reason, volume = comments.split('||||')
87     text += 'The reason for the extension is "%s"\n' % reason
88     text += 'It represents %s datasets\n' % len(dids)
89     if volume:
90         text += 'The estimated physical volume is %s\n' % volume
91     if expires_at and (isinstance(expires_at, str) or isinstance(expires_at, unicode)):
92         lifetime = str_to_date(expires_at)
93         text += 'The lifetime exception should expires on %s\n' % str(expires_at)
94     elif isinstance(expires_at, datetime):
95         lifetime = expires_at
96         text += 'The lifetime exception should expires on %s\n' % str(expires_at)
97     text += 'Link to approve or reject this request can be found at the end of the mail\n'
98     text += '\n'
99     text += 'DIDTYPE SCOPE NAME\n'
100     text += '\n'
101     truncated_message = False
102     for did in dids:
103         did_type = None
104         if 'did_type' in did:
105             if isinstance(did['did_type'], str) or isinstance(did['did_type'], unicode):
106                 did_type = DIDType.from_sym(did['did_type'])
107             else:
108                 did_type = did['did_type']
109         new_exception = models.LifetimeExceptions(id=exception_id, scope=did['scope'], name=did['name'], did_type=did_type,
110                                                   account=account, pattern=pattern, comments=reason, state=LifetimeExceptionsState.WAITING, expires_at=lifetime)
111         if len(text) < 3000:
112             text += '%s %s %s\n' % (str(did_type), did['scope'], did['name'])
113         else:
114             truncated_message = True
115         try:
116             new_exception.save(session=session, flush=False)
117         except IntegrityError as error:
118             if match('.*ORA-00001.*', str(error.args[0]))\
119                or match('.*IntegrityError.*UNIQUE constraint failed.*', str(error.args[0]))\
120                or match('.*1062.*Duplicate entry.*for key.*', str(error.args[0]))\
121                or match('.*sqlite3.IntegrityError.*are not unique.*', error.args[0]):
122                 raise LifetimeExceptionDuplicate()
123             raise RucioException(error.args[0])
124     if truncated_message:
125         text += '...\n'
126         text += 'List too long. Truncated\n'
127     text += '\n'
128     text += 'Approve:   https://rucio-ui.cern.ch/lifetime_exception?id=%s&action=approve\n' % str(exception_id)
129     text += 'Deny:      https://rucio-ui.cern.ch/lifetime_exception?id=%s&action=deny\n' % str(exception_id)
130     approvers_email = []
131     try:
132         approvers_email = get('lifetime_model', 'approvers_email', session=session)
133         approvers_email = approvers_email.split(',')  # pylint: disable=no-member
134     except ConfigNotFound:
135         approvers_email = []
136 
137     add_message(event_type='email',
138                 payload={'body': text, 'to': approvers_email,
139                          'subject': '[RUCIO] Request to approve lifetime exception %s' % str(exception_id)},
140                 session=session)
141     return exception_id
142 
143 
144 @transactional_session
145 def update_exception(exception_id, state, session=None):
146     """
147     Update exceptions state to Lifetime Model.
148 
149     :param exception_id:   The id of the exception
150     :param state:          The states to filter
151     :param session:        The database session in use.
152     """
153     query = session.query(models.LifetimeExceptions).filter_by(id=exception_id)
154     try:
155         query.one()
156     except NoResultFound:
157         raise LifetimeExceptionNotFound
158 
159     if state in [LifetimeExceptionsState.APPROVED, LifetimeExceptionsState.REJECTED]:
160         query.update({'state': state, 'updated_at': datetime.utcnow()}, synchronize_session=False)
161     else:
162         raise UnsupportedOperation
163 
164 
165 @read_session
166 def define_eol(scope, name, rses, session=None):
167     """
168     ATLAS policy for rules on SCRATCHDISK
169 
170     :param scope:    Scope of the DID.
171     :param name:     Name of the DID.
172     :param rses:     List of RSEs.
173     :param session:  The database session in use.
174     """
175     policy = rucio.common.policy.get_policy()
176     if policy != 'atlas':
177         return None
178 
179     # Check if on ATLAS managed space
180     if [rse for rse in rses if list_rse_attributes(rse=None, rse_id=rse['id'], session=session).get('type') in ['LOCALGROUPDISK', 'LOCALGROUPTAPE', 'GROUPDISK', 'GROUPTAPE']]:
181         return None
182     # Now check the lifetime policy
183     try:
184         did = session.query(models.DataIdentifier).filter(models.DataIdentifier.scope == scope,
185                                                           models.DataIdentifier.name == name).one()
186     except NoResultFound:
187         return None
188     policy_dict = rucio.common.policy.get_lifetime_policy()
189     did_type = 'other'
190     if scope.startswith('mc'):
191         did_type = 'mc'
192     elif scope.startswith('data'):
193         did_type = 'data'
194     elif scope.startswith('valid'):
195         did_type = 'valid'
196     else:
197         did_type = 'other'
198     for policy in policy_dict[did_type]:
199         if 'exclude' in policy:
200             to_exclude = False
201             for key in policy['exclude']:
202                 meta_key = None
203                 if key not in ['datatype', 'project', ]:
204                     if key == 'stream':
205                         meta_key = 'stream_name'
206                     elif key == 'tags':
207                         meta_key = 'version'
208                 else:
209                     meta_key = key
210                 values = policy['exclude'][key]
211                 for value in values:
212                     value = value.replace('%', '.*')
213                     if meta_key and did[meta_key] and value and match(value, did[meta_key]):
214                         to_exclude = True
215                         break
216                 if to_exclude:
217                     break
218             if to_exclude:
219                 continue
220         if 'include' in policy:
221             match_policy = True
222             for key in policy['include']:
223                 meta_key = None
224                 if key not in ['datatype', 'project', ]:
225                     if key == 'stream':
226                         meta_key = 'stream_name'
227                     elif key == 'tags':
228                         meta_key = 'version'
229                     else:
230                         continue
231                 else:
232                     meta_key = key
233                 values = policy['include'][key]
234                 to_keep = False
235                 for value in values:
236                     value = value.replace('%', '.*')
237                     if meta_key and did[meta_key] and value and match(value, did[meta_key]):
238                         to_keep = True
239                         break
240                 match_policy = match_policy and to_keep
241                 if not to_keep:
242                     match_policy = False
243                     break
244             if match_policy:
245                 if int(policy['age']) >= 12:
246                     years = int(int(policy['age']) / 12)
247                     months = int(policy['age']) - years * 12
248                     lifetime_value = 365 * years + 30 * months
249                 else:
250                     lifetime_value = int(policy['age']) * 30
251                 if int(policy['extension']) >= 12:
252                     years = int(int(policy['extension']) / 12)
253                     months = int(policy['extension']) - years * 12
254                     extension = 365 * years + 30 * months
255                 else:
256                     extension = int(policy['extension']) * 30
257 
258                 default_eol_at = did.created_at + timedelta(days=lifetime_value)
259                 eol_at = default_eol_at
260                 if did.accessed_at:
261                     eol_at = did.accessed_at + timedelta(days=extension)
262                     if eol_at < default_eol_at:
263                         eol_at = default_eol_at
264                 return eol_at
265     return None
266
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/rucio/core/lifetime_exception.py b/lib/rucio/core/lifetime_exception.py
--- a/lib/rucio/core/lifetime_exception.py
+++ b/lib/rucio/core/lifetime_exception.py
@@ -7,6 +7,7 @@
 #
 #  Authors:
 #  - Cedric Serfon, <cedric.serfon@cern.ch>, 2016-2018
+#  - Dimitrios Christidis, <dimitrios.christidis@cern.ch> 2018
 
 from re import match
 from datetime import datetime, timedelta
@@ -152,7 +153,7 @@
     """
     query = session.query(models.LifetimeExceptions).filter_by(id=exception_id)
     try:
-        query.one()
+        query.first()
     except NoResultFound:
         raise LifetimeExceptionNotFound

{"golden_diff": "diff --git a/lib/rucio/core/lifetime_exception.py b/lib/rucio/core/lifetime_exception.py\n--- a/lib/rucio/core/lifetime_exception.py\n+++ b/lib/rucio/core/lifetime_exception.py\n@@ -7,6 +7,7 @@\n #\n #  Authors:\n #  - Cedric Serfon, <cedric.serfon@cern.ch>, 2016-2018\n+#  - Dimitrios Christidis, <dimitrios.christidis@cern.ch> 2018\n \n from re import match\n from datetime import datetime, timedelta\n@@ -152,7 +153,7 @@\n     \"\"\"\n     query = session.query(models.LifetimeExceptions).filter_by(id=exception_id)\n     try:\n-        query.one()\n+        query.first()\n     except NoResultFound:\n         raise LifetimeExceptionNotFound\n", "issue": "Problems with accessing the REST API and the WebUI in the development docker container\nMotivation\r\n----------\r\nIt is not possible to access the REST API under 'https://localhost/proxy' and to access the Web UI under 'https://localhost/ui/' within the docker container build with the Dockerfile under /etc/docker/dev.\r\n\r\nModification\r\n------------\r\nA few modifications can be copied from the demo environment.\n", "before_files": [{"content": "#  Copyright European Organization for Nuclear Research (CERN)\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  You may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#  http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Authors:\n#  - Cedric Serfon, <cedric.serfon@cern.ch>, 2016-2018\n\nfrom re import match\nfrom datetime import datetime, timedelta\n\nfrom sqlalchemy import or_\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom rucio.common.exception import ConfigNotFound, RucioException, LifetimeExceptionDuplicate, LifetimeExceptionNotFound, UnsupportedOperation\nfrom rucio.common.utils import generate_uuid, str_to_date\nimport rucio.common.policy\nfrom rucio.core.config import get\nfrom rucio.core.message import add_message\nfrom rucio.core.rse import list_rse_attributes\n\nfrom rucio.db.sqla import models\nfrom rucio.db.sqla.constants import DIDType, LifetimeExceptionsState\nfrom rucio.db.sqla.session import transactional_session, stream_session, read_session\n\n\n@stream_session\ndef list_exceptions(exception_id, states, session=None):\n    \"\"\"\n    List exceptions to Lifetime Model.\n\n    :param exception_id: The id of the exception\n    :param states:       The states to filter\n    :param session:      The database session in use.\n    \"\"\"\n\n    state_clause = []\n    if states:\n        state_clause = [models.LifetimeExceptions.state == state for state in states]\n\n    query = session.query(models.LifetimeExceptions.id,\n                          models.LifetimeExceptions.scope, models.LifetimeExceptions.name,\n                          models.LifetimeExceptions.did_type,\n                          models.LifetimeExceptions.account,\n                          models.LifetimeExceptions.pattern,\n                          models.LifetimeExceptions.comments,\n                          models.LifetimeExceptions.state,\n                          models.LifetimeExceptions.expires_at,\n                          models.LifetimeExceptions.created_at)\n    if state_clause != []:\n        query = query.filter(or_(*state_clause))\n    if exception_id:\n        query = query.filter(id=exception_id)\n\n    for exception in query.yield_per(5):\n        yield {'id': exception.id, 'scope': exception.scope, 'name': exception.name,\n               'did_type': exception.did_type, 'account': exception.account,\n               'pattern': exception.pattern, 'comments': exception.comments,\n               'state': exception.state, 'created_at': exception.created_at,\n               'expires_at': exception.expires_at}\n\n\n@transactional_session\ndef add_exception(dids, account, pattern, comments, expires_at, session=None):\n    \"\"\"\n    Add exceptions to Lifetime Model.\n\n    :param dids:        The list of dids\n    :param account:     The account of the requester.\n    :param pattern:     The account.\n    :param comments:    The comments associated to the exception.\n    :param expires_at:  The expiration date of the exception.\n    :param session:     The database session in use.\n\n    returns:            The id of the exception.\n    \"\"\"\n    exception_id = generate_uuid()\n    text = 'Account %s requested a lifetime extension for a list of DIDs that can be found below\\n' % account\n    reason = comments\n    volume = None\n    lifetime = None\n    if comments.find('||||') > -1:\n        reason, volume = comments.split('||||')\n    text += 'The reason for the extension is \"%s\"\\n' % reason\n    text += 'It represents %s datasets\\n' % len(dids)\n    if volume:\n        text += 'The estimated physical volume is %s\\n' % volume\n    if expires_at and (isinstance(expires_at, str) or isinstance(expires_at, unicode)):\n        lifetime = str_to_date(expires_at)\n        text += 'The lifetime exception should expires on %s\\n' % str(expires_at)\n    elif isinstance(expires_at, datetime):\n        lifetime = expires_at\n        text += 'The lifetime exception should expires on %s\\n' % str(expires_at)\n    text += 'Link to approve or reject this request can be found at the end of the mail\\n'\n    text += '\\n'\n    text += 'DIDTYPE SCOPE NAME\\n'\n    text += '\\n'\n    truncated_message = False\n    for did in dids:\n        did_type = None\n        if 'did_type' in did:\n            if isinstance(did['did_type'], str) or isinstance(did['did_type'], unicode):\n                did_type = DIDType.from_sym(did['did_type'])\n            else:\n                did_type = did['did_type']\n        new_exception = models.LifetimeExceptions(id=exception_id, scope=did['scope'], name=did['name'], did_type=did_type,\n                                                  account=account, pattern=pattern, comments=reason, state=LifetimeExceptionsState.WAITING, expires_at=lifetime)\n        if len(text) < 3000:\n            text += '%s %s %s\\n' % (str(did_type), did['scope'], did['name'])\n        else:\n            truncated_message = True\n        try:\n            new_exception.save(session=session, flush=False)\n        except IntegrityError as error:\n            if match('.*ORA-00001.*', str(error.args[0]))\\\n               or match('.*IntegrityError.*UNIQUE constraint failed.*', str(error.args[0]))\\\n               or match('.*1062.*Duplicate entry.*for key.*', str(error.args[0]))\\\n               or match('.*sqlite3.IntegrityError.*are not unique.*', error.args[0]):\n                raise LifetimeExceptionDuplicate()\n            raise RucioException(error.args[0])\n    if truncated_message:\n        text += '...\\n'\n        text += 'List too long. Truncated\\n'\n    text += '\\n'\n    text += 'Approve:   https://rucio-ui.cern.ch/lifetime_exception?id=%s&action=approve\\n' % str(exception_id)\n    text += 'Deny:      https://rucio-ui.cern.ch/lifetime_exception?id=%s&action=deny\\n' % str(exception_id)\n    approvers_email = []\n    try:\n        approvers_email = get('lifetime_model', 'approvers_email', session=session)\n        approvers_email = approvers_email.split(',')  # pylint: disable=no-member\n    except ConfigNotFound:\n        approvers_email = []\n\n    add_message(event_type='email',\n                payload={'body': text, 'to': approvers_email,\n                         'subject': '[RUCIO] Request to approve lifetime exception %s' % str(exception_id)},\n                session=session)\n    return exception_id\n\n\n@transactional_session\ndef update_exception(exception_id, state, session=None):\n    \"\"\"\n    Update exceptions state to Lifetime Model.\n\n    :param exception_id:   The id of the exception\n    :param state:          The states to filter\n    :param session:        The database session in use.\n    \"\"\"\n    query = session.query(models.LifetimeExceptions).filter_by(id=exception_id)\n    try:\n        query.one()\n    except NoResultFound:\n        raise LifetimeExceptionNotFound\n\n    if state in [LifetimeExceptionsState.APPROVED, LifetimeExceptionsState.REJECTED]:\n        query.update({'state': state, 'updated_at': datetime.utcnow()}, synchronize_session=False)\n    else:\n        raise UnsupportedOperation\n\n\n@read_session\ndef define_eol(scope, name, rses, session=None):\n    \"\"\"\n    ATLAS policy for rules on SCRATCHDISK\n\n    :param scope:    Scope of the DID.\n    :param name:     Name of the DID.\n    :param rses:     List of RSEs.\n    :param session:  The database session in use.\n    \"\"\"\n    policy = rucio.common.policy.get_policy()\n    if policy != 'atlas':\n        return None\n\n    # Check if on ATLAS managed space\n    if [rse for rse in rses if list_rse_attributes(rse=None, rse_id=rse['id'], session=session).get('type') in ['LOCALGROUPDISK', 'LOCALGROUPTAPE', 'GROUPDISK', 'GROUPTAPE']]:\n        return None\n    # Now check the lifetime policy\n    try:\n        did = session.query(models.DataIdentifier).filter(models.DataIdentifier.scope == scope,\n                                                          models.DataIdentifier.name == name).one()\n    except NoResultFound:\n        return None\n    policy_dict = rucio.common.policy.get_lifetime_policy()\n    did_type = 'other'\n    if scope.startswith('mc'):\n        did_type = 'mc'\n    elif scope.startswith('data'):\n        did_type = 'data'\n    elif scope.startswith('valid'):\n        did_type = 'valid'\n    else:\n        did_type = 'other'\n    for policy in policy_dict[did_type]:\n        if 'exclude' in policy:\n            to_exclude = False\n            for key in policy['exclude']:\n                meta_key = None\n                if key not in ['datatype', 'project', ]:\n                    if key == 'stream':\n                        meta_key = 'stream_name'\n                    elif key == 'tags':\n                        meta_key = 'version'\n                else:\n                    meta_key = key\n                values = policy['exclude'][key]\n                for value in values:\n                    value = value.replace('%', '.*')\n                    if meta_key and did[meta_key] and value and match(value, did[meta_key]):\n                        to_exclude = True\n                        break\n                if to_exclude:\n                    break\n            if to_exclude:\n                continue\n        if 'include' in policy:\n            match_policy = True\n            for key in policy['include']:\n                meta_key = None\n                if key not in ['datatype', 'project', ]:\n                    if key == 'stream':\n                        meta_key = 'stream_name'\n                    elif key == 'tags':\n                        meta_key = 'version'\n                    else:\n                        continue\n                else:\n                    meta_key = key\n                values = policy['include'][key]\n                to_keep = False\n                for value in values:\n                    value = value.replace('%', '.*')\n                    if meta_key and did[meta_key] and value and match(value, did[meta_key]):\n                        to_keep = True\n                        break\n                match_policy = match_policy and to_keep\n                if not to_keep:\n                    match_policy = False\n                    break\n            if match_policy:\n                if int(policy['age']) >= 12:\n                    years = int(int(policy['age']) / 12)\n                    months = int(policy['age']) - years * 12\n                    lifetime_value = 365 * years + 30 * months\n                else:\n                    lifetime_value = int(policy['age']) * 30\n                if int(policy['extension']) >= 12:\n                    years = int(int(policy['extension']) / 12)\n                    months = int(policy['extension']) - years * 12\n                    extension = 365 * years + 30 * months\n                else:\n                    extension = int(policy['extension']) * 30\n\n                default_eol_at = did.created_at + timedelta(days=lifetime_value)\n                eol_at = default_eol_at\n                if did.accessed_at:\n                    eol_at = did.accessed_at + timedelta(days=extension)\n                    if eol_at < default_eol_at:\n                        eol_at = default_eol_at\n                return eol_at\n    return None\n", "path": "lib/rucio/core/lifetime_exception.py"}], "after_files": [{"content": "#  Copyright European Organization for Nuclear Research (CERN)\n#\n#  Licensed under the Apache License, Version 2.0 (the \"License\");\n#  You may not use this file except in compliance with the License.\n#  You may obtain a copy of the License at\n#  http://www.apache.org/licenses/LICENSE-2.0\n#\n#  Authors:\n#  - Cedric Serfon, <cedric.serfon@cern.ch>, 2016-2018\n#  - Dimitrios Christidis, <dimitrios.christidis@cern.ch> 2018\n\nfrom re import match\nfrom datetime import datetime, timedelta\n\nfrom sqlalchemy import or_\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.orm.exc import NoResultFound\n\nfrom rucio.common.exception import ConfigNotFound, RucioException, LifetimeExceptionDuplicate, LifetimeExceptionNotFound, UnsupportedOperation\nfrom rucio.common.utils import generate_uuid, str_to_date\nimport rucio.common.policy\nfrom rucio.core.config import get\nfrom rucio.core.message import add_message\nfrom rucio.core.rse import list_rse_attributes\n\nfrom rucio.db.sqla import models\nfrom rucio.db.sqla.constants import DIDType, LifetimeExceptionsState\nfrom rucio.db.sqla.session import transactional_session, stream_session, read_session\n\n\n@stream_session\ndef list_exceptions(exception_id, states, session=None):\n    \"\"\"\n    List exceptions to Lifetime Model.\n\n    :param exception_id: The id of the exception\n    :param states:       The states to filter\n    :param session:      The database session in use.\n    \"\"\"\n\n    state_clause = []\n    if states:\n        state_clause = [models.LifetimeExceptions.state == state for state in states]\n\n    query = session.query(models.LifetimeExceptions.id,\n                          models.LifetimeExceptions.scope, models.LifetimeExceptions.name,\n                          models.LifetimeExceptions.did_type,\n                          models.LifetimeExceptions.account,\n                          models.LifetimeExceptions.pattern,\n                          models.LifetimeExceptions.comments,\n                          models.LifetimeExceptions.state,\n                          models.LifetimeExceptions.expires_at,\n                          models.LifetimeExceptions.created_at)\n    if state_clause != []:\n        query = query.filter(or_(*state_clause))\n    if exception_id:\n        query = query.filter(id=exception_id)\n\n    for exception in query.yield_per(5):\n        yield {'id': exception.id, 'scope': exception.scope, 'name': exception.name,\n               'did_type': exception.did_type, 'account': exception.account,\n               'pattern': exception.pattern, 'comments': exception.comments,\n               'state': exception.state, 'created_at': exception.created_at,\n               'expires_at': exception.expires_at}\n\n\n@transactional_session\ndef add_exception(dids, account, pattern, comments, expires_at, session=None):\n    \"\"\"\n    Add exceptions to Lifetime Model.\n\n    :param dids:        The list of dids\n    :param account:     The account of the requester.\n    :param pattern:     The account.\n    :param comments:    The comments associated to the exception.\n    :param expires_at:  The expiration date of the exception.\n    :param session:     The database session in use.\n\n    returns:            The id of the exception.\n    \"\"\"\n    exception_id = generate_uuid()\n    text = 'Account %s requested a lifetime extension for a list of DIDs that can be found below\\n' % account\n    reason = comments\n    volume = None\n    lifetime = None\n    if comments.find('||||') > -1:\n        reason, volume = comments.split('||||')\n    text += 'The reason for the extension is \"%s\"\\n' % reason\n    text += 'It represents %s datasets\\n' % len(dids)\n    if volume:\n        text += 'The estimated physical volume is %s\\n' % volume\n    if expires_at and (isinstance(expires_at, str) or isinstance(expires_at, unicode)):\n        lifetime = str_to_date(expires_at)\n        text += 'The lifetime exception should expires on %s\\n' % str(expires_at)\n    elif isinstance(expires_at, datetime):\n        lifetime = expires_at\n        text += 'The lifetime exception should expires on %s\\n' % str(expires_at)\n    text += 'Link to approve or reject this request can be found at the end of the mail\\n'\n    text += '\\n'\n    text += 'DIDTYPE SCOPE NAME\\n'\n    text += '\\n'\n    truncated_message = False\n    for did in dids:\n        did_type = None\n        if 'did_type' in did:\n            if isinstance(did['did_type'], str) or isinstance(did['did_type'], unicode):\n                did_type = DIDType.from_sym(did['did_type'])\n            else:\n                did_type = did['did_type']\n        new_exception = models.LifetimeExceptions(id=exception_id, scope=did['scope'], name=did['name'], did_type=did_type,\n                                                  account=account, pattern=pattern, comments=reason, state=LifetimeExceptionsState.WAITING, expires_at=lifetime)\n        if len(text) < 3000:\n            text += '%s %s %s\\n' % (str(did_type), did['scope'], did['name'])\n        else:\n            truncated_message = True\n        try:\n            new_exception.save(session=session, flush=False)\n        except IntegrityError as error:\n            if match('.*ORA-00001.*', str(error.args[0]))\\\n               or match('.*IntegrityError.*UNIQUE constraint failed.*', str(error.args[0]))\\\n               or match('.*1062.*Duplicate entry.*for key.*', str(error.args[0]))\\\n               or match('.*sqlite3.IntegrityError.*are not unique.*', error.args[0]):\n                raise LifetimeExceptionDuplicate()\n            raise RucioException(error.args[0])\n    if truncated_message:\n        text += '...\\n'\n        text += 'List too long. Truncated\\n'\n    text += '\\n'\n    text += 'Approve:   https://rucio-ui.cern.ch/lifetime_exception?id=%s&action=approve\\n' % str(exception_id)\n    text += 'Deny:      https://rucio-ui.cern.ch/lifetime_exception?id=%s&action=deny\\n' % str(exception_id)\n    approvers_email = []\n    try:\n        approvers_email = get('lifetime_model', 'approvers_email', session=session)\n        approvers_email = approvers_email.split(',')  # pylint: disable=no-member\n    except ConfigNotFound:\n        approvers_email = []\n\n    add_message(event_type='email',\n                payload={'body': text, 'to': approvers_email,\n                         'subject': '[RUCIO] Request to approve lifetime exception %s' % str(exception_id)},\n                session=session)\n    return exception_id\n\n\n@transactional_session\ndef update_exception(exception_id, state, session=None):\n    \"\"\"\n    Update exceptions state to Lifetime Model.\n\n    :param exception_id:   The id of the exception\n    :param state:          The states to filter\n    :param session:        The database session in use.\n    \"\"\"\n    query = session.query(models.LifetimeExceptions).filter_by(id=exception_id)\n    try:\n        query.first()\n    except NoResultFound:\n        raise LifetimeExceptionNotFound\n\n    if state in [LifetimeExceptionsState.APPROVED, LifetimeExceptionsState.REJECTED]:\n        query.update({'state': state, 'updated_at': datetime.utcnow()}, synchronize_session=False)\n    else:\n        raise UnsupportedOperation\n\n\n@read_session\ndef define_eol(scope, name, rses, session=None):\n    \"\"\"\n    ATLAS policy for rules on SCRATCHDISK\n\n    :param scope:    Scope of the DID.\n    :param name:     Name of the DID.\n    :param rses:     List of RSEs.\n    :param session:  The database session in use.\n    \"\"\"\n    policy = rucio.common.policy.get_policy()\n    if policy != 'atlas':\n        return None\n\n    # Check if on ATLAS managed space\n    if [rse for rse in rses if list_rse_attributes(rse=None, rse_id=rse['id'], session=session).get('type') in ['LOCALGROUPDISK', 'LOCALGROUPTAPE', 'GROUPDISK', 'GROUPTAPE']]:\n        return None\n    # Now check the lifetime policy\n    try:\n        did = session.query(models.DataIdentifier).filter(models.DataIdentifier.scope == scope,\n                                                          models.DataIdentifier.name == name).one()\n    except NoResultFound:\n        return None\n    policy_dict = rucio.common.policy.get_lifetime_policy()\n    did_type = 'other'\n    if scope.startswith('mc'):\n        did_type = 'mc'\n    elif scope.startswith('data'):\n        did_type = 'data'\n    elif scope.startswith('valid'):\n        did_type = 'valid'\n    else:\n        did_type = 'other'\n    for policy in policy_dict[did_type]:\n        if 'exclude' in policy:\n            to_exclude = False\n            for key in policy['exclude']:\n                meta_key = None\n                if key not in ['datatype', 'project', ]:\n                    if key == 'stream':\n                        meta_key = 'stream_name'\n                    elif key == 'tags':\n                        meta_key = 'version'\n                else:\n                    meta_key = key\n                values = policy['exclude'][key]\n                for value in values:\n                    value = value.replace('%', '.*')\n                    if meta_key and did[meta_key] and value and match(value, did[meta_key]):\n                        to_exclude = True\n                        break\n                if to_exclude:\n                    break\n            if to_exclude:\n                continue\n        if 'include' in policy:\n            match_policy = True\n            for key in policy['include']:\n                meta_key = None\n                if key not in ['datatype', 'project', ]:\n                    if key == 'stream':\n                        meta_key = 'stream_name'\n                    elif key == 'tags':\n                        meta_key = 'version'\n                    else:\n                        continue\n                else:\n                    meta_key = key\n                values = policy['include'][key]\n                to_keep = False\n                for value in values:\n                    value = value.replace('%', '.*')\n                    if meta_key and did[meta_key] and value and match(value, did[meta_key]):\n                        to_keep = True\n                        break\n                match_policy = match_policy and to_keep\n                if not to_keep:\n                    match_policy = False\n                    break\n            if match_policy:\n                if int(policy['age']) >= 12:\n                    years = int(int(policy['age']) / 12)\n                    months = int(policy['age']) - years * 12\n                    lifetime_value = 365 * years + 30 * months\n                else:\n                    lifetime_value = int(policy['age']) * 30\n                if int(policy['extension']) >= 12:\n                    years = int(int(policy['extension']) / 12)\n                    months = int(policy['extension']) - years * 12\n                    extension = 365 * years + 30 * months\n                else:\n                    extension = int(policy['extension']) * 30\n\n                default_eol_at = did.created_at + timedelta(days=lifetime_value)\n                eol_at = default_eol_at\n                if did.accessed_at:\n                    eol_at = did.accessed_at + timedelta(days=extension)\n                    if eol_at < default_eol_at:\n                        eol_at = default_eol_at\n                return eol_at\n    return None\n", "path": "lib/rucio/core/lifetime_exception.py"}]}