lambderize the moto lambda

This commit is contained in:
rocky4570fft 2016-10-06 19:52:23 +10:00
parent 79fe9df6cc
commit 6c577091da
4 changed files with 147 additions and 47 deletions

View File

@ -3,7 +3,11 @@ from __future__ import unicode_literals
import base64 import base64
import datetime import datetime
import hashlib import hashlib
import io
import json import json
import StringIO
import sys
import zipfile
import boto.awslambda import boto.awslambda
from moto.core import BaseBackend from moto.core import BaseBackend
@ -34,9 +38,13 @@ class LambdaFunction(object):
self.version = '$LATEST' self.version = '$LATEST'
self.last_modified = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') self.last_modified = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
if 'ZipFile' in self.code: if 'ZipFile' in self.code:
code = base64.b64decode(self.code['ZipFile']) to_unzip_code = base64.b64decode(self.code['ZipFile'])
self.code_size = len(code) zbuffer = io.BytesIO()
self.code_sha_256 = hashlib.sha256(code).hexdigest() zbuffer.write(to_unzip_code)
zip_file = zipfile.ZipFile(zbuffer, 'r', zipfile.ZIP_DEFLATED)
self.code = zip_file.read("".join(zip_file.namelist()))
self.code_size = len(to_unzip_code)
self.code_sha_256 = hashlib.sha256(to_unzip_code).hexdigest()
else: else:
# validate s3 bucket # validate s3 bucket
try: try:
@ -93,15 +101,47 @@ class LambdaFunction(object):
"Configuration": self.get_configuration(), "Configuration": self.get_configuration(),
} }
def _invoke_lambda(self, code, event={}, context={}):
# TO DO: context not yet implemented
try:
codeOut = StringIO.StringIO()
codeErr = StringIO.StringIO()
mycode = "\n".join([self.code, 'print lambda_handler(%s, %s)' % (event, context)])
#print "moto_lambda_debug: ", mycode
sys.stdout = codeOut
sys.stderr = codeErr
exec(mycode, {'event': event, 'context': context})
exec_err = codeErr.getvalue()
exec_out = codeOut.getvalue()
result = "\n".join([exec_out, exec_err])
except Exception as ex:
result = 'Exception %s, %s' % (ex, ex.message)
finally:
codeErr.close()
codeOut.close()
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
return result
def is_json(self, test_str):
try:
response = json.loads(test_str)
except:
response = test_str
return response
def invoke(self, request, headers): def invoke(self, request, headers):
payload = dict() payload = dict()
# Get the invocation type: # Get the invocation type:
invoke_type = request.headers.get("x-amz-invocation-type")
response = self._invoke_lambda(code=self.code, event=self.is_json(request.body))
if request.headers.get("x-amz-invocation-type") == "RequestResponse": if request.headers.get("x-amz-invocation-type") == "RequestResponse":
encoded = base64.b64encode("Some log file output...".encode('utf-8')) encoded = base64.b64encode(response.encode('utf-8'))
payload['result'] = encoded
headers["x-amz-log-result"] = encoded.decode('utf-8') headers["x-amz-log-result"] = encoded.decode('utf-8')
elif request.headers.get("x-amz-invocation-type") == "Event":
payload["result"] = "Good" payload['result'] = 'good' # nothing should be sent back possibly headers etc.
return json.dumps(payload, indent=4) return json.dumps(payload, indent=4)
@ -154,3 +194,7 @@ class LambdaBackend(BaseBackend):
lambda_backends = {} lambda_backends = {}
for region in boto.awslambda.regions(): for region in boto.awslambda.regions():
lambda_backends[region.name] = LambdaBackend() lambda_backends[region.name] = LambdaBackend()
# Handle us forgotten regions, unless Lambda truly only runs out of US and EU?????
for region in ['ap-southeast-2']:
lambda_backends[region] = LambdaBackend()

View File

@ -43,7 +43,7 @@ class LambdaResponse(BaseResponse):
if lambda_backend.has_function(function_name): if lambda_backend.has_function(function_name):
fn = lambda_backend.get_function(function_name) fn = lambda_backend.get_function(function_name)
payload = fn.invoke(request, headers) payload = fn.invoke(request, headers)
return 200, headers, payload return 202, headers, payload
else: else:
return 404, headers, "{}" return 404, headers, "{}"

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import base64
import botocore.client import botocore.client
import boto3 import boto3
import hashlib import hashlib
@ -8,44 +9,57 @@ import zipfile
import sure # noqa import sure # noqa
from freezegun import freeze_time from freezegun import freeze_time
from moto import mock_lambda, mock_s3 from moto import mock_lambda, mock_s3, mock_ec2
def get_test_zip_file(): def _process_lamda(pfunc):
zip_output = io.BytesIO() zip_output = io.BytesIO()
zip_file = zipfile.ZipFile(zip_output, 'w') zip_file = zipfile.ZipFile(zip_output, 'w', zipfile.ZIP_DEFLATED)
zip_file.writestr('lambda_function.py', b'''\ zip_file.writestr('lambda_function.zip', pfunc)
def handler(event, context):
return "hello world"
''')
zip_file.close() zip_file.close()
zip_output.seek(0) zip_output.seek(0)
return zip_output.read() return zip_output.read()
@mock_lambda def get_test_zip_file1():
def test_list_functions(): pfunc = b"""
conn = boto3.client('lambda', 'us-west-2') def lambda_handler(event, context):
return (event, context)
"""
return _process_lamda(pfunc)
result = conn.list_functions()
result['Functions'].should.have.length_of(0) def get_test_zip_file2():
pfunc = b"""
def lambda_handler(event, context):
volume_id = event.get('volume_id')
print 'get volume details for %s' % volume_id
import boto3
ec2 = boto3.resource('ec2', region_name='us-west-2')
vol = ec2.Volume(volume_id)
print 'Volume - %s state=%s, size=%s' % (volume_id, vol.state, vol.size)
"""
return _process_lamda(pfunc)
@mock_lambda @mock_lambda
@mock_s3 @mock_s3
def test_list_functions():
conn = boto3.client('lambda', 'us-west-2')
result = conn.list_functions()
result['Functions'].should.have.length_of(0)
@mock_lambda
@freeze_time('2015-01-01 00:00:00') @freeze_time('2015-01-01 00:00:00')
def test_invoke_function(): def test_invoke_function():
conn = boto3.client('lambda', 'us-west-2') conn = boto3.client('lambda', 'us-west-2')
zip_content = get_test_zip_file()
conn.create_function( conn.create_function(
FunctionName='testFunction', FunctionName='testFunction',
Runtime='python2.7', Runtime='python2.7',
Role='test-iam-role', Role='test-iam-role',
Handler='lambda_function.handler', Handler='lambda_function.handler',
Code={ Code={
'ZipFile': zip_content, 'ZipFile': get_test_zip_file1(),
}, },
Description='test lambda function', Description='test lambda function',
Timeout=3, Timeout=3,
@ -53,8 +67,8 @@ def test_invoke_function():
Publish=True, Publish=True,
) )
success_result = conn.invoke(FunctionName='testFunction', InvocationType='Event', Payload='{}') success_result = conn.invoke(FunctionName='testFunction', InvocationType='Event', Payload="Mostly Harmless")
success_result["StatusCode"].should.equal(200) success_result["StatusCode"].should.equal(202)
conn.invoke.when.called_with( conn.invoke.when.called_with(
FunctionName='notAFunction', FunctionName='notAFunction',
@ -62,11 +76,42 @@ def test_invoke_function():
Payload='{}' Payload='{}'
).should.throw(botocore.client.ClientError) ).should.throw(botocore.client.ClientError)
success_result = conn.invoke(FunctionName='testFunction', InvocationType='RequestResponse', Payload='{}') success_result = conn.invoke(FunctionName='testFunction', InvocationType='RequestResponse', Payload='{"msg": "So long and thanks for all the fish"}')
success_result["StatusCode"].should.equal(200) success_result["StatusCode"].should.equal(202)
import base64 import base64
base64.b64decode(success_result["LogResult"]).decode('utf-8').should.equal("Some log file output...") base64.b64decode(success_result["LogResult"]).decode('utf-8').should.equal("({u'msg': u'So long and thanks for all the fish'}, {})\n\n")
@mock_ec2
@mock_lambda
@freeze_time('2015-01-01 00:00:00')
def test_invoke_function_get_ec2_volume():
conn = boto3.resource("ec2", "us-west-2")
vol = conn.create_volume(Size=99, AvailabilityZone='us-west-2')
vol = conn.Volume(vol.id)
conn = boto3.client('lambda', 'us-west-2')
conn.create_function(
FunctionName='testFunction',
Runtime='python2.7',
Role='test-iam-role',
Handler='lambda_function.handler',
Code={
'ZipFile': get_test_zip_file2(),
},
Description='test lambda function',
Timeout=3,
MemorySize=128,
Publish=True,
)
import json
success_result = conn.invoke(FunctionName='testFunction', InvocationType='RequestResponse', Payload=json.dumps({'volume_id': vol.id}))
success_result["StatusCode"].should.equal(202)
import base64
msg = 'get volume details for %s\nVolume - %s state=%s, size=%s\nNone\n\n' % (vol.id, vol.id, vol.state, vol.size)
base64.b64decode(success_result["LogResult"]).decode('utf-8').should.equal(msg)
@mock_lambda @mock_lambda
@ -101,7 +146,7 @@ def test_create_function_from_aws_bucket():
s3_conn = boto3.client('s3', 'us-west-2') s3_conn = boto3.client('s3', 'us-west-2')
s3_conn.create_bucket(Bucket='test-bucket') s3_conn.create_bucket(Bucket='test-bucket')
zip_content = get_test_zip_file() zip_content = get_test_zip_file2()
s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content) s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content)
conn = boto3.client('lambda', 'us-west-2') conn = boto3.client('lambda', 'us-west-2')
@ -151,8 +196,7 @@ def test_create_function_from_aws_bucket():
@freeze_time('2015-01-01 00:00:00') @freeze_time('2015-01-01 00:00:00')
def test_create_function_from_zipfile(): def test_create_function_from_zipfile():
conn = boto3.client('lambda', 'us-west-2') conn = boto3.client('lambda', 'us-west-2')
zip_content = get_test_zip_file1()
zip_content = get_test_zip_file()
result = conn.create_function( result = conn.create_function(
FunctionName='testFunction', FunctionName='testFunction',
Runtime='python2.7', Runtime='python2.7',
@ -196,7 +240,7 @@ def test_get_function():
s3_conn = boto3.client('s3', 'us-west-2') s3_conn = boto3.client('s3', 'us-west-2')
s3_conn.create_bucket(Bucket='test-bucket') s3_conn.create_bucket(Bucket='test-bucket')
zip_content = get_test_zip_file() zip_content = get_test_zip_file1()
s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content) s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content)
conn = boto3.client('lambda', 'us-west-2') conn = boto3.client('lambda', 'us-west-2')
@ -245,14 +289,13 @@ def test_get_function():
}) })
@mock_lambda @mock_lambda
@mock_s3 @mock_s3
def test_delete_function(): def test_delete_function():
s3_conn = boto3.client('s3', 'us-west-2') s3_conn = boto3.client('s3', 'us-west-2')
s3_conn.create_bucket(Bucket='test-bucket') s3_conn.create_bucket(Bucket='test-bucket')
zip_content = get_test_zip_file() zip_content = get_test_zip_file2()
s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content) s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content)
conn = boto3.client('lambda', 'us-west-2') conn = boto3.client('lambda', 'us-west-2')
@ -289,7 +332,7 @@ def test_list_create_list_get_delete_list():
s3_conn = boto3.client('s3', 'us-west-2') s3_conn = boto3.client('s3', 'us-west-2')
s3_conn.create_bucket(Bucket='test-bucket') s3_conn.create_bucket(Bucket='test-bucket')
zip_content = get_test_zip_file() zip_content = get_test_zip_file2()
s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content) s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content)
conn = boto3.client('lambda', 'us-west-2') conn = boto3.client('lambda', 'us-west-2')

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json import json
import base64
import boto import boto
import boto.cloudformation import boto.cloudformation
import boto.datapipeline import boto.datapipeline
@ -1724,10 +1725,29 @@ def test_datapipeline():
stack_resources.should.have.length_of(1) stack_resources.should.have.length_of(1)
stack_resources[0].physical_resource_id.should.equal(data_pipelines['pipelineIdList'][0]['id']) stack_resources[0].physical_resource_id.should.equal(data_pipelines['pipelineIdList'][0]['id'])
def _process_lamda(pfunc):
import io
import zipfile
zip_output = io.BytesIO()
zip_file = zipfile.ZipFile(zip_output, 'w', zipfile.ZIP_DEFLATED)
zip_file.writestr('lambda_function.zip', pfunc)
zip_file.close()
zip_output.seek(0)
return zip_output.read()
def get_test_zip_file1():
pfunc = b"""
def lambda_handler(event, context):
return (event, context)
"""
return _process_lamda(pfunc)
@mock_cloudformation @mock_cloudformation
@mock_lambda @mock_lambda
def test_lambda_function(): def test_lambda_function():
# switch this to python as backend lambda only supports python execution.
conn = boto3.client('lambda', 'us-east-1') conn = boto3.client('lambda', 'us-east-1')
template = { template = {
"AWSTemplateFormatVersion": "2010-09-09", "AWSTemplateFormatVersion": "2010-09-09",
@ -1736,22 +1756,15 @@ def test_lambda_function():
"Type": "AWS::Lambda::Function", "Type": "AWS::Lambda::Function",
"Properties": { "Properties": {
"Code": { "Code": {
"ZipFile": {"Fn::Join": [ "ZipFile": base64.b64encode(get_test_zip_file1())
"\n",
"""
exports.handler = function(event, context) {
context.succeed();
}
""".splitlines()
]}
}, },
"Handler": "index.handler", "Handler": "lambda_function.handler",
"Description": "Test function", "Description": "Test function",
"MemorySize": 128, "MemorySize": 128,
"Role": "test-role", "Role": "test-role",
"Runtime": "nodejs", "Runtime": "python2.7"
} }
}, }
} }
} }
@ -1765,10 +1778,10 @@ def test_lambda_function():
result = conn.list_functions() result = conn.list_functions()
result['Functions'].should.have.length_of(1) result['Functions'].should.have.length_of(1)
result['Functions'][0]['Description'].should.equal('Test function') result['Functions'][0]['Description'].should.equal('Test function')
result['Functions'][0]['Handler'].should.equal('index.handler') result['Functions'][0]['Handler'].should.equal('lambda_function.handler')
result['Functions'][0]['MemorySize'].should.equal(128) result['Functions'][0]['MemorySize'].should.equal(128)
result['Functions'][0]['Role'].should.equal('test-role') result['Functions'][0]['Role'].should.equal('test-role')
result['Functions'][0]['Runtime'].should.equal('nodejs') result['Functions'][0]['Runtime'].should.equal('python2.7')
@mock_cloudformation @mock_cloudformation