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