This repository has been archived by the owner on Mar 15, 2018. It is now read-only.
/
tasks.py
222 lines (186 loc) · 8.2 KB
/
tasks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import datetime
from django.conf import settings
from django.core.mail import EmailMessage, EmailMultiAlternatives
import commonware.log
import phpserialize
from celeryutils import task
from hera.contrib.django_utils import flush_urls
import amo
from abuse.models import AbuseReport
from addons.models import Addon
from amo.decorators import set_task_user
from amo.utils import get_email_backend
from bandwagon.models import Collection
from devhub.models import ActivityLog, AppLog
from editors.models import EscalationQueue, EventLog
from market.models import Refund
from reviews.models import Review
from stats.models import Contribution
log = commonware.log.getLogger('z.task')
@task
def send_email(recipient, subject, message, from_email=None,
html_message=None, attachments=None, real_email=False,
cc=None, headers=None, fail_silently=False, async=False,
max_retries=None, **kwargs):
backend = EmailMultiAlternatives if html_message else EmailMessage
connection = get_email_backend(real_email)
result = backend(subject, message,
from_email, recipient, cc=cc, connection=connection,
headers=headers, attachments=attachments)
if html_message:
result.attach_alternative(html_message, 'text/html')
try:
result.send(fail_silently=False)
return True
except Exception as e:
log.error('send_mail failed with error: %s' % e)
if async:
return send_email.retry(exc=e, max_retries=max_retries)
elif not fail_silently:
raise
else:
return False
@task
def flush_front_end_cache_urls(urls, **kw):
"""Accepts a list of urls which will be sent through Hera to the front end
cache. This does no checking for success or failure or whether the URLs
were in the cache to begin with."""
if not urls:
return
log.info(u"Flushing %d URLs from front end cache: (%s)" % (len(urls),
urls))
# Zeus is only interested in complete URLs. We can't just pass a
# prefix to Hera because some URLs will be on SAMO.
for index, url in enumerate(urls):
if not url.startswith('http'):
if '/api/' in url:
urls[index] = u"%s%s" % (settings.SERVICES_URL, url)
else:
urls[index] = u"%s%s" % (settings.SITE_URL, url)
flush_urls(urls)
@task
def set_modified_on_object(obj, **kw):
"""Sets modified on one object at a time."""
try:
log.info('Setting modified on object: %s, %s' %
(obj.__class__.__name__, obj.pk))
obj.update(modified=datetime.datetime.now(), **kw)
except Exception, e:
log.error('Failed to set modified on: %s, %s - %s' %
(obj.__class__.__name__, obj.pk, e))
@task
def delete_logs(items, **kw):
log.info('[%s@%s] Deleting logs' % (len(items), delete_logs.rate_limit))
ActivityLog.objects.filter(pk__in=items).exclude(
action__in=amo.LOG_KEEP).delete()
@task
def delete_stale_contributions(items, **kw):
log.info('[%s@%s] Deleting stale contributions' %
(len(items), delete_stale_contributions.rate_limit))
Contribution.objects.filter(
transaction_id__isnull=True, pk__in=items).delete()
@task
def delete_anonymous_collections(items, **kw):
log.info('[%s@%s] Deleting anonymous collections' %
(len(items), delete_anonymous_collections.rate_limit))
Collection.objects.filter(type=amo.COLLECTION_ANONYMOUS,
pk__in=items).delete()
@task
def delete_incomplete_addons(items, **kw):
log.info('[%s@%s] Deleting incomplete add-ons' %
(len(items), delete_incomplete_addons.rate_limit))
for addon in Addon.objects.filter(
highest_status=0, status=0, pk__in=items):
try:
addon.delete('Deleted for incompleteness')
except Exception as e:
log.error("Couldn't delete add-on %s: %s" % (addon.id, e))
@task
def migrate_editor_eventlog(items, **kw):
log.info('[%s@%s] Migrating eventlog items' %
(len(items), migrate_editor_eventlog.rate_limit))
for item in EventLog.objects.filter(pk__in=items):
kw = dict(user=item.user, created=item.created)
if item.action == 'review_delete':
details = None
try:
details = phpserialize.loads(item.notes)
except ValueError:
pass
amo.log(amo.LOG.DELETE_REVIEW, item.changed_id, details=details,
**kw)
elif item.action == 'review_approve':
try:
r = Review.objects.get(pk=item.changed_id)
amo.log(amo.LOG.ADD_REVIEW, r, r.addon, **kw)
except Review.DoesNotExist:
log.warning("Couldn't find review for %d" % item.changed_id)
@task
@set_task_user
def find_abuse_escalations(addon_id, **kw):
weekago = datetime.date.today() - datetime.timedelta(days=7)
add_to_queue = True
for abuse in AbuseReport.recent_high_abuse_reports(1, weekago, addon_id):
if EscalationQueue.objects.filter(addon=abuse.addon).exists():
# App is already in the queue, no need to re-add it.
log.info(u'[addon:%s] High abuse reports, but already escalated' %
(abuse.addon,))
add_to_queue = False
# We have an abuse report... has it been detected and dealt with?
logs = (AppLog.objects.filter(
activity_log__action=amo.LOG.ESCALATED_HIGH_ABUSE.id,
addon=abuse.addon).order_by('-created'))
if logs:
abuse_since_log = AbuseReport.recent_high_abuse_reports(
1, logs[0].created, addon_id)
# If no abuse reports have happened since the last logged abuse
# report, do not add to queue.
if not abuse_since_log:
log.info(u'[addon:%s] High abuse reports, but none since last '
u'escalation' % abuse.addon)
continue
# If we haven't bailed out yet, escalate this app.
msg = u'High number of abuse reports detected'
if add_to_queue:
EscalationQueue.objects.create(addon=abuse.addon)
amo.log(amo.LOG.ESCALATED_HIGH_ABUSE, abuse.addon,
abuse.addon.current_version, details={'comments': msg})
log.info(u'[addon:%s] %s' % (abuse.addon, msg))
@task
@set_task_user
def find_refund_escalations(addon_id, **kw):
try:
addon = Addon.objects.get(pk=addon_id)
except Addon.DoesNotExist:
log.info(u'[addon:%s] Task called but no addon found.' % addon_id)
return
refund_threshold = 0.05
weekago = datetime.date.today() - datetime.timedelta(days=7)
add_to_queue = True
ratio = Refund.recent_refund_ratio(addon.id, weekago)
if ratio > refund_threshold:
if EscalationQueue.objects.filter(addon=addon).exists():
# App is already in the queue, no need to re-add it.
log.info(u'[addon:%s] High refunds, but already escalated' % addon)
add_to_queue = False
# High refunds... has it been detected and dealt with already?
logs = (AppLog.objects.filter(
activity_log__action=amo.LOG.ESCALATED_HIGH_REFUNDS.id,
addon=addon).order_by('-created', '-id'))
if logs:
since_ratio = Refund.recent_refund_ratio(addon.id, logs[0].created)
# If not high enough ratio since the last logged, do not add to
# the queue.
if not since_ratio > refund_threshold:
log.info(u'[addon:%s] High refunds, but not enough since last '
u'escalation. Ratio: %.0f%%' % (addon,
since_ratio * 100))
return
# If we haven't bailed out yet, escalate this app.
msg = u'High number of refund requests (%.0f%%) detected.' % (
(ratio * 100),)
if add_to_queue:
EscalationQueue.objects.create(addon=addon)
amo.log(amo.LOG.ESCALATED_HIGH_REFUNDS, addon,
addon.current_version, details={'comments': msg})
log.info(u'[addon:%s] %s' % (addon, msg))