2010-09-23 00:42:49 +04:00
|
|
|
import json
|
2010-06-17 22:20:56 +04:00
|
|
|
import os
|
2010-05-07 10:47:26 +04:00
|
|
|
import random
|
2010-08-19 23:55:09 +04:00
|
|
|
from PIL import Image
|
2010-02-05 02:14:16 +03:00
|
|
|
import socket
|
2010-08-19 23:55:09 +04:00
|
|
|
import StringIO
|
2010-08-20 22:56:17 +04:00
|
|
|
import time
|
2010-03-15 23:37:20 +03:00
|
|
|
import urllib2
|
2010-08-10 21:40:50 +04:00
|
|
|
from urlparse import urlparse
|
2010-02-05 02:14:16 +03:00
|
|
|
|
2010-03-15 23:37:20 +03:00
|
|
|
from django import http
|
2010-02-05 02:14:16 +03:00
|
|
|
from django.conf import settings
|
2010-05-02 10:46:17 +04:00
|
|
|
from django.core.cache import cache, parse_backend_uri
|
2010-09-23 00:42:49 +04:00
|
|
|
from django.http import HttpResponse, HttpResponseBadRequest
|
2010-02-05 02:14:16 +03:00
|
|
|
from django.views.decorators.cache import never_cache
|
2010-03-15 23:37:20 +03:00
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
2010-09-23 00:42:49 +04:00
|
|
|
from django.views.decorators.http import require_POST
|
2010-02-05 02:14:16 +03:00
|
|
|
|
2010-08-05 03:29:28 +04:00
|
|
|
import caching.invalidation
|
2011-01-25 02:29:58 +03:00
|
|
|
import celery.exceptions
|
2010-08-20 22:56:17 +04:00
|
|
|
import celery.task
|
2010-05-20 01:05:20 +04:00
|
|
|
import commonware.log
|
2010-02-05 04:51:14 +03:00
|
|
|
import jingo
|
2010-03-15 23:37:20 +03:00
|
|
|
import phpserialize as php
|
|
|
|
|
2010-10-16 01:50:46 +04:00
|
|
|
import amo
|
2010-09-22 01:27:10 +04:00
|
|
|
import mongoutils
|
2010-08-10 21:40:50 +04:00
|
|
|
from hera.contrib.django_utils import get_hera
|
2010-04-23 03:17:23 +04:00
|
|
|
from stats.models import Contribution, ContributionError, SubscriptionEvent
|
2010-12-27 23:56:23 +03:00
|
|
|
from applications.management.commands import dump_apps
|
2011-01-25 02:29:58 +03:00
|
|
|
from . import tasks
|
2010-02-05 04:51:14 +03:00
|
|
|
|
2010-11-24 03:03:11 +03:00
|
|
|
monitor_log = commonware.log.getLogger('z.monitor')
|
2010-05-20 01:05:20 +04:00
|
|
|
paypal_log = commonware.log.getLogger('z.paypal')
|
2010-09-23 00:42:49 +04:00
|
|
|
csp_log = commonware.log.getLogger('z.csp')
|
2010-02-05 02:14:16 +03:00
|
|
|
|
2010-05-13 08:12:11 +04:00
|
|
|
|
2010-08-05 03:29:28 +04:00
|
|
|
def check_redis():
|
|
|
|
redis = caching.invalidation.get_redis_backend()
|
|
|
|
try:
|
|
|
|
return redis.info(), None
|
|
|
|
except Exception, e:
|
2010-11-24 03:03:11 +03:00
|
|
|
monitor_log.critical('Failed to chat with redis: (%s)' % e)
|
2010-08-05 03:29:28 +04:00
|
|
|
return None, e
|
|
|
|
|
|
|
|
|
2011-01-25 02:29:58 +03:00
|
|
|
def check_rabbit():
|
|
|
|
# Figure out all the queues we're using. celery is the default.
|
|
|
|
# We're skipping the celery queue for now since it could be backed up and
|
|
|
|
# we don't depend on it as much as the devhub and images queues.
|
|
|
|
queues = [] # ['celery']
|
|
|
|
queues.extend(set(r['queue'] for r in settings.CELERY_ROUTES.values()))
|
|
|
|
|
|
|
|
rv = {}
|
|
|
|
for queue in queues:
|
|
|
|
start = time.time()
|
|
|
|
result = tasks.ping.apply_async(routing_key=queue)
|
|
|
|
try:
|
|
|
|
result.get(timeout=2)
|
|
|
|
rv[queue] = time.time() - start
|
|
|
|
except (AttributeError, celery.exceptions.TimeoutError):
|
|
|
|
monitor_log.critical(
|
|
|
|
'Celery[%s] did not respond within 1 second.' % queue)
|
|
|
|
rv[queue] = None
|
|
|
|
return rv
|
|
|
|
|
|
|
|
|
2010-02-05 02:14:16 +03:00
|
|
|
@never_cache
|
|
|
|
def monitor(request):
|
|
|
|
|
2010-02-05 08:13:41 +03:00
|
|
|
# For each check, a boolean pass/fail status to show in the template
|
|
|
|
status_summary = {}
|
2010-02-05 02:14:16 +03:00
|
|
|
status = 200
|
|
|
|
|
2010-02-05 08:13:41 +03:00
|
|
|
# Check all memcached servers
|
|
|
|
scheme, servers, _ = parse_backend_uri(settings.CACHE_BACKEND)
|
|
|
|
memcache_results = []
|
|
|
|
status_summary['memcache'] = True
|
2010-02-05 02:14:16 +03:00
|
|
|
if 'memcached' in scheme:
|
|
|
|
hosts = servers.split(';')
|
|
|
|
for host in hosts:
|
|
|
|
ip, port = host.split(':')
|
|
|
|
try:
|
|
|
|
s = socket.socket()
|
|
|
|
s.connect((ip, int(port)))
|
|
|
|
except Exception, e:
|
2010-02-05 08:13:41 +03:00
|
|
|
result = False
|
|
|
|
status_summary['memcache'] = False
|
2010-11-24 03:03:11 +03:00
|
|
|
monitor_log.critical('Failed to connect to memcached (%s): %s' %
|
2010-03-24 22:51:03 +03:00
|
|
|
(host, e))
|
2010-02-05 02:14:16 +03:00
|
|
|
else:
|
2010-02-05 08:13:41 +03:00
|
|
|
result = True
|
2010-02-05 02:14:16 +03:00
|
|
|
finally:
|
|
|
|
s.close()
|
|
|
|
|
2010-02-05 08:13:41 +03:00
|
|
|
memcache_results.append((ip, port, result))
|
|
|
|
if len(memcache_results) < 2:
|
|
|
|
status_summary['memcache'] = False
|
2010-11-24 03:03:11 +03:00
|
|
|
monitor_log.warning('You should have 2+ memcache servers. You have %s.' %
|
2010-03-24 22:51:03 +03:00
|
|
|
len(memcache_results))
|
|
|
|
if not memcache_results:
|
|
|
|
status_summary['memcache'] = False
|
2010-11-24 03:03:11 +03:00
|
|
|
monitor_log.info('Memcache is not configured.')
|
2010-02-05 02:14:16 +03:00
|
|
|
|
2010-08-19 23:55:09 +04:00
|
|
|
# Check Libraries and versions
|
|
|
|
libraries_results = []
|
|
|
|
status_summary['libraries'] = True
|
|
|
|
try:
|
|
|
|
Image.new('RGB', (16, 16)).save(StringIO.StringIO(), 'JPEG')
|
|
|
|
libraries_results.append(('PIL+JPEG', True, 'Got it!'))
|
|
|
|
except Exception, e:
|
|
|
|
status_summary['libraries'] = False
|
|
|
|
msg = "Failed to create a jpeg image: %s" % e
|
|
|
|
libraries_results.append(('PIL+JPEG', False, msg))
|
|
|
|
|
2010-12-23 03:30:13 +03:00
|
|
|
if settings.SPIDERMONKEY:
|
|
|
|
if os.access(settings.SPIDERMONKEY, os.R_OK):
|
|
|
|
libraries_results.append(('Spidermonkey is ready!', True, None))
|
|
|
|
# TODO: see if it works?
|
|
|
|
else:
|
|
|
|
status_summary['libraries'] = False
|
|
|
|
msg = "You said it was at (%s)" % settings.SPIDERMONKEY
|
|
|
|
libraries_results.append(('Spidermonkey not found!', False, msg))
|
|
|
|
else:
|
|
|
|
status_summary['libraries'] = False
|
|
|
|
msg = "Please set SPIDERMONKEY in your settings file."
|
|
|
|
libraries_results.append(("Spidermonkey isn't set up.", False, msg))
|
|
|
|
|
2010-06-17 22:20:56 +04:00
|
|
|
# Check file paths / permissions
|
2011-01-27 05:20:40 +03:00
|
|
|
rw = (settings.TMP_PATH,
|
|
|
|
settings.NETAPP_STORAGE,
|
|
|
|
settings.UPLOADS_PATH,
|
|
|
|
settings.ADDONS_PATH,
|
|
|
|
settings.GUARDED_ADDONS_PATH,
|
|
|
|
settings.ADDON_ICONS_PATH,
|
|
|
|
settings.COLLECTIONS_ICON_PATH,
|
|
|
|
settings.PREVIEWS_PATH,
|
|
|
|
settings.USERPICS_PATH,
|
|
|
|
settings.SPHINX_CATALOG_PATH,
|
|
|
|
settings.SPHINX_LOG_PATH,
|
|
|
|
dump_apps.Command.JSON_PATH)
|
|
|
|
r = [os.path.join(settings.ROOT, 'locale')]
|
|
|
|
filepaths = [(path, os.R_OK | os.W_OK, "We want read + write")
|
|
|
|
for path in rw]
|
|
|
|
filepaths += [(path, os.R_OK, "We want read") for path in r]
|
2010-06-17 22:20:56 +04:00
|
|
|
filepath_results = []
|
|
|
|
filepath_status = True
|
|
|
|
|
|
|
|
for path, perms, notes in filepaths:
|
2010-12-27 23:56:23 +03:00
|
|
|
path_exists = os.path.exists(path)
|
2010-06-17 22:20:56 +04:00
|
|
|
path_perms = os.access(path, perms)
|
|
|
|
filepath_status = filepath_status and path_exists and path_perms
|
|
|
|
filepath_results.append((path, path_exists, path_perms, notes))
|
|
|
|
|
|
|
|
status_summary['filepaths'] = filepath_status
|
|
|
|
|
2010-08-10 21:40:50 +04:00
|
|
|
# Check Redis
|
2010-08-05 03:29:28 +04:00
|
|
|
redis_results = [None, 'REDIS_BACKEND is not set']
|
|
|
|
if getattr(settings, 'REDIS_BACKEND', False):
|
|
|
|
redis_results = check_redis()
|
|
|
|
status_summary['redis'] = bool(redis_results[0])
|
|
|
|
|
2011-01-25 02:29:58 +03:00
|
|
|
rabbit_results = check_rabbit()
|
|
|
|
status_summary['rabbit'] = all(rabbit_results.values())
|
|
|
|
|
2010-08-10 21:40:50 +04:00
|
|
|
# Check Hera
|
|
|
|
hera_results = []
|
|
|
|
status_summary['hera'] = True
|
|
|
|
for i in settings.HERA:
|
|
|
|
r = {'location': urlparse(i['LOCATION'])[1],
|
|
|
|
'result': bool(get_hera(i))}
|
|
|
|
hera_results.append(r)
|
|
|
|
if not hera_results[-1]['result']:
|
|
|
|
status_summary['hera'] = False
|
|
|
|
|
2010-09-22 01:27:10 +04:00
|
|
|
# Check Mongo
|
|
|
|
mongo_results = []
|
|
|
|
status_summary['mongo'] = mongoutils.connect_mongo()
|
|
|
|
|
2010-06-17 22:20:56 +04:00
|
|
|
# If anything broke, send HTTP 500
|
|
|
|
if not all(status_summary):
|
|
|
|
status = 500
|
|
|
|
|
2010-02-05 02:14:16 +03:00
|
|
|
return jingo.render(request, 'services/monitor.html',
|
2010-02-05 08:13:41 +03:00
|
|
|
{'memcache_results': memcache_results,
|
2010-08-19 23:55:09 +04:00
|
|
|
'libraries_results': libraries_results,
|
2010-06-17 22:20:56 +04:00
|
|
|
'filepath_results': filepath_results,
|
2010-08-05 03:29:28 +04:00
|
|
|
'redis_results': redis_results,
|
2010-08-10 21:40:50 +04:00
|
|
|
'hera_results': hera_results,
|
2010-09-22 01:27:10 +04:00
|
|
|
'mongo_results': mongo_results,
|
2011-01-25 02:29:58 +03:00
|
|
|
'rabbit_results': rabbit_results,
|
2010-02-05 08:13:41 +03:00
|
|
|
'status_summary': status_summary},
|
|
|
|
status=status)
|
2010-02-05 04:51:14 +03:00
|
|
|
|
|
|
|
|
2010-10-16 01:50:46 +04:00
|
|
|
def robots(request):
|
|
|
|
"""Generate a robots.txt"""
|
|
|
|
if not settings.ENGAGE_ROBOTS:
|
2011-01-07 20:30:05 +03:00
|
|
|
template = "User-agent: *\nDisallow: /"
|
2010-10-16 01:50:46 +04:00
|
|
|
else:
|
|
|
|
template = jingo.render(request, 'amo/robots.html',
|
|
|
|
{'apps': amo.APP_USAGE})
|
|
|
|
|
|
|
|
return HttpResponse(template, mimetype="text/plain")
|
|
|
|
|
|
|
|
|
2010-03-15 23:37:20 +03:00
|
|
|
@csrf_exempt
|
|
|
|
def paypal(request):
|
|
|
|
"""
|
|
|
|
Handle PayPal IPN post-back for contribution transactions.
|
|
|
|
|
|
|
|
IPN will retry periodically until it gets success (status=200). Any
|
|
|
|
db errors or replication lag will result in an exception and http
|
|
|
|
status of 500, which is good so PayPal will try again later.
|
2010-05-02 10:46:17 +04:00
|
|
|
|
|
|
|
PayPal IPN variables available at:
|
|
|
|
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content
|
|
|
|
&content_ID=developer/e_howto_html_IPNandPDTVariables
|
2010-03-15 23:37:20 +03:00
|
|
|
"""
|
2010-05-11 01:28:56 +04:00
|
|
|
try:
|
|
|
|
return _paypal(request)
|
|
|
|
except Exception, e:
|
2010-05-11 03:27:13 +04:00
|
|
|
paypal_log.error('%s\n%s' % (e, request))
|
2010-05-11 01:28:56 +04:00
|
|
|
return http.HttpResponseServerError('Unknown error.')
|
2010-05-07 10:47:26 +04:00
|
|
|
|
|
|
|
|
2010-05-11 01:28:56 +04:00
|
|
|
def _paypal(request):
|
2010-05-11 03:27:13 +04:00
|
|
|
|
2010-05-07 10:47:26 +04:00
|
|
|
def _log_error_with_data(msg, request):
|
|
|
|
"""Log a message along with some of the POST info from PayPal."""
|
|
|
|
|
2010-05-11 01:28:56 +04:00
|
|
|
id = random.randint(0, 99999999)
|
2010-05-07 10:47:26 +04:00
|
|
|
msg = "[%s] %s (dumping data)" % (id, msg)
|
|
|
|
|
2010-05-11 03:27:13 +04:00
|
|
|
paypal_log.error(msg)
|
2010-05-07 10:47:26 +04:00
|
|
|
|
|
|
|
logme = {'txn_id': request.POST.get('txn_id'),
|
2010-05-11 02:33:01 +04:00
|
|
|
'txn_type': request.POST.get('txn_type'),
|
2010-05-07 10:47:26 +04:00
|
|
|
'payer_email': request.POST.get('payer_email'),
|
|
|
|
'receiver_email': request.POST.get('receiver_email'),
|
|
|
|
'payment_status': request.POST.get('payment_status'),
|
|
|
|
'payment_type': request.POST.get('payment_type'),
|
|
|
|
'mc_gross': request.POST.get('mc_gross'),
|
|
|
|
'item_number': request.POST.get('item_number'),
|
|
|
|
}
|
|
|
|
|
2010-05-11 03:27:13 +04:00
|
|
|
paypal_log.error("[%s] PayPal Data: %s" % (id, logme))
|
2010-05-07 10:47:26 +04:00
|
|
|
|
2010-04-30 22:14:26 +04:00
|
|
|
if request.method != 'POST':
|
|
|
|
return http.HttpResponseNotAllowed(['POST'])
|
2010-03-15 23:37:20 +03:00
|
|
|
|
2010-11-17 03:45:22 +03:00
|
|
|
if not request.META['CONTENT_LENGTH']:
|
|
|
|
post = {}
|
|
|
|
raw = ""
|
|
|
|
else:
|
|
|
|
# Copying request.POST to avoid this issue:
|
|
|
|
# http://code.djangoproject.com/ticket/12522
|
|
|
|
post = request.POST.copy()
|
|
|
|
raw = request.raw_post_data
|
|
|
|
|
2010-03-15 23:37:20 +03:00
|
|
|
# Check that the request is valid and coming from PayPal.
|
2010-11-17 03:45:22 +03:00
|
|
|
data = '%s&%s' % ('cmd=_notify-validate', raw)
|
2010-05-07 10:47:26 +04:00
|
|
|
paypal_response = urllib2.urlopen(settings.PAYPAL_CGI_URL,
|
2010-05-11 01:28:56 +04:00
|
|
|
data, 20).readline()
|
2010-11-17 03:45:22 +03:00
|
|
|
|
2010-05-07 10:47:26 +04:00
|
|
|
if paypal_response != 'VERIFIED':
|
|
|
|
msg = ("Expecting 'VERIFIED' from PayPal, got '%s'. "
|
|
|
|
"Failing." % paypal_response)
|
|
|
|
_log_error_with_data(msg, request)
|
2010-03-15 23:37:20 +03:00
|
|
|
return http.HttpResponseForbidden('Invalid confirmation')
|
|
|
|
|
2010-11-17 03:45:22 +03:00
|
|
|
if post.get('txn_type', '').startswith('subscr_'):
|
|
|
|
SubscriptionEvent.objects.create(post_data=php.serialize(post))
|
2010-05-02 10:46:17 +04:00
|
|
|
return http.HttpResponse('Success!')
|
2010-04-23 03:17:23 +04:00
|
|
|
|
2010-03-15 23:37:20 +03:00
|
|
|
# We only care about completed transactions.
|
2010-11-17 03:45:22 +03:00
|
|
|
if post.get('payment_status') != 'Completed':
|
2010-03-15 23:37:20 +03:00
|
|
|
return http.HttpResponse('Payment not completed')
|
|
|
|
|
|
|
|
# Make sure transaction has not yet been processed.
|
2010-05-02 10:46:17 +04:00
|
|
|
if (Contribution.objects
|
2010-11-17 03:45:22 +03:00
|
|
|
.filter(transaction_id=post['txn_id']).count()) > 0:
|
2010-03-15 23:37:20 +03:00
|
|
|
return http.HttpResponse('Transaction already processed')
|
|
|
|
|
|
|
|
# Fetch and update the contribution - item_number is the uuid we created.
|
2010-03-25 00:55:09 +03:00
|
|
|
try:
|
2010-11-17 03:45:22 +03:00
|
|
|
c = Contribution.objects.get(uuid=post['item_number'])
|
2010-03-25 00:55:09 +03:00
|
|
|
except Contribution.DoesNotExist:
|
2010-05-02 10:46:17 +04:00
|
|
|
key = "%s%s:%s" % (settings.CACHE_PREFIX, 'contrib',
|
2010-11-17 03:45:22 +03:00
|
|
|
post['item_number'])
|
2010-05-02 10:46:17 +04:00
|
|
|
count = cache.get(key, 0) + 1
|
|
|
|
|
2010-05-11 03:27:13 +04:00
|
|
|
paypal_log.warning('Contribution (uuid=%s) not found for IPN request '
|
2010-11-17 03:45:22 +03:00
|
|
|
'#%s.' % (post['item_number'], count))
|
2010-05-02 10:46:17 +04:00
|
|
|
if count > 10:
|
2010-05-07 10:47:26 +04:00
|
|
|
msg = ("Paypal sent a transaction that we don't know "
|
|
|
|
"about and we're giving up on it.")
|
|
|
|
_log_error_with_data(msg, request)
|
2010-05-02 10:46:17 +04:00
|
|
|
cache.delete(key)
|
|
|
|
return http.HttpResponse('Transaction not found; skipping.')
|
|
|
|
cache.set(key, count, 1209600) # This is 2 weeks.
|
2010-03-25 00:55:09 +03:00
|
|
|
return http.HttpResponseServerError('Contribution not found')
|
|
|
|
|
2010-11-17 03:45:22 +03:00
|
|
|
c.transaction_id = post['txn_id']
|
|
|
|
c.amount = post['mc_gross']
|
2010-03-15 23:37:20 +03:00
|
|
|
c.uuid = None
|
2010-11-17 03:45:22 +03:00
|
|
|
c.post_data = php.serialize(post)
|
2010-03-15 23:37:20 +03:00
|
|
|
c.save()
|
|
|
|
|
|
|
|
# Send thankyou email.
|
|
|
|
try:
|
|
|
|
c.mail_thankyou(request)
|
|
|
|
except ContributionError as e:
|
|
|
|
# A failed thankyou email is not a show stopper, but is good to know.
|
2010-05-11 03:27:13 +04:00
|
|
|
paypal_log.error('Thankyou note email failed with error: %s' % e)
|
2010-03-15 23:37:20 +03:00
|
|
|
|
|
|
|
return http.HttpResponse('Success!')
|
|
|
|
|
|
|
|
|
2010-02-05 04:51:14 +03:00
|
|
|
def handler404(request):
|
|
|
|
return jingo.render(request, 'amo/404.lhtml', status=404)
|
|
|
|
|
|
|
|
|
|
|
|
def handler500(request):
|
2010-02-17 02:55:22 +03:00
|
|
|
return jingo.render(request, 'amo/500.lhtml', status=500)
|
2010-05-15 01:01:58 +04:00
|
|
|
|
|
|
|
|
|
|
|
def loaded(request):
|
|
|
|
return http.HttpResponse('%s' % request.META['wsgi.loaded'],
|
|
|
|
content_type='text/plain')
|
2010-09-23 00:42:49 +04:00
|
|
|
|
2010-10-16 01:50:46 +04:00
|
|
|
|
2010-09-23 00:42:49 +04:00
|
|
|
@csrf_exempt
|
|
|
|
@require_POST
|
|
|
|
def cspreport(request):
|
|
|
|
"""Accept CSP reports and log them."""
|
|
|
|
try:
|
|
|
|
v = json.loads(request.raw_post_data)['csp-report']
|
|
|
|
msg = ("CSP Violation Report: (Request: %s) (Blocked: %s) (Rule: %s)"
|
|
|
|
% (v['request'], v['blocked-uri'], v['violated-directive']))
|
|
|
|
csp_log.warning(msg)
|
|
|
|
except Exception:
|
|
|
|
csp_log.debug("Got a malformed violation report. Ignoring...")
|
|
|
|
return HttpResponseBadRequest()
|
|
|
|
|
|
|
|
return HttpResponse()
|