blob: 4654964086ae1ca95f9dfd7e36156d986feecc41 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file or at
4# https://developers.google.com/open-source/licenses/bsd
5
6"""A class to handle cron requests to expunge doomed and deletable projects."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import logging
12import time
13
14from framework import jsonfeed
15
16RUN_DURATION_LIMIT = 50 * 60 # 50 minutes
17
18
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020019class Reap(jsonfeed.FlaskInternalTask):
Copybara854996b2021-09-07 19:36:02 +000020 """Look for doomed and deletable projects and delete them."""
21
22 def HandleRequest(self, mr):
23 """Update/Delete doomed and deletable projects as needed.
24
25 Args:
26 mr: common information parsed from the HTTP request.
27
28 Returns:
29 Results dictionary in JSON format. The JSON will look like this:
30 {
31 'doomed_project_ids': <int>,
32 'expunged_project_ids': <int>
33 }
34 doomed_project_ids are the projects which have been marked as deletable.
35 expunged_project_ids are the projects that have either been completely
36 expunged or are in the midst of being expunged.
37 """
38 doomed_project_ids = self._MarkDoomedProjects(mr.cnxn)
39 expunged_project_ids = self._ExpungeDeletableProjects(mr.cnxn)
40 return {
41 'doomed_project_ids': doomed_project_ids,
42 'expunged_project_ids': expunged_project_ids,
43 }
44
45 def _MarkDoomedProjects(self, cnxn):
46 """No longer needed projects get doomed, and this marks them deletable."""
47 now = int(time.time())
48 doomed_project_rows = self.services.project.project_tbl.Select(
49 cnxn, cols=['project_id'],
50 # We only match projects with real timestamps and not delete_time = 0.
51 where=[('delete_time < %s', [now]), ('delete_time != %s', [0])],
52 state='archived', limit=1000)
53 doomed_project_ids = [row[0] for row in doomed_project_rows]
54 for project_id in doomed_project_ids:
55 # Note: We go straight to services layer because this is an internal
56 # request, not a request from a user.
57 self.services.project.MarkProjectDeletable(
58 cnxn, project_id, self.services.config)
59
60 return doomed_project_ids
61
62 def _ExpungeDeletableProjects(self, cnxn):
63 """Chip away at deletable projects until they are gone."""
64 request_deadline = time.time() + RUN_DURATION_LIMIT
65
66 deletable_project_rows = self.services.project.project_tbl.Select(
67 cnxn, cols=['project_id'], state='deletable', limit=100)
68 deletable_project_ids = [row[0] for row in deletable_project_rows]
69 # expunged_project_ids will contain projects that have either been
70 # completely expunged or are in the midst of being expunged.
71 expunged_project_ids = set()
72 for project_id in deletable_project_ids:
73 for _part in self._ExpungeParts(cnxn, project_id):
74 expunged_project_ids.add(project_id)
75 if time.time() > request_deadline:
76 return list(expunged_project_ids)
77
78 return list(expunged_project_ids)
79
80 def _ExpungeParts(self, cnxn, project_id):
81 """Delete all data from the specified project, one part at a time.
82
83 This method purges all data associated with the specified project. The
84 following is purged:
85 * All issues of the project.
86 * Project config.
87 * Saved queries.
88 * Filter rules.
89 * Former locations.
90 * Local ID counters.
91 * Quick edit history.
92 * Item stars.
93 * Project from the DB.
94
95 Returns a generator whose return values can be either issue
96 ids or the specified project id. The returned values are intended to be
97 iterated over and not read.
98 """
99 # Purge all issues of the project.
100 while True:
101 issue_id_rows = self.services.issue.issue_tbl.Select(
102 cnxn, cols=['id'], project_id=project_id, limit=1000)
103 issue_ids = [row[0] for row in issue_id_rows]
104 for issue_id in issue_ids:
105 self.services.issue_star.ExpungeStars(cnxn, issue_id)
106 self.services.issue.ExpungeIssues(cnxn, issue_ids)
107 yield issue_ids
108 break
109
110 # All project purge functions are called with cnxn and project_id.
111 project_purge_functions = (
112 self.services.config.ExpungeConfig,
113 self.services.template.ExpungeProjectTemplates,
114 self.services.features.ExpungeSavedQueriesExecuteInProject,
115 self.services.features.ExpungeFilterRules,
116 self.services.issue.ExpungeFormerLocations,
117 self.services.issue.ExpungeLocalIDCounters,
118 self.services.features.ExpungeQuickEditHistory,
119 self.services.project_star.ExpungeStars,
120 self.services.project.ExpungeProject,
121 )
122
123 for f in project_purge_functions:
124 f(cnxn, project_id)
125 yield project_id
Adrià Vilanova Martínezde942802022-07-15 14:06:55 +0200126
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +0200127 def GetReap(self, **kwargs):
128 return self.handler(**kwargs)