[LNT] r306901 - Add a delete method with auth to the runs API

Chris Matthews via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 30 15:39:22 PDT 2017


Author: cmatthews
Date: Fri Jun 30 15:39:22 2017
New Revision: 306901

URL: http://llvm.org/viewvc/llvm-project?rev=306901&view=rev
Log:
Add a delete method with auth to the runs API

Add a token based auth system to the API.  Add a delete method to the
runs endpoint for authorized users.  This will make it easier for non
admins to delete their data.  Hopefully we can get a better auth system
in place, but for now it is single key per instance.

Added:
    lnt/trunk/tests/server/ui/test_api_deletes.py
Modified:
    lnt/trunk/lnt/server/config.py
    lnt/trunk/lnt/server/db/testsuitedb.py
    lnt/trunk/lnt/server/ui/api.py
    lnt/trunk/tests/SharedInputs/SmallInstance/lnt.cfg
    lnt/trunk/tests/server/ui/change_processing.py

Modified: lnt/trunk/lnt/server/config.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/config.py?rev=306901&r1=306900&r2=306901&view=diff
==============================================================================
--- lnt/trunk/lnt/server/config.py (original)
+++ lnt/trunk/lnt/server/config.py Fri Jun 30 15:39:22 2017
@@ -110,6 +110,7 @@ class Config:
         # FIXME: Remove this default.
         tempDir = data.get('tmp_dir', 'viewer/resources/graphs')
         blacklist = data.get('blacklist', None)
+        api_auth_token = data.get('api_auth_token', None)
         if blacklist and baseDir:
             blacklist = os.path.join(baseDir, blacklist)
         else:
@@ -123,7 +124,7 @@ class Config:
                                                  default_email_config,
                                                  0))
                            for k, v in data['databases'].items()]),
-                      blacklist)
+                      blacklist, api_auth_token)
     
     @staticmethod
     def dummy_instance():
@@ -142,9 +143,10 @@ class Config:
                       profileDirPath,
                       secretKey,
                       dbInfo,
-                      blacklist)
+                      blacklist,
+                      "test_key")
 
-    def __init__(self, name, zorgURL, dbDir, tempDir, profileDir, secretKey, databases, blacklist):
+    def __init__(self, name, zorgURL, dbDir, tempDir, profileDir, secretKey, databases, blacklist, api_auth_token=None):
         self.name = name
         self.zorgURL = zorgURL
         self.dbDir = dbDir
@@ -157,6 +159,7 @@ class Config:
         self.databases = databases
         for db in self.databases.values():
             db.config = self
+        self.api_auth_token = api_auth_token
 
     def get_database(self, name, echo=False):
         """

Modified: lnt/trunk/lnt/server/db/testsuitedb.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/db/testsuitedb.py?rev=306901&r1=306900&r2=306901&view=diff
==============================================================================
--- lnt/trunk/lnt/server/db/testsuitedb.py (original)
+++ lnt/trunk/lnt/server/db/testsuitedb.py Fri Jun 30 15:39:22 2017
@@ -12,6 +12,7 @@ import os
 import sqlalchemy
 from flask import session
 from sqlalchemy import *
+from typing import List
 
 import testsuite
 import lnt.testing.profile.profile as profile
@@ -1003,5 +1004,37 @@ supplied run is missing required run par
     def get_next_runs_on_machine(self, run, N):
         return self.get_adjacent_runs_on_machine(run, N, direction = 1)
 
+    def delete_runs(self, run_ids, commit=False):
+        # type: (object, List[int], bool) -> None
+        """Delete the following Runs, their Samples, Field Changes and Regression Indicators.
+
+        :param run_ids: list of the run ids to delete.
+        :param commit: commit now?
+        """
+
+        # Delete all samples associated with those runs.
+        self.query(self.Sample). \
+            filter(self.Sample.run_id.in_(run_ids)). \
+            delete(synchronize_session=False)
+
+        # Delete all FieldChanges and RegressionIndicators
+        for r in run_ids:
+            fcs = self.query(self.FieldChange). \
+                filter(self.FieldChange.run_id == r).all()
+            for f in fcs:
+                ris = self.query(self.RegressionIndicator). \
+                    filter(self.RegressionIndicator.field_change_id == f.id).all()
+                for ri in ris:
+                    self.delete(ri)
+                self.delete(f)
+
+        # Delete all those runs.
+        self.query(self.Run). \
+            filter(self.Run.id.in_(run_ids)). \
+            delete(synchronize_session=False)
+
+        if commit:
+            self.commit()
+
     def __repr__(self):
         return "{} (on {})".format(self.name, self.v4db.path)

Modified: lnt/trunk/lnt/server/ui/api.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/lnt/server/ui/api.py?rev=306901&r1=306900&r2=306901&view=diff
==============================================================================
--- lnt/trunk/lnt/server/ui/api.py (original)
+++ lnt/trunk/lnt/server/ui/api.py Fri Jun 30 15:39:22 2017
@@ -8,6 +8,7 @@ from sqlalchemy.orm.exc import NoResultF
 
 from lnt.server.ui.util import convert_revision
 from lnt.testing import PASS
+from functools import wraps
 
 
 def in_db(func):
@@ -32,6 +33,16 @@ def in_db(func):
     return wrap
 
 
+def requires_auth_token(f):
+    @wraps(f)
+    def decorated(*args, **kwargs):
+        token = request.headers.get("AuthToken", None)
+        if not current_app.old_config.api_auth_token or token != current_app.old_config.api_auth_token:
+            return abort(401, msg="Auth Token must be passed in AuthToken header, and included in LNT config.")
+        return f(*args, **kwargs)
+    return decorated
+
+
 def with_ts(obj):
     """For Url type fields to work, the objects we return must have a test-suite
     and database attribute set, the function attempts to set them."""
@@ -169,6 +180,23 @@ class Runs(Resource):
         full_run['samples'] = ret
         return jsonify(full_run)
 
+    @staticmethod
+    @requires_auth_token
+    def delete(run_id):
+        ts = request.get_testsuite()
+
+        try:
+            run = ts.query(ts.Run) \
+                .join(ts.Machine) \
+                .join(ts.Order) \
+                .filter(ts.Run.id == run_id) \
+                .options(joinedload('order')) \
+                .one()
+        except sqlalchemy.orm.exc.NoResultFound:
+            return abort(404, msg="Did not find run " + str(run_id))
+        ts.delete_runs([run_id], commit=True)
+        return
+
 
 class Order(Resource):
     method_decorators = [in_db]

Modified: lnt/trunk/tests/SharedInputs/SmallInstance/lnt.cfg
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/tests/SharedInputs/SmallInstance/lnt.cfg?rev=306901&r1=306900&r2=306901&view=diff
==============================================================================
--- lnt/trunk/tests/SharedInputs/SmallInstance/lnt.cfg (original)
+++ lnt/trunk/tests/SharedInputs/SmallInstance/lnt.cfg Fri Jun 30 15:39:22 2017
@@ -24,6 +24,9 @@ db_dir = 'data'
 # Secret key for this server instance.
 secret_key = '540de6a499d57deecc880ce399a95de8b7bf43fb'
 
+# API Auth Token
+api_auth_token = "test_token"
+
 # The list of available databases, and their properties. At a minimum, there
 # should be a 'default' entry for the default database.
 databases = {

Modified: lnt/trunk/tests/server/ui/change_processing.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/tests/server/ui/change_processing.py?rev=306901&r1=306900&r2=306901&view=diff
==============================================================================
--- lnt/trunk/tests/server/ui/change_processing.py (original)
+++ lnt/trunk/tests/server/ui/change_processing.py Fri Jun 30 15:39:22 2017
@@ -72,6 +72,7 @@ class ChangeProcessingTests(unittest.Tes
                                          machine,
                                          test,
                                          a_field)
+        field_change.run = run
         ts_db.add(field_change)
 
         fc_mach2 = ts_db.FieldChange(order1234,
@@ -79,12 +80,15 @@ class ChangeProcessingTests(unittest.Tes
                                          machine2,
                                          test,
                                          a_field)
+        fc_mach2.run = run2
         ts_db.add(fc_mach2)
 
 
         field_change2 = self.field_change2 = ts_db.FieldChange(order1235, order1236, machine,
                                           test,
                                           a_field)
+
+        field_change2.run = run
         ts_db.add(field_change2)
 
         field_change3 = self.field_change3 = ts_db.FieldChange(order1237, order1238, machine,
@@ -192,6 +196,30 @@ class ChangeProcessingTests(unittest.Tes
         delete_fieldchange(self.ts_db, self.field_change2)
         delete_fieldchange(self.ts_db, self.field_change3)
 
+    def test_run_deletion(self):
+        """Do the FC and RIs get cleaned up when runs are deleted?"""
+        ts_db = self.ts_db
+        run_ids = ts_db.query(ts_db.Run.id).all()
+        fc_ids = ts_db.query(ts_db.FieldChange.id).all()
+        ri_ids = ts_db.query(ts_db.RegressionIndicator.id).all()
+
+        ts_db.delete_runs([r[0] for r in run_ids])
+        run_ids_new = ts_db.query(ts_db.Run.id).all()
+        fc_ids_new = ts_db.query(ts_db.FieldChange.id).all()
+        ri_ids_new = ts_db.query(ts_db.RegressionIndicator.id).all()
+        # Make sure there was some runs.
+        self.assertNotEqual(len(run_ids), 0)
+        self.assertNotEqual(len(fc_ids), 0)
+        self.assertNotEqual(len(ri_ids), 0)
+
+        # Now make sure there were all deleted.
+        self.assertEqual(len(run_ids_new), 0)
+
+        # Not all the FCs are covered by the runs.
+        self.assertEqual(len(fc_ids_new), 1)
+
+        self.assertEqual(len(ri_ids_new), 0)
+
 
 if __name__ == '__main__':
     unittest.main(argv=[sys.argv[0], ])

Added: lnt/trunk/tests/server/ui/test_api_deletes.py
URL: http://llvm.org/viewvc/llvm-project/lnt/trunk/tests/server/ui/test_api_deletes.py?rev=306901&view=auto
==============================================================================
--- lnt/trunk/tests/server/ui/test_api_deletes.py (added)
+++ lnt/trunk/tests/server/ui/test_api_deletes.py Fri Jun 30 15:39:22 2017
@@ -0,0 +1,60 @@
+# Check that the LNT REST JSON API is working.
+# create temporary instance
+# RUN: rm -rf %t.instance
+# RUN: python %{shared_inputs}/create_temp_instance.py \
+# RUN:     %s %{shared_inputs}/SmallInstance \
+# RUN:     %t.instance %S/Inputs/V4Pages_extra_records.sql
+#
+# RUN: python %s %t.instance
+
+import logging
+import sys
+import unittest
+
+import lnt.server.db.migrate
+import lnt.server.ui.app
+from V4Pages import check_json
+
+logging.basicConfig(level=logging.DEBUG)
+
+
+class JSONAPIDeleteTester(unittest.TestCase):
+    """Test the REST api."""
+
+    def setUp(self):
+        """Bind to the LNT test instance."""
+        _, instance_path = sys.argv
+        app = lnt.server.ui.app.App.create_standalone(instance_path)
+        app.testing = True
+        self.client = app.test_client()
+
+    def test_run_api(self):
+        """Check /runs/n can be deleted."""
+        client = self.client
+
+        j = check_json(client, 'api/db_default/v4/nts/runs/1')
+        sample_ids = [s['id'] for s in j['samples']]
+        self.assertNotEqual(len(sample_ids), 0)
+        for sid in sample_ids:
+            resp = client.get('api/db_default/v4/nts/samples/{}'.format(sid))
+            self.assertEqual(resp.status_code, 200)
+
+        resp = client.delete('api/db_default/v4/nts/runs/1')
+        self.assertEqual(resp.status_code, 401)
+
+        resp = client.delete('api/db_default/v4/nts/runs/1', headers={'AuthToken': 'wrong token'})
+        self.assertEqual(resp.status_code, 401)
+
+        resp = client.delete('api/db_default/v4/nts/runs/1', headers={'AuthToken': 'test_token'})
+        self.assertEqual(resp.status_code, 200)
+
+        resp = client.get('api/db_default/v4/nts/runs/1')
+        self.assertEqual(resp.status_code, 404)
+
+        for sid in sample_ids:
+            resp = client.get('api/db_default/v4/nts/samples/{}'.format(sid))
+            self.assertEqual(resp.status_code, 404)
+
+
+if __name__ == '__main__':
+    unittest.main(argv=[sys.argv[0], ])




More information about the llvm-commits mailing list