Repository: rucio/rucio

Issue:
Delete distance API
Although there is a core method
https://github.com/rucio/rucio/blob/a0823b3ec19ff1f98639361a14b628ca6c908651/lib/rucio/core/distance.py#L115
we have no client or REST API access to this method.


Discussion:
@davidpob99 please have a look on this one and ask me if anything is unclear. It's mainly mirroring the interface and exposing it to the REST and rucio-admin.

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
@@ -1,4 +1,5 @@
-# Copyright 2012-2020 CERN for the benefit of the ATLAS collaboration.
+# -*- coding: utf-8 -*-
+# Copyright 2012-2021 CERN
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,19 +14,21 @@
 # limitations under the License.
 #
 # Authors:
-# - Mario Lassnig <mario@lassnig.net>, 2012-2020
-# - Vincent Garonne <vgaronne@gmail.com>, 2012-2017
+# - Mario Lassnig <mario.lassnig@cern.ch>, 2012-2020
+# - Vincent Garonne <vincent.garonne@cern.ch>, 2012-2017
 # - Ralph Vigne <ralph.vigne@cern.ch>, 2013-2014
 # - Cedric Serfon <cedric.serfon@cern.ch>, 2013-2017
-# - Martin Barisits <martin.barisits@cern.ch>, 2013-2014
+# - Martin Barisits <martin.barisits@cern.ch>, 2013-2020
 # - Thomas Beermann <thomas.beermann@cern.ch>, 2014
 # - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2018
 # - Andrew Lister <andrew.lister@stfc.ac.uk>, 2019
-# - Gabriele <sucre.91@hotmail.it>, 2019
-# - elichad <eli.chadwick.256@gmail.com>, 2020
-# - patrick-austin <patrick.austin@stfc.ac.uk>, 2020
-#
-#  PY3K COMPATIBLE
+# - Gabriele Fronze' <gfronze@cern.ch>, 2019
+# - Eli Chadwick <eli.chadwick@stfc.ac.uk>, 2020
+# - Patrick Austin <patrick.austin@stfc.ac.uk>, 2020
+# - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2020
+# - James Perry <j.perry@epcc.ed.ac.uk>, 2020
+# - Tomas Javurek <tomas.javurek@cern.ch>, 2020
+# - David Población Criado <david.poblacion.criado@cern.ch>, 2021
 
 from rucio.api import permission
 from rucio.common import exception
@@ -487,6 +490,23 @@ def get_distance(source, destination, issuer, vo='def'):
     return [api_update_return_dict(d) for d in distances]
 
 
+def delete_distance(source, destination, issuer, vo='def'):
+    """
+    Delete distances with the given RSE ids.
+
+    :param source: The source RSE.
+    :param destination: The destination RSE.
+    :param issuer: The issuer account.
+    :param vo: The VO to act on.
+    """
+    kwargs = {'source': source, 'destination': destination}
+    if not permission.has_permission(issuer=issuer, vo=vo, action='delete_distance', kwargs=kwargs):
+        raise exception.AccessDenied('Account %s can not update RSE distances' % issuer)
+
+    return distance_module.delete_distances(src_rse_id=rse_module.get_rse_id(source, vo=vo),
+                                            dest_rse_id=rse_module.get_rse_id(destination, vo=vo))
+
+
 def add_qos_policy(rse, qos_policy, issuer, vo='def'):
     """
     Add a QoS policy from an RSE.
diff --git a/lib/rucio/client/rseclient.py b/lib/rucio/client/rseclient.py
--- a/lib/rucio/client/rseclient.py
+++ b/lib/rucio/client/rseclient.py
@@ -28,6 +28,7 @@
 # - Andrew Lister <andrew.lister@stfc.ac.uk>, 2019
 # - Tomas Javurek <tomas.javurek@cern.ch>, 2020
 # - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2021
+# - David Población Criado <david.poblacion.criado@cern.ch>, 2021
 
 from json import dumps, loads
 
@@ -664,3 +665,21 @@ def get_distance(self, source, destination):
             return next(self._load_json_data(r))
         exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
         raise exc_cls(exc_msg)
+
+    def delete_distance(self, source, destination):
+        """
+        Delete distances with the given RSE ids.
+
+        :param source: The source.
+        :param destination: The destination.
+        """
+        path = [self.RSE_BASEURL, source, 'distances', destination]
+        path = '/'.join(path)
+        url = build_url(choice(self.list_hosts), path=path)
+        r = self._send_request(url, type='DEL')
+        if r.status_code == codes.ok:
+            return True
+        exc_cls, exc_msg = self._get_exception(headers=r.headers,
+                                               status_code=r.status_code,
+                                               data=r.content)
+        raise exc_cls(exc_msg)
diff --git a/lib/rucio/web/rest/flaskapi/v1/rses.py b/lib/rucio/web/rest/flaskapi/v1/rses.py
--- a/lib/rucio/web/rest/flaskapi/v1/rses.py
+++ b/lib/rucio/web/rest/flaskapi/v1/rses.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2018-2021 CERN
+# Copyright 2021 CERN
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -14,13 +14,8 @@
 # limitations under the License.
 #
 # Authors:
-# - Thomas Beermann <thomas.beermann@cern.ch>, 2018-2021
-# - Vincent Garonne <vincent.garonne@cern.ch>, 2018
-# - Mario Lassnig <mario.lassnig@cern.ch>, 2018-2020
-# - Cedric Serfon <cedric.serfon@cern.ch>, 2018
-# - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2018-2019
-# - Andrew Lister <andrew.lister@stfc.ac.uk>, 2019
-# - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2020-2021
+# - Benedikt Ziemons <benedikt.ziemons@cern.ch>, 2021
+# - Thomas Beermann <thomas.beermann@cern.ch>, 2021
 # - David Población Criado <david.poblacion.criado@cern.ch>, 2021
 
 from json import dumps
@@ -31,7 +26,7 @@
 from rucio.api.rse import add_rse, update_rse, list_rses, del_rse, add_rse_attribute, list_rse_attributes, \
     del_rse_attribute, add_protocol, get_rse_protocols, del_protocols, update_protocols, get_rse, set_rse_usage, \
     get_rse_usage, list_rse_usage_history, set_rse_limits, get_rse_limits, delete_rse_limits, parse_rse_expression, \
-    add_distance, get_distance, update_distance, list_qos_policies, add_qos_policy, delete_qos_policy
+    add_distance, get_distance, update_distance, delete_distance, list_qos_policies, add_qos_policy, delete_qos_policy
 from rucio.common.exception import Duplicate, AccessDenied, RSENotFound, RSEOperationNotSupported, \
     RSEProtocolNotSupported, InvalidObject, RSEProtocolDomainNotSupported, RSEProtocolPriorityError, \
     InvalidRSEExpression, RSEAttributeNotFound, CounterNotFound, InvalidPath, ReplicaNotFound
@@ -789,6 +784,31 @@ def put(self, source, destination):
 
         return '', 200
 
+    def delete(self, source, destination):
+        """ Delete distance information between source RSE and destination RSE.
+
+        .. :quickref: Distance; Delete RSE distances.
+
+        :param source: The source RSE name.
+        :param destination: The destination RSE name.
+        :status 200: OK.
+        :status 401: Invalid Auth Token.
+        :status 404: RSE Not Found.
+        """
+        try:
+            delete_distance(
+                source=source,
+                destination=destination,
+                issuer=request.environ.get('issuer'),
+                vo=request.environ.get('vo')
+            )
+        except AccessDenied as error:
+            return generate_http_error_flask(401, error)
+        except RSENotFound as error:
+            return generate_http_error_flask(404, error)
+
+        return 'Deleted', 200
+
 
 class QoSPolicy(ErrorHandlingMethodView):
     """ Add/Delete/List QoS policies on an RSE. """
@@ -860,7 +880,7 @@ def blueprint():
     bp.add_url_rule('/<rse>/attr/<key>', view_func=attributes_view, methods=['post', 'delete'])
     bp.add_url_rule('/<rse>/attr/', view_func=attributes_view, methods=['get', ])
     distance_view = Distance.as_view('distance')
-    bp.add_url_rule('/<source>/distances/<destination>', view_func=distance_view, methods=['get', 'post', 'put'])
+    bp.add_url_rule('/<source>/distances/<destination>', view_func=distance_view, methods=['get', 'post', 'put', 'delete'])
     protocol_view = Protocol.as_view('protocol')
     bp.add_url_rule('/<rse>/protocols/<scheme>/<hostname>/<port>', view_func=protocol_view, methods=['delete', 'put'])
     bp.add_url_rule('/<rse>/protocols/<scheme>/<hostname>', view_func=protocol_view, methods=['delete', 'put'])


Test patch:
diff --git a/lib/rucio/tests/test_bin_rucio.py b/lib/rucio/tests/test_bin_rucio.py
--- a/lib/rucio/tests/test_bin_rucio.py
+++ b/lib/rucio/tests/test_bin_rucio.py
@@ -273,6 +273,40 @@ def test_rse_add_distance(self):
         assert exitcode != 0
         assert 'Distance from %s to %s already exists!' % (temprse2, temprse1) in err
 
+    def test_rse_delete_distance(self):
+        """CLIENT (ADMIN): Delete distance to RSE"""
+        # add RSEs
+        temprse1 = rse_name_generator()
+        cmd = 'rucio-admin rse add %s' % temprse1
+        exitcode, out, err = execute(cmd)
+        print(out, err)
+        assert exitcode == 0
+        temprse2 = rse_name_generator()
+        cmd = 'rucio-admin rse add %s' % temprse2
+        exitcode, out, err = execute(cmd)
+        print(out, err)
+        assert exitcode == 0
+
+        # add distance between the RSEs
+        cmd = 'rucio-admin rse add-distance --distance 1 --ranking 1 %s %s' % (temprse1, temprse2)
+        print(self.marker + cmd)
+        exitcode, out, err = execute(cmd)
+        print(out, err)
+        assert exitcode == 0
+
+        # delete distance OK
+        cmd = 'rucio-admin rse delete-distance %s %s' % (temprse1, temprse2)
+        exitcode, out, err = execute(cmd)
+        print(out, err)
+        assert exitcode == 0
+        assert "Deleted distance information from %s to %s." % (temprse1, temprse2) in out
+
+        # delete distance RSE not found
+        cmd = 'rucio-admin rse delete-distance %s %s' % (temprse1, generate_uuid())
+        exitcode, out, err = execute(cmd)
+        print(out, err)
+        assert 'RSE does not exist.' in err
+
     def test_upload(self):
         """CLIENT(USER): Upload"""
         tmp_val = rse_name_generator()