Index: appengine/monorail/framework/reap.py |
diff --git a/appengine/monorail/framework/reap.py b/appengine/monorail/framework/reap.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4c2bbd706d0fb277efd0a3213ba8f0cb3c709d7d |
--- /dev/null |
+++ b/appengine/monorail/framework/reap.py |
@@ -0,0 +1,119 @@ |
+# Copyright 2016 The Chromium Authors. All rights reserved. |
+# Use of this source code is govered by a BSD-style |
+# license that can be found in the LICENSE file or at |
+# https://developers.google.com/open-source/licenses/bsd |
+ |
+"""A class to handle cron requests to expunge doomed and deletable projects.""" |
+ |
+import logging |
+import time |
+ |
+from framework import jsonfeed |
+ |
+RUN_DURATION_LIMIT = 50 * 60 # 50 minutes |
+ |
+ |
+class Reap(jsonfeed.InternalTask): |
+ """Look for doomed and deletable projects and delete them.""" |
+ |
+ def HandleRequest(self, mr): |
+ """Update/Delete doomed and deletable projects as needed. |
+ |
+ Args: |
+ mr: common information parsed from the HTTP request. |
+ |
+ Returns: |
+ Results dictionary in JSON format. The JSON will look like this: |
+ { |
+ 'doomed_project_ids': <int>, |
+ 'expunged_project_ids': <int> |
+ } |
+ doomed_project_ids are the projects which have been marked as deletable. |
+ expunged_project_ids are the projects that have either been completely |
+ expunged or are in the midst of being expunged. |
+ """ |
+ doomed_project_ids = self._MarkDoomedProjects(mr.cnxn) |
+ expunged_project_ids = self._ExpungeDeletableProjects(mr.cnxn) |
+ return { |
+ 'doomed_project_ids': doomed_project_ids, |
+ 'expunged_project_ids': expunged_project_ids, |
+ } |
+ |
+ def _MarkDoomedProjects(self, cnxn): |
+ """No longer needed projects get doomed, and this marks them deletable.""" |
+ now = int(time.time()) |
+ doomed_project_rows = self.services.project.project_tbl.Select( |
+ cnxn, cols=['project_id'], |
+ # We only match projects with real timestamps and not delete_time = 0. |
+ where=[('delete_time < %s', [now]), ('delete_time != %s', [0])], |
+ state='archived', limit=1000) |
+ doomed_project_ids = [row[0] for row in doomed_project_rows] |
+ for project_id in doomed_project_ids: |
+ self.services.project.MarkProjectDeletable( |
+ cnxn, project_id, self.services.config) |
+ |
+ return doomed_project_ids |
+ |
+ def _ExpungeDeletableProjects(self, cnxn): |
+ """Chip away at deletable projects until they are gone.""" |
+ request_deadline = time.time() + RUN_DURATION_LIMIT |
+ |
+ deletable_project_rows = self.services.project.project_tbl.Select( |
+ cnxn, cols=['project_id'], state='deletable', limit=100) |
+ deletable_project_ids = [row[0] for row in deletable_project_rows] |
+ # expunged_project_ids will contain projects that have either been |
+ # completely expunged or are in the midst of being expunged. |
+ expunged_project_ids = set() |
+ for project_id in deletable_project_ids: |
+ for _part in self._ExpungeParts(cnxn, project_id): |
+ expunged_project_ids.add(project_id) |
+ if time.time() > request_deadline: |
+ return list(expunged_project_ids) |
+ |
+ return list(expunged_project_ids) |
+ |
+ def _ExpungeParts(self, cnxn, project_id): |
+ """Delete all data from the specified project, one part at a time. |
+ |
+ This method purges all data associated with the specified project. The |
+ following is purged: |
+ * All issues of the project. |
+ * Project config. |
+ * Saved queries. |
+ * Filter rules. |
+ * Former locations. |
+ * Local ID counters. |
+ * Quick edit history. |
+ * Item stars. |
+ * Project from the DB. |
+ |
+ Returns a generator whose return values can be either issue |
+ ids or the specified project id. The returned values are intended to be |
+ iterated over and not read. |
+ """ |
+ # Purge all issues of the project. |
+ while True: |
+ issue_id_rows = self.services.issue.issue_tbl.Select( |
+ cnxn, cols=['id'], project_id=project_id, limit=1000) |
+ issue_ids = [row[0] for row in issue_id_rows] |
+ for issue_id in issue_ids: |
+ self.services.issue_star.ExpungeStars(cnxn, issue_id) |
+ self.services.issue.ExpungeIssues(cnxn, issue_ids) |
+ yield issue_ids |
+ break |
+ |
+ # All project purge functions are called with cnxn and project_id. |
+ project_purge_functions = ( |
+ self.services.config.ExpungeConfig, |
+ self.services.features.ExpungeSavedQueriesExecuteInProject, |
+ self.services.features.ExpungeFilterRules, |
+ self.services.issue.ExpungeFormerLocations, |
+ self.services.issue.ExpungeLocalIDCounters, |
+ self.services.features.ExpungeQuickEditHistory, |
+ self.services.project_star.ExpungeStars, |
+ self.services.project.ExpungeProject, |
+ ) |
+ |
+ for f in project_purge_functions: |
+ f(cnxn, project_id) |
+ yield project_id |