Some code simplifications & logging for Zencoder notifications.

This commit is contained in:
Sybren A. Stüvel 2016-03-25 17:21:18 +01:00
parent d7ee2121d9
commit fd5bcaec52
2 changed files with 102 additions and 45 deletions

View File

@ -1,58 +1,65 @@
import logging
from bson import ObjectId
from eve.methods.put import put_internal
from flask import Blueprint
from flask import abort
from flask import request
from application import app
from application import utils
encoding = Blueprint('encoding', __name__)
log = logging.getLogger(__name__)
@encoding.route('/zencoder/notifications', methods=['POST'])
def zencoder_notifications():
if app.config['ENCODING_BACKEND'] == 'zencoder':
if not app.config['DEBUG']:
# If we are in production, look for the Zencoder header secret
try:
notification_secret_request = request.headers[
'X-Zencoder-Notification-Secret']
except KeyError:
return abort(401)
# If the header is found, check it agains the one in the config
notification_secret = app.config['ZENCODER_NOTIFICATIONS_SECRET']
if notification_secret_request != notification_secret:
return abort(401)
# Cast request data into a dict
data = request.get_json()
files_collection = app.data.driver.db['files']
# Find the file object based on processing backend and job_id
lookup = {'processing.backend': 'zencoder', 'processing.job_id': str(
data['job']['id'])}
f = files_collection.find_one(lookup)
if f:
file_id = f['_id']
# Remove internal keys (so that we can run put internal)
internal_fields = ['_id', '_etag', '_updated', '_created', '_status']
for field in internal_fields:
f.pop(field, None)
# Update processing status
f['processing']['status'] = data['job']['state']
# For every variation encoded, try to update the file object
for output in data['outputs']:
format = output['format']
# Change the zencoder 'mpeg4' format to 'mp4' used internally
format = 'mp4' if format == 'mpeg4' else format
# Find a variation matching format and resolution
variation = next((v for v in f['variations'] if v['format'] == format \
and v['width'] == output['width']), None)
# If found, update with delivered file size
# TODO: calculate md5 on the storage
if variation:
variation['length'] = output['file_size_in_bytes']
r = put_internal('files', f, **{'_id': ObjectId(file_id)})
return ''
else:
return abort(404)
else:
if app.config['ENCODING_BACKEND'] != 'zencoder':
log.warning('Received notification from Zencoder but app not configured for Zencoder.')
return abort(403)
if not app.config['DEBUG']:
# If we are in production, look for the Zencoder header secret
try:
notification_secret_request = request.headers[
'X-Zencoder-Notification-Secret']
except KeyError:
log.warning('Received Zencoder notification without secret.')
return abort(401)
# If the header is found, check it agains the one in the config
notification_secret = app.config['ZENCODER_NOTIFICATIONS_SECRET']
if notification_secret_request != notification_secret:
log.warning('Received Zencoder notification with incorrect secret.')
return abort(401)
# Cast request data into a dict
data = request.get_json()
files_collection = app.data.driver.db['files']
# Find the file object based on processing backend and job_id
lookup = {'processing.backend': 'zencoder', 'processing.job_id': str(data['job']['id'])}
f = files_collection.find_one(lookup)
if not f:
log.warning('Unknown Zencoder job id %r', data['job']['id'])
return abort(404)
file_id = f['_id']
# Remove internal keys (so that we can run put internal)
f = utils.remove_private_keys(f)
# Update processing status
f['processing']['status'] = data['job']['state']
# For every variation encoded, try to update the file object
for output in data['outputs']:
format = output['format']
# Change the zencoder 'mpeg4' format to 'mp4' used internally
format = 'mp4' if format == 'mpeg4' else format
# Find a variation matching format and resolution
variation = next((v for v in f['variations'] if v['format'] == format \
and v['width'] == output['width']), None)
# If found, update with delivered file size
# TODO: calculate md5 on the storage
if variation:
variation['length'] = output['file_size_in_bytes']
put_internal('files', f, _id=ObjectId(file_id))
return ''

50
tests/test_encoding.py Normal file
View File

@ -0,0 +1,50 @@
"""Test cases for the zencoder notifications."""
import json
from common_test_class import AbstractPillarTest
class ZencoderNotificationTest(AbstractPillarTest):
def test_missing_secret(self):
with self.app.test_request_context():
resp = self.client.post('/encoding/zencoder/notifications')
self.assertEqual(401, resp.status_code)
def test_wrong_secret(self):
with self.app.test_request_context():
resp = self.client.post('/encoding/zencoder/notifications',
headers={'X-Zencoder-Notification-Secret': 'koro'})
self.assertEqual(401, resp.status_code)
def test_good_secret_missing_file(self):
with self.app.test_request_context():
secret = self.app.config['ZENCODER_NOTIFICATIONS_SECRET']
resp = self.client.post('/encoding/zencoder/notifications',
data=json.dumps({'job': {'id': 'koro-007'}}),
headers={'X-Zencoder-Notification-Secret': secret,
'Content-Type': 'application/json'})
self.assertEqual(404, resp.status_code)
def test_good_secret_existing_file(self):
self.ensure_file_exists(file_overrides={
'processing': {'backend': 'zencoder',
'job_id': 'koro-007',
'status': 'processing'}
})
with self.app.test_request_context():
secret = self.app.config['ZENCODER_NOTIFICATIONS_SECRET']
resp = self.client.post('/encoding/zencoder/notifications',
data=json.dumps({'job': {'id': 'koro-007',
'state': 'done'},
'outputs': [{
'format': 'jpg',
'width': 2048,
'file_size_in_bytes': 15,
}]}),
headers={'X-Zencoder-Notification-Secret': secret,
'Content-Type': 'application/json'})
# TODO: check that the file in MongoDB is actually updated properly.
self.assertEqual(200, resp.status_code)