blob: d0b721fca7cba146d29af30afc2ccb73c336eb3c [file] [log] [blame]
# 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
"""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
# TODO: change to FlaskInternalTask when convert to Flask
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.
mr: common information parsed from the HTTP request.
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 =
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.
cnxn, project_id,
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 =
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):
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 =
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:, issue_id), issue_ids)
yield issue_ids
# All project purge functions are called with cnxn and project_id.
project_purge_functions = (,,,,,,,,,
for f in project_purge_functions:
f(cnxn, project_id)
yield project_id
# def GetReap(self, **kwargs):
# return self.handler(**kwargs)
# def PostReap(self, **kwargs):
# return self.handler(**kwargs)