Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-09 10:47:30

0001 """Removing datasets from a disk RSE through the monitor
0002 (monitor_app/panda/rse_deletions.py): the preview and confirmation
0003 requests, the guards on confirmation, the operations-role gate, and the
0004 reading of the agent's request files. No table and no database: the
0005 ActiveMQ send and the authority lookup are mocked, and the request files
0006 live in a temporary SWF_TMP_DIR."""
0007 import json
0008 import shutil
0009 import tempfile
0010 import uuid
0011 from datetime import timedelta
0012 from pathlib import Path
0013 from unittest import mock
0014 
0015 from django.contrib.auth.models import User
0016 from django.test import SimpleTestCase, override_settings
0017 from django.utils import timezone
0018 from rest_framework.test import APIRequestFactory, force_authenticate
0019 
0020 from monitor_app.panda import api as panda_api
0021 from monitor_app.panda import rse_deletions as rd
0022 
0023 SEND = 'monitor_app.panda.rse_deletions.ActiveMQConnectionManager'
0024 
0025 PLAN = {'rse': 'BNL-XRD', 'pattern': '/RECO/26.03.0/*', 'datasets': [
0026     {'name': '/RECO/26.03.0/a', 'files': 10, 'bytes': 1000, 'eligible': True,
0027      'reason': '', 'tape': 'JLAB-TAPE-SE rule OK, 10/10 files'},
0028     {'name': '/RECO/26.03.0/b', 'files': 10, 'bytes': 1000, 'eligible': False,
0029      'reason': 'tape copy incomplete', 'tape': ''},
0030 ], 'totals': {'datasets': 2, 'eligible': 1, 'refused': 1,
0031               'eligible_files': 10, 'eligible_bytes': 1000}}
0032 
0033 
0034 class RseDeletionTests(SimpleTestCase):
0035     def setUp(self):
0036         self.tmp = tempfile.mkdtemp()
0037         self.addCleanup(shutil.rmtree, self.tmp)
0038         settings_patch = override_settings(SWF_TMP_DIR=self.tmp)
0039         settings_patch.enable()
0040         self.addCleanup(settings_patch.disable)
0041         patcher = mock.patch(SEND)
0042         self.send = patcher.start().return_value.send_message
0043         self.send.return_value = True
0044         self.addCleanup(patcher.stop)
0045 
0046     def _write(self, **fields):
0047         now = timezone.now()
0048         record = dict(id=str(uuid.uuid4()), rse='BNL-XRD', pattern='/RECO/26.03.0/*',
0049                       requested_by='op', status='previewed', plan=PLAN,
0050                       previewed_at=now.isoformat(), updated_at=now.isoformat())
0051         record.update(fields)
0052         directory = Path(self.tmp) / 'rse-deletions'
0053         directory.mkdir(exist_ok=True)
0054         (directory / f"{record['id']}.json").write_text(json.dumps(record))
0055         return record
0056 
0057     def _sent(self):
0058         return json.loads(self.send.call_args[0][1])
0059 
0060     def test_preview_queues_the_agent_and_refuses_bad_requests(self):
0061         rec = rd.request_preview(rse='BNL-XRD', pattern='/RECO/26.03.0/*', requested_by='op')
0062         self.assertEqual(rec['status'], 'previewing')
0063         msg = self._sent()
0064         self.assertEqual((msg['phase'], msg['deletion_id'], msg['rse']),
0065                          ('preview', rec['id'], 'BNL-XRD'))
0066         for rse, pattern in (('JLAB-TAPE-SE', '/RECO/26.03.0/*'), ('BNL-XRD', '/RECO/*')):
0067             with self.assertRaises(rd.RseDeletionRequestError):
0068                 rd.request_preview(rse=rse, pattern=pattern, requested_by='op')
0069 
0070     def test_execute_sends_only_eligible_names_after_typed_confirmation(self):
0071         rec = self._write()
0072         with self.assertRaises(rd.RseDeletionRequestError):
0073             rd.request_execute(rec['id'], confirm_rse='EIC-XRD', confirmed_by='op')
0074         self.send.reset_mock()
0075         out = rd.request_execute(rec['id'], confirm_rse='BNL-XRD', confirmed_by='op')
0076         self.assertEqual(out['status'], 'queued')
0077         msg = self._sent()
0078         self.assertEqual((msg['phase'], msg['names'], msg['created_by']),
0079                          ('execute', ['/RECO/26.03.0/a'], 'op'))
0080 
0081     def test_execute_refuses_a_stale_or_unpreviewed_request(self):
0082         old = (timezone.now() - timedelta(hours=2)).isoformat()
0083         for rec in (self._write(previewed_at=old),
0084                     self._write(status='done'),
0085                     self._write(plan=dict(PLAN, datasets=PLAN['datasets'][1:]))):
0086             with self.assertRaises(rd.RseDeletionRequestError):
0087                 rd.request_execute(rec['id'], confirm_rse='BNL-XRD', confirmed_by='op')
0088         with self.assertRaises(rd.RseDeletionRequestError) as e:
0089             rd.request_execute(str(uuid.uuid4()), confirm_rse='BNL-XRD', confirmed_by='op')
0090         self.assertEqual(e.exception.status, 404)
0091         self.send.assert_not_called()
0092 
0093     def test_a_pending_request_the_agent_abandoned_reads_failed(self):
0094         old = (timezone.now() - timedelta(hours=3)).isoformat()
0095         rec = rd.serialize(self._write(status='running', updated_at=old,
0096                                        result={'datasets': [{'name': 'x', 'outcome': 'removed'}]}))
0097         self.assertEqual(rec['status'], 'failed')
0098         self.assertFalse(rec['pending'])
0099         self.assertEqual(rec['result']['datasets'][0]['outcome'], 'removed')
0100         self.assertEqual(rd.serialize(self._write(status='running'))['status'], 'running')
0101 
0102     def test_unreadable_ids_are_not_found(self):
0103         self.assertIsNone(rd.read_request('../../etc/passwd'))
0104         self.assertIsNone(rd.read_request(str(uuid.uuid4())))
0105 
0106     def test_only_the_operations_role_acts_on_the_internal_monitor(self):
0107         factory = APIRequestFactory()
0108         body = {'rse': 'BNL-XRD', 'pattern': '/RECO/26.03.0/*'}
0109         for ops, tunnel, expected in ((False, False, 403), (True, True, 403), (True, False, 202)):
0110             request = factory.post('/api/panda/rse-deletions/', body, format='json')
0111             force_authenticate(request, user=User(username='op'))
0112             with mock.patch('monitor_app.authority.get_authority', return_value={}), \
0113                     mock.patch('monitor_app.authority.is_ops', return_value=ops), \
0114                     mock.patch.object(panda_api, 'is_tunnel_request', return_value=tunnel):
0115                 response = panda_api.rse_deletion_request(request)
0116             self.assertEqual(response.status_code, expected, (ops, tunnel))