Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/framework/reap.py b/framework/reap.py
new file mode 100644
index 0000000..6bc5cf0
--- /dev/null
+++ b/framework/reap.py
@@ -0,0 +1,125 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed 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."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+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:
+ # Note: We go straight to services layer because this is an internal
+ # request, not a request from a user.
+ 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.template.ExpungeProjectTemplates,
+ 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