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