We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Correct storm path resolve for list_replicas
Motivation
----------

The fake storm:// protocol needs to be included in the list_replicas reply.


Crash on __list_transfer_requests_and_source_replicas
Motivation
----------

```
2019-06-19 14:17:43,558 32683   CRITICAL        Thread [1/31] : Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/rucio/daemons/conveyor/submitter.py", line 162, in submitter
    retry_other_fts=retry_other_fts)
  File "/usr/lib/python2.7/site-packages/rucio/daemons/conveyor/submitter.py", line 286, in __get_transfers
    failover_schemes=failover_schemes)
  File "/usr/lib/python2.7/site-packages/retrying.py", line 49, in wrapped_f
    return Retrying(*dargs, **dkw).call(f, *args, **kw)
  File "/usr/lib/python2.7/site-packages/retrying.py", line 206, in call
    return attempt.get(self._wrap_exception)
  File "/usr/lib/python2.7/site-packages/retrying.py", line 247, in get
    six.reraise(self.value[0], self.value[1], self.value[2])
  File "/usr/lib/python2.7/site-packages/retrying.py", line 200, in call
    attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
  File "/usr/lib/python2.7/site-packages/rucio/db/sqla/session.py", line 281, in new_funct
    raise DatabaseException(str(error))
DatabaseException: Database exception.
Details: (cx_Oracle.DatabaseError) ORA-00918: column ambiguously defined
```




--- 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/rse/protocols/storm.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 # - Tomas Javor Javurek, <tomas.javurek@cern.ch>, 2019
10 
11 
12 import os
13 
14 from exceptions import NotImplementedError
15 from xml.dom import minidom
16 
17 from rucio.common import exception
18 from rucio.common.utils import run_cmd_process
19 from rucio.rse.protocols import protocol
20 
21 
22 class Default(protocol.RSEProtocol):
23     """ Implementing access to RSEs using the local filesystem."""
24 
25     def __init__(self, protocol_attr, rse_settings):
26         """ Initializes the object with information about the referred RSE.
27 
28             :param props Properties derived from the RSE Repository
29         """
30         super(Default, self).__init__(protocol_attr, rse_settings)
31         self.attributes.pop('determinism_type', None)
32         self.files = []
33 
34     def _get_path(self, scope, name):
35         """ Transforms the physical file name into the local URI in the referred RSE.
36             Suitable for sites implementoing the RUCIO naming convention.
37 
38             :param name: filename
39             :param scope: scope
40 
41             :returns: RSE specific URI of the physical file
42         """
43         return '%s/%s' % (scope, name)
44 
45     def lfns2pfns(self, lfns):
46         """ In this case, just returns back lfn with external scope. """
47         if type(lfns) == dict:
48             val = lfns.copy()
49             if 'scope' in val and val['scope'] is not None:
50                 val['scope'] = val['scope'].external
51 
52         elif type(lfns) == list:
53             val = []
54             for l in lfns:
55                 v = l.copy()
56                 if 'scope' in v and v['scope'] is not None:
57                     v['scope'] = v['scope'].external
58                 val.append(v)
59 
60         else:
61             val = lfns
62         return val
63 
64     def path2pfn(self, path):
65         """
66             Retruns a fully qualified PFN for the file referred by path.
67 
68             :param path: The path to the file.
69 
70             :returns: Fully qualified PFN.
71 
72         """
73         return ''.join([self.rse['scheme'], '://%s' % self.rse['hostname'], path])
74 
75     def exists(self, pfn):
76         """ Checks if the requested file is known by the referred RSE.
77 
78             :param pfn Physical file name
79 
80             :returns: True if the file exists, False if it doesn't
81 
82             :raise  ServiceUnavailable
83         """
84         raise NotImplementedError
85 
86     def connect(self):
87         """ Establishes the actual connection to the referred RSE.
88 
89             :param credentials Provide all necessary information to establish a connection
90                 to the referred storage system. Some is loaded from the repository inside the
91                 RSE class and some must be provided specific for the SFTP protocol like
92                 username, password, private_key, private_key_pass, port.
93                 For details about possible additional parameters and details about their usage
94                 see the pysftp.Connection() documentation.
95                 NOTE: the host parametrer is overwritten with the value provided by the repository
96 
97             :raise RSEAccessDenied
98         """
99         pass
100 
101     def close(self):
102         """ Closes the connection to RSE."""
103         pass
104 
105     def get(self, pfn, dest, transfer_timeout=None):
106         """ Provides access to files stored inside connected the RSE.
107 
108             :param pfn Physical file name of requested file
109             :param dest Name and path of the files when stored at the client
110             :param transfer_timeout Transfer timeout (in seconds)
111 
112             :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
113          """
114 
115         # storm prefix needs to be replaced by davs in order to get etag
116         pfn = 'davs' + pfn[5:]
117 
118         # retrieve the TURL from the webdav etag, TODO: make it configurable
119         cmd = 'davix-http --capath /cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase/etc/grid-security-emi/certificates --cert $X509_USER_PROXY -X PROPFIND %s' % pfn
120         try:
121             rcode, output = run_cmd_process(cmd, timeout=10)
122         except Exception as e:
123             raise exception.ServiceUnavailable('Could not retrieve STORM WebDAV ETag: %s' % str(e))
124         p_output = minidom.parseString(output)
125 
126         # we need to strip off the quotation marks and the <timestamp> from the etag
127         # but since we can have multiple underscores, we have to rely on the uniqueness
128         # of the full LFN to make the split
129         target = p_output.getElementsByTagName('d:getetag')[0].childNodes[0].nodeValue.replace('"', '')
130         target_ending = '_' + target.split('_')[-1]
131         target = target.split(target_ending)[0]
132 
133         # make the symlink
134         try:
135             os.symlink(target, dest)
136         except Exception as e:
137             exception.ServiceUnavailable('Could not create symlink: %s for target %s' % (str(e), str(target)))
138 
139     def put(self, source, target, source_dir=None, transfer_timeout=None):
140         """ Allows to store files inside the referred RSE.
141 
142             :param source Physical file name
143             :param target Name of the file on the storage system e.g. with prefixed scope
144             :param source_dir Path where the to be transferred files are stored in the local file system
145             :param transfer_timeout Transfer timeout (in seconds)
146 
147             :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
148             """
149         raise NotImplementedError
150 
151     def delete(self, pfn):
152         """ Deletes a file from the connected RSE.
153 
154             :param pfn Physical file name
155 
156             :raises ServiceUnavailable, SourceNotFound
157         """
158         raise NotImplementedError
159 
160     def rename(self, pfn, new_pfn):
161         """ Allows to rename a file stored inside the connected RSE.
162 
163             :param pfn      Current physical file name
164             :param new_pfn  New physical file name
165 
166             :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
167         """
168         raise NotImplementedError
169
```
--- 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/rse/protocols/storm.py b/lib/rucio/rse/protocols/storm.py
--- a/lib/rucio/rse/protocols/storm.py
+++ b/lib/rucio/rse/protocols/storm.py
@@ -1,13 +1,20 @@
-# Copyright European Organization for Nuclear Research (CERN)
+# Copyright 2014-2019 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.
+# you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
-# http://www.apache.org/licenses/LICENSE-2.0
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 #
 # Authors:
-# - Tomas Javor Javurek, <tomas.javurek@cern.ch>, 2019
-
+# - Tomas Javor Javurek <tomas.javurek@cern.ch>, 2019
+# - Mario Lassnig <mario.lassnig@cern.ch>, 2019
 
 import os
 
@@ -31,35 +38,27 @@
         self.attributes.pop('determinism_type', None)
         self.files = []
 
-    def _get_path(self, scope, name):
-        """ Transforms the physical file name into the local URI in the referred RSE.
-            Suitable for sites implementoing the RUCIO naming convention.
-
-            :param name: filename
-            :param scope: scope
-
-            :returns: RSE specific URI of the physical file
-        """
-        return '%s/%s' % (scope, name)
-
     def lfns2pfns(self, lfns):
-        """ In this case, just returns back lfn with external scope. """
-        if type(lfns) == dict:
-            val = lfns.copy()
-            if 'scope' in val and val['scope'] is not None:
-                val['scope'] = val['scope'].external
-
-        elif type(lfns) == list:
-            val = []
-            for l in lfns:
-                v = l.copy()
-                if 'scope' in v and v['scope'] is not None:
-                    v['scope'] = v['scope'].external
-                val.append(v)
-
-        else:
-            val = lfns
-        return val
+        """ Create fake storm:// path. Will be resolved at the get() stage later. """
+        pfns = {}
+
+        hostname = self.attributes['hostname']
+        if '://' in hostname:
+            hostname = hostname.split("://")[1]
+
+        prefix = self.attributes['prefix']
+        if not prefix.startswith('/'):
+            prefix = ''.join(['/', prefix])
+        if not prefix.endswith('/'):
+            prefix = ''.join([prefix, '/'])
+
+        lfns = [lfns] if isinstance(lfns, dict) else lfns
+        for lfn in lfns:
+            path = lfn['path'] if 'path' in lfn and lfn['path'] else self._get_path(scope=lfn['scope'].external,
+                                                                                    name=lfn['name'])
+            pfns['%s:%s' % (lfn['scope'], lfn['name'])] = ''.join(['storm://', hostname, ':', str(self.attributes['port']), prefix, path])
+
+        return pfns
 
     def path2pfn(self, path):
         """

{"golden_diff": "diff --git a/lib/rucio/rse/protocols/storm.py b/lib/rucio/rse/protocols/storm.py\n--- a/lib/rucio/rse/protocols/storm.py\n+++ b/lib/rucio/rse/protocols/storm.py\n@@ -1,13 +1,20 @@\n-# Copyright European Organization for Nuclear Research (CERN)\n+# Copyright 2014-2019 CERN for the benefit of the ATLAS collaboration.\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 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+#    http://www.apache.org/licenses/LICENSE-2.0\n+#\n+# Unless required by applicable law or agreed to in writing, software\n+# distributed under the License is distributed on an \"AS IS\" BASIS,\n+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+# See the License for the specific language governing permissions and\n+# limitations under the License.\n #\n # Authors:\n-# - Tomas Javor Javurek, <tomas.javurek@cern.ch>, 2019\n-\n+# - Tomas Javor Javurek <tomas.javurek@cern.ch>, 2019\n+# - Mario Lassnig <mario.lassnig@cern.ch>, 2019\n \n import os\n \n@@ -31,35 +38,27 @@\n         self.attributes.pop('determinism_type', None)\n         self.files = []\n \n-    def _get_path(self, scope, name):\n-        \"\"\" Transforms the physical file name into the local URI in the referred RSE.\n-            Suitable for sites implementoing the RUCIO naming convention.\n-\n-            :param name: filename\n-            :param scope: scope\n-\n-            :returns: RSE specific URI of the physical file\n-        \"\"\"\n-        return '%s/%s' % (scope, name)\n-\n     def lfns2pfns(self, lfns):\n-        \"\"\" In this case, just returns back lfn with external scope. \"\"\"\n-        if type(lfns) == dict:\n-            val = lfns.copy()\n-            if 'scope' in val and val['scope'] is not None:\n-                val['scope'] = val['scope'].external\n-\n-        elif type(lfns) == list:\n-            val = []\n-            for l in lfns:\n-                v = l.copy()\n-                if 'scope' in v and v['scope'] is not None:\n-                    v['scope'] = v['scope'].external\n-                val.append(v)\n-\n-        else:\n-            val = lfns\n-        return val\n+        \"\"\" Create fake storm:// path. Will be resolved at the get() stage later. \"\"\"\n+        pfns = {}\n+\n+        hostname = self.attributes['hostname']\n+        if '://' in hostname:\n+            hostname = hostname.split(\"://\")[1]\n+\n+        prefix = self.attributes['prefix']\n+        if not prefix.startswith('/'):\n+            prefix = ''.join(['/', prefix])\n+        if not prefix.endswith('/'):\n+            prefix = ''.join([prefix, '/'])\n+\n+        lfns = [lfns] if isinstance(lfns, dict) else lfns\n+        for lfn in lfns:\n+            path = lfn['path'] if 'path' in lfn and lfn['path'] else self._get_path(scope=lfn['scope'].external,\n+                                                                                    name=lfn['name'])\n+            pfns['%s:%s' % (lfn['scope'], lfn['name'])] = ''.join(['storm://', hostname, ':', str(self.attributes['port']), prefix, path])\n+\n+        return pfns\n \n     def path2pfn(self, path):\n         \"\"\"\n", "issue": "Correct storm path resolve for list_replicas\nMotivation\r\n----------\r\n\r\nThe fake storm:// protocol needs to be included in the list_replicas reply.\r\n\r\n\nCrash on __list_transfer_requests_and_source_replicas\nMotivation\r\n----------\r\n\r\n```\r\n2019-06-19 14:17:43,558 32683   CRITICAL        Thread [1/31] : Traceback (most recent call last):\r\n  File \"/usr/lib/python2.7/site-packages/rucio/daemons/conveyor/submitter.py\", line 162, in submitter\r\n    retry_other_fts=retry_other_fts)\r\n  File \"/usr/lib/python2.7/site-packages/rucio/daemons/conveyor/submitter.py\", line 286, in __get_transfers\r\n    failover_schemes=failover_schemes)\r\n  File \"/usr/lib/python2.7/site-packages/retrying.py\", line 49, in wrapped_f\r\n    return Retrying(*dargs, **dkw).call(f, *args, **kw)\r\n  File \"/usr/lib/python2.7/site-packages/retrying.py\", line 206, in call\r\n    return attempt.get(self._wrap_exception)\r\n  File \"/usr/lib/python2.7/site-packages/retrying.py\", line 247, in get\r\n    six.reraise(self.value[0], self.value[1], self.value[2])\r\n  File \"/usr/lib/python2.7/site-packages/retrying.py\", line 200, in call\r\n    attempt = Attempt(fn(*args, **kwargs), attempt_number, False)\r\n  File \"/usr/lib/python2.7/site-packages/rucio/db/sqla/session.py\", line 281, in new_funct\r\n    raise DatabaseException(str(error))\r\nDatabaseException: Database exception.\r\nDetails: (cx_Oracle.DatabaseError) ORA-00918: column ambiguously defined\r\n```\r\n\r\n\r\n\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# - Tomas Javor Javurek, <tomas.javurek@cern.ch>, 2019\n\n\nimport os\n\nfrom exceptions import NotImplementedError\nfrom xml.dom import minidom\n\nfrom rucio.common import exception\nfrom rucio.common.utils import run_cmd_process\nfrom rucio.rse.protocols import protocol\n\n\nclass Default(protocol.RSEProtocol):\n    \"\"\" Implementing access to RSEs using the local filesystem.\"\"\"\n\n    def __init__(self, protocol_attr, rse_settings):\n        \"\"\" Initializes the object with information about the referred RSE.\n\n            :param props Properties derived from the RSE Repository\n        \"\"\"\n        super(Default, self).__init__(protocol_attr, rse_settings)\n        self.attributes.pop('determinism_type', None)\n        self.files = []\n\n    def _get_path(self, scope, name):\n        \"\"\" Transforms the physical file name into the local URI in the referred RSE.\n            Suitable for sites implementoing the RUCIO naming convention.\n\n            :param name: filename\n            :param scope: scope\n\n            :returns: RSE specific URI of the physical file\n        \"\"\"\n        return '%s/%s' % (scope, name)\n\n    def lfns2pfns(self, lfns):\n        \"\"\" In this case, just returns back lfn with external scope. \"\"\"\n        if type(lfns) == dict:\n            val = lfns.copy()\n            if 'scope' in val and val['scope'] is not None:\n                val['scope'] = val['scope'].external\n\n        elif type(lfns) == list:\n            val = []\n            for l in lfns:\n                v = l.copy()\n                if 'scope' in v and v['scope'] is not None:\n                    v['scope'] = v['scope'].external\n                val.append(v)\n\n        else:\n            val = lfns\n        return val\n\n    def path2pfn(self, path):\n        \"\"\"\n            Retruns a fully qualified PFN for the file referred by path.\n\n            :param path: The path to the file.\n\n            :returns: Fully qualified PFN.\n\n        \"\"\"\n        return ''.join([self.rse['scheme'], '://%s' % self.rse['hostname'], path])\n\n    def exists(self, pfn):\n        \"\"\" Checks if the requested file is known by the referred RSE.\n\n            :param pfn Physical file name\n\n            :returns: True if the file exists, False if it doesn't\n\n            :raise  ServiceUnavailable\n        \"\"\"\n        raise NotImplementedError\n\n    def connect(self):\n        \"\"\" Establishes the actual connection to the referred RSE.\n\n            :param credentials Provide all necessary information to establish a connection\n                to the referred storage system. Some is loaded from the repository inside the\n                RSE class and some must be provided specific for the SFTP protocol like\n                username, password, private_key, private_key_pass, port.\n                For details about possible additional parameters and details about their usage\n                see the pysftp.Connection() documentation.\n                NOTE: the host parametrer is overwritten with the value provided by the repository\n\n            :raise RSEAccessDenied\n        \"\"\"\n        pass\n\n    def close(self):\n        \"\"\" Closes the connection to RSE.\"\"\"\n        pass\n\n    def get(self, pfn, dest, transfer_timeout=None):\n        \"\"\" Provides access to files stored inside connected the RSE.\n\n            :param pfn Physical file name of requested file\n            :param dest Name and path of the files when stored at the client\n            :param transfer_timeout Transfer timeout (in seconds)\n\n            :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound\n         \"\"\"\n\n        # storm prefix needs to be replaced by davs in order to get etag\n        pfn = 'davs' + pfn[5:]\n\n        # retrieve the TURL from the webdav etag, TODO: make it configurable\n        cmd = 'davix-http --capath /cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase/etc/grid-security-emi/certificates --cert $X509_USER_PROXY -X PROPFIND %s' % pfn\n        try:\n            rcode, output = run_cmd_process(cmd, timeout=10)\n        except Exception as e:\n            raise exception.ServiceUnavailable('Could not retrieve STORM WebDAV ETag: %s' % str(e))\n        p_output = minidom.parseString(output)\n\n        # we need to strip off the quotation marks and the <timestamp> from the etag\n        # but since we can have multiple underscores, we have to rely on the uniqueness\n        # of the full LFN to make the split\n        target = p_output.getElementsByTagName('d:getetag')[0].childNodes[0].nodeValue.replace('\"', '')\n        target_ending = '_' + target.split('_')[-1]\n        target = target.split(target_ending)[0]\n\n        # make the symlink\n        try:\n            os.symlink(target, dest)\n        except Exception as e:\n            exception.ServiceUnavailable('Could not create symlink: %s for target %s' % (str(e), str(target)))\n\n    def put(self, source, target, source_dir=None, transfer_timeout=None):\n        \"\"\" Allows to store files inside the referred RSE.\n\n            :param source Physical file name\n            :param target Name of the file on the storage system e.g. with prefixed scope\n            :param source_dir Path where the to be transferred files are stored in the local file system\n            :param transfer_timeout Transfer timeout (in seconds)\n\n            :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound\n            \"\"\"\n        raise NotImplementedError\n\n    def delete(self, pfn):\n        \"\"\" Deletes a file from the connected RSE.\n\n            :param pfn Physical file name\n\n            :raises ServiceUnavailable, SourceNotFound\n        \"\"\"\n        raise NotImplementedError\n\n    def rename(self, pfn, new_pfn):\n        \"\"\" Allows to rename a file stored inside the connected RSE.\n\n            :param pfn      Current physical file name\n            :param new_pfn  New physical file name\n\n            :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound\n        \"\"\"\n        raise NotImplementedError\n", "path": "lib/rucio/rse/protocols/storm.py"}], "after_files": [{"content": "# Copyright 2014-2019 CERN for the benefit of the ATLAS collaboration.\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#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# Authors:\n# - Tomas Javor Javurek <tomas.javurek@cern.ch>, 2019\n# - Mario Lassnig <mario.lassnig@cern.ch>, 2019\n\nimport os\n\nfrom exceptions import NotImplementedError\nfrom xml.dom import minidom\n\nfrom rucio.common import exception\nfrom rucio.common.utils import run_cmd_process\nfrom rucio.rse.protocols import protocol\n\n\nclass Default(protocol.RSEProtocol):\n    \"\"\" Implementing access to RSEs using the local filesystem.\"\"\"\n\n    def __init__(self, protocol_attr, rse_settings):\n        \"\"\" Initializes the object with information about the referred RSE.\n\n            :param props Properties derived from the RSE Repository\n        \"\"\"\n        super(Default, self).__init__(protocol_attr, rse_settings)\n        self.attributes.pop('determinism_type', None)\n        self.files = []\n\n    def lfns2pfns(self, lfns):\n        \"\"\" Create fake storm:// path. Will be resolved at the get() stage later. \"\"\"\n        pfns = {}\n\n        hostname = self.attributes['hostname']\n        if '://' in hostname:\n            hostname = hostname.split(\"://\")[1]\n\n        prefix = self.attributes['prefix']\n        if not prefix.startswith('/'):\n            prefix = ''.join(['/', prefix])\n        if not prefix.endswith('/'):\n            prefix = ''.join([prefix, '/'])\n\n        lfns = [lfns] if isinstance(lfns, dict) else lfns\n        for lfn in lfns:\n            path = lfn['path'] if 'path' in lfn and lfn['path'] else self._get_path(scope=lfn['scope'].external,\n                                                                                    name=lfn['name'])\n            pfns['%s:%s' % (lfn['scope'], lfn['name'])] = ''.join(['storm://', hostname, ':', str(self.attributes['port']), prefix, path])\n\n        return pfns\n\n    def path2pfn(self, path):\n        \"\"\"\n            Retruns a fully qualified PFN for the file referred by path.\n\n            :param path: The path to the file.\n\n            :returns: Fully qualified PFN.\n\n        \"\"\"\n        return ''.join([self.rse['scheme'], '://%s' % self.rse['hostname'], path])\n\n    def exists(self, pfn):\n        \"\"\" Checks if the requested file is known by the referred RSE.\n\n            :param pfn Physical file name\n\n            :returns: True if the file exists, False if it doesn't\n\n            :raise  ServiceUnavailable\n        \"\"\"\n        raise NotImplementedError\n\n    def connect(self):\n        \"\"\" Establishes the actual connection to the referred RSE.\n\n            :param credentials Provide all necessary information to establish a connection\n                to the referred storage system. Some is loaded from the repository inside the\n                RSE class and some must be provided specific for the SFTP protocol like\n                username, password, private_key, private_key_pass, port.\n                For details about possible additional parameters and details about their usage\n                see the pysftp.Connection() documentation.\n                NOTE: the host parametrer is overwritten with the value provided by the repository\n\n            :raise RSEAccessDenied\n        \"\"\"\n        pass\n\n    def close(self):\n        \"\"\" Closes the connection to RSE.\"\"\"\n        pass\n\n    def get(self, pfn, dest, transfer_timeout=None):\n        \"\"\" Provides access to files stored inside connected the RSE.\n\n            :param pfn Physical file name of requested file\n            :param dest Name and path of the files when stored at the client\n            :param transfer_timeout Transfer timeout (in seconds)\n\n            :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound\n         \"\"\"\n\n        # storm prefix needs to be replaced by davs in order to get etag\n        pfn = 'davs' + pfn[5:]\n\n        # retrieve the TURL from the webdav etag, TODO: make it configurable\n        cmd = 'davix-http --capath /cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase/etc/grid-security-emi/certificates --cert $X509_USER_PROXY -X PROPFIND %s' % pfn\n        try:\n            rcode, output = run_cmd_process(cmd, timeout=10)\n        except Exception as e:\n            raise exception.ServiceUnavailable('Could not retrieve STORM WebDAV ETag: %s' % str(e))\n        p_output = minidom.parseString(output)\n\n        # we need to strip off the quotation marks and the <timestamp> from the etag\n        # but since we can have multiple underscores, we have to rely on the uniqueness\n        # of the full LFN to make the split\n        target = p_output.getElementsByTagName('d:getetag')[0].childNodes[0].nodeValue.replace('\"', '')\n        target_ending = '_' + target.split('_')[-1]\n        target = target.split(target_ending)[0]\n\n        # make the symlink\n        try:\n            os.symlink(target, dest)\n        except Exception as e:\n            exception.ServiceUnavailable('Could not create symlink: %s for target %s' % (str(e), str(target)))\n\n    def put(self, source, target, source_dir=None, transfer_timeout=None):\n        \"\"\" Allows to store files inside the referred RSE.\n\n            :param source Physical file name\n            :param target Name of the file on the storage system e.g. with prefixed scope\n            :param source_dir Path where the to be transferred files are stored in the local file system\n            :param transfer_timeout Transfer timeout (in seconds)\n\n            :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound\n            \"\"\"\n        raise NotImplementedError\n\n    def delete(self, pfn):\n        \"\"\" Deletes a file from the connected RSE.\n\n            :param pfn Physical file name\n\n            :raises ServiceUnavailable, SourceNotFound\n        \"\"\"\n        raise NotImplementedError\n\n    def rename(self, pfn, new_pfn):\n        \"\"\" Allows to rename a file stored inside the connected RSE.\n\n            :param pfn      Current physical file name\n            :param new_pfn  New physical file name\n\n            :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound\n        \"\"\"\n        raise NotImplementedError\n", "path": "lib/rucio/rse/protocols/storm.py"}]}