/** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Aws::Auth; using namespace Aws::Http; using namespace Aws::Client; using namespace Aws::Lambda; using namespace Aws::Lambda::Model; using namespace Aws::Kinesis; using namespace Aws::Kinesis::Model; using namespace Aws::IAM; using namespace Aws::IAM::Model; using namespace Aws::CognitoIdentity; namespace { static const char BASE_KINESIS_STREAM_NAME[] = "AWSNativeSDKIntegrationTest"; static const char BASE_SIMPLE_FUNCTION[] = "TestSimple"; static const char BASE_UNHANDLED_ERROR_FUNCTION[] = "TestUnhandledError"; static const char SIMPLE_FUNCTION_CODE[] = RESOURCES_DIR "/succeed.zip"; static const char UNHANDLED_ERROR_FUNCTION_CODE[] = RESOURCES_DIR "/unhandled.zip"; static const char ALLOCATION_TAG[] = "FunctionTest"; static const char BASE_IAM_ROLE_NAME[] = "AWSNativeSDKLambdaIntegrationTestRole"; class FunctionTest : public ::testing::Test { public: static std::shared_ptr m_client; static std::shared_ptr m_kinesis_client; static std::shared_ptr m_limiter; static std::shared_ptr m_role; static std::shared_ptr m_iamClient; static std::shared_ptr m_accessManagementClient; static std::map functionArnMapping; Aws::String m_UUID; protected: Aws::String BuildResourceName(const char* baseName) { return Aws::Testing::GetAwsResourcePrefix() + baseName + m_UUID; } static Aws::String MakeFilePath(const Aws::String& localFile) { #ifdef __ANDROID__ return Aws::Platform::GetCacheDirectory() + localFile; #else return localFile; #endif } void SetUp() { m_UUID = Aws::Utils::UUID::RandomUUID(); CreateFunction(BuildResourceName(BASE_SIMPLE_FUNCTION), SIMPLE_FUNCTION_CODE); } void TearDown() { DeleteFunction(BuildResourceName(BASE_SIMPLE_FUNCTION)); } static void SetUpTestCase() { m_limiter = Aws::MakeShared>(ALLOCATION_TAG, 200000); // Create a client ClientConfiguration config; config.region = Aws::Region::US_EAST_1; config.scheme = Scheme::HTTPS; config.connectTimeoutMs = 30000; config.requestTimeoutMs = 30000; config.readRateLimiter = m_limiter; config.writeRateLimiter = m_limiter; //to test proxy functionality, uncomment the next two lines. m_client = Aws::MakeShared(ALLOCATION_TAG, config); m_kinesis_client = Aws::MakeShared(ALLOCATION_TAG, config); //Create our IAM Role, so that the Lambda tests have the right policies. m_role = Aws::MakeShared(ALLOCATION_TAG); ClientConfiguration clientConfig; clientConfig.region = Aws::Region::US_EAST_1; m_iamClient = Aws::MakeShared(ALLOCATION_TAG, clientConfig); auto cognitoClient = Aws::MakeShared(ALLOCATION_TAG); m_accessManagementClient = Aws::MakeShared(ALLOCATION_TAG, m_iamClient, cognitoClient); m_accessManagementClient->GetRole(BASE_IAM_ROLE_NAME, *m_role); } static void TearDownTestCase() { // Return the memory claimed for static variables to memory manager before shutting down memory manager. // Otherwise there will be double free crash. functionArnMapping.clear(); m_limiter = nullptr; m_client = nullptr; m_kinesis_client = nullptr; m_role = nullptr; m_iamClient = nullptr; m_accessManagementClient = nullptr; } enum class ResourceStatusType { READY, NOT_FOUND }; static void WaitForFunctionStatus(Aws::String functionName, ResourceStatusType status) { bool done = false; while(!done) { ListFunctionsRequest listFunctionsRequest; ListFunctionsOutcome listFunctionsOutcome = m_client->ListFunctions(listFunctionsRequest); EXPECT_TRUE(listFunctionsOutcome.IsSuccess()); auto functions = listFunctionsOutcome.GetResult().GetFunctions(); auto iter = std::find_if(functions.cbegin(), functions.cend(), [=](const FunctionConfiguration& function){ return function.GetFunctionName() == functionName; }); switch(status) { case ResourceStatusType::READY: if(iter != functions.cend()) { return; } break; case ResourceStatusType::NOT_FOUND: if(iter == functions.cend()) { return; } break; } std::this_thread::sleep_for(std::chrono::seconds(1)); } } static void DeleteFunction(Aws::String functionName) { DeleteFunctionRequest deleteFunctionRequest; deleteFunctionRequest.SetFunctionName(functionName); bool done = false; while(!done) { DeleteFunctionOutcome deleteFunctionOutcome = m_client->DeleteFunction(deleteFunctionRequest); if (deleteFunctionOutcome.IsSuccess()) { done = true; } else { //Look at the specific error, from here: http://docs.aws.amazon.com/lambda/latest/dg/API_DeleteFunction.html //Some we will want to handle by aborting, others we can continue to spin on. auto errCode = deleteFunctionOutcome.GetError().GetErrorType(); switch (errCode) { case LambdaErrors::RESOURCE_NOT_FOUND: //The function was already deleted or not present. done = true; break; case LambdaErrors::TOO_MANY_REQUESTS: //This is OK; Keep spinning. break; default: //Something bad happened, and we can't commit to this being successful. FAIL(); break; } } } } static void CreateFunction(Aws::String functionName,Aws::String zipLocation) { //Get the ARN off the IAM role. We'll need this for creating the functions. auto & roleARN = m_role->GetArn(); //Now attempt to create the function. CreateFunctionRequest createFunctionRequest; createFunctionRequest.SetHandler("test.handler"); createFunctionRequest.SetFunctionName(functionName); createFunctionRequest.SetRole(roleARN); FunctionCode functionCode; auto filePath = MakeFilePath(zipLocation); std::ifstream fc(filePath.c_str(), std::ios_base::in | std::ios_base::binary); Aws::StringStream buffer; buffer << fc.rdbuf(); functionCode.SetZipFile(Aws::Utils::ByteBuffer((unsigned char*)buffer.str().c_str(), buffer.str().length())); createFunctionRequest.SetCode(functionCode); createFunctionRequest.SetRuntime(Aws::Lambda::Model::Runtime::nodejs12_x); CreateFunctionOutcome createFunctionOutcome = m_client->CreateFunction(createFunctionRequest); ASSERT_TRUE(createFunctionOutcome.IsSuccess()); ASSERT_EQ(functionName,createFunctionOutcome.GetResult().GetFunctionName()); ASSERT_EQ("test.handler",createFunctionOutcome.GetResult().GetHandler()); ASSERT_EQ(roleARN,createFunctionOutcome.GetResult().GetRole()); ASSERT_EQ(Aws::Lambda::Model::Runtime::nodejs12_x, createFunctionOutcome.GetResult().GetRuntime()); functionArnMapping[functionName] = createFunctionOutcome.GetResult().GetFunctionArn(); WaitForFunctionStatus(functionName, ResourceStatusType::READY); } }; std::shared_ptr FunctionTest::m_client(nullptr); std::shared_ptr FunctionTest::m_kinesis_client(nullptr); std::shared_ptr FunctionTest::m_limiter(nullptr); std::shared_ptr FunctionTest::m_role(nullptr); std::shared_ptr FunctionTest::m_iamClient(nullptr); std::shared_ptr< Aws::AccessManagement::AccessManagementClient > FunctionTest::m_accessManagementClient(nullptr); std::map FunctionTest::functionArnMapping; TEST_F(FunctionTest, TestListFunction) { ListFunctionsRequest listFunctionsRequest; ListFunctionsOutcome listFunctionsOutcome = m_client->ListFunctions(listFunctionsRequest); EXPECT_TRUE(listFunctionsOutcome.IsSuccess()); //We are just going to make sure the simple function above is in the list Aws::Vector filteredFunctionConfigurations; auto functions = listFunctionsOutcome.GetResult().GetFunctions(); Aws::String simpleFunctionName = BuildResourceName(BASE_SIMPLE_FUNCTION); std::copy_if(functions.cbegin(), functions.cend(), std::back_inserter(filteredFunctionConfigurations), [=](const FunctionConfiguration& function) { return function.GetFunctionName().find(simpleFunctionName) == 0;}); ASSERT_EQ(1uL, filteredFunctionConfigurations.size()); EXPECT_EQ(simpleFunctionName, filteredFunctionConfigurations[0].GetFunctionName()); } TEST_F(FunctionTest, TestGetFunction) { Aws::String simpleFunctionName = BuildResourceName(BASE_SIMPLE_FUNCTION); GetFunctionRequest getFunctionRequest; getFunctionRequest.SetFunctionName(simpleFunctionName); GetFunctionOutcome getFunctionOutcome = m_client->GetFunction(getFunctionRequest); EXPECT_TRUE(getFunctionOutcome.IsSuccess()); GetFunctionResult getFunctionResult = getFunctionOutcome.GetResult(); EXPECT_EQ(Runtime::nodejs12_x, getFunctionResult.GetConfiguration().GetRuntime()); EXPECT_EQ("test.handler",getFunctionResult.GetConfiguration().GetHandler()); EXPECT_EQ(simpleFunctionName,getFunctionResult.GetConfiguration().GetFunctionName()); //Just see that is looks like an aws url EXPECT_NE(std::string::npos, getFunctionResult.GetCode().GetLocation().find("amazonaws")); EXPECT_EQ("S3", getFunctionResult.GetCode().GetRepositoryType()); } TEST_F(FunctionTest, TestGetFunctionConfiguration) { Aws::String simpleFunctionName = BuildResourceName(BASE_SIMPLE_FUNCTION); GetFunctionConfigurationRequest getFunctionConfigurationRequest; getFunctionConfigurationRequest.SetFunctionName(simpleFunctionName); GetFunctionConfigurationOutcome getFunctionConfigurationOutcome = m_client->GetFunctionConfiguration(getFunctionConfigurationRequest); EXPECT_TRUE(getFunctionConfigurationOutcome.IsSuccess()); GetFunctionConfigurationResult getFunctionConfigurationResult = getFunctionConfigurationOutcome.GetResult(); EXPECT_EQ(Runtime::nodejs12_x, getFunctionConfigurationResult.GetRuntime()); EXPECT_EQ("test.handler",getFunctionConfigurationResult.GetHandler()); EXPECT_EQ(simpleFunctionName,getFunctionConfigurationResult.GetFunctionName()); } TEST_F(FunctionTest, TestInvokeEvent) { Aws::String simpleFunctionName = BuildResourceName(BASE_SIMPLE_FUNCTION); InvokeRequest invokeRequest; invokeRequest.SetFunctionName(simpleFunctionName); invokeRequest.SetInvocationType(InvocationType::Event); InvokeOutcome invokeOutcome = m_client->Invoke(invokeRequest); EXPECT_TRUE(invokeOutcome.IsSuccess()); EXPECT_EQ(202,invokeOutcome.GetResult().GetStatusCode()); } TEST_F(FunctionTest, TestInvokeEventFromARN) { Aws::String simpleFunctionName = BuildResourceName(BASE_SIMPLE_FUNCTION); InvokeRequest invokeRequest; invokeRequest.SetFunctionName(functionArnMapping[simpleFunctionName]); invokeRequest.SetInvocationType(InvocationType::Event); InvokeOutcome invokeOutcome = m_client->Invoke(invokeRequest); EXPECT_TRUE(invokeOutcome.IsSuccess()); EXPECT_EQ(202,invokeOutcome.GetResult().GetStatusCode()); } TEST_F(FunctionTest, TestInvokeDryRun) { InvokeRequest invokeRequest; invokeRequest.SetFunctionName(BuildResourceName(BASE_SIMPLE_FUNCTION)); invokeRequest.SetInvocationType(InvocationType::DryRun); InvokeOutcome invokeOutcome = m_client->Invoke(invokeRequest); EXPECT_TRUE(invokeOutcome.IsSuccess()); EXPECT_EQ(204,invokeOutcome.GetResult().GetStatusCode()); } TEST_F(FunctionTest, TestInvokeSync) { InvokeRequest invokeRequest; invokeRequest.SetFunctionName(BuildResourceName(BASE_SIMPLE_FUNCTION)); invokeRequest.SetInvocationType(InvocationType::RequestResponse); invokeRequest.SetContentType("application/javascript"); invokeRequest.SetLogType(LogType::Tail); std::shared_ptr payload = Aws::MakeShared("FunctionTest"); Aws::Utils::Json::JsonValue jsonPayload; jsonPayload.WithString("input", "ThePayload"); *payload << jsonPayload.View().WriteReadable(); invokeRequest.SetBody(payload); InvokeOutcome invokeOutcome = m_client->Invoke(invokeRequest); EXPECT_TRUE(invokeOutcome.IsSuccess()); auto &result = invokeOutcome.GetResult(); EXPECT_EQ(200,result.GetStatusCode()); //Decode the log result header and ensure our test line is in it Aws::StringStream ss; auto byteLogResult = Aws::Utils::HashingUtils::Base64Decode(result.GetLogResult()); for(unsigned i = 0; i < byteLogResult.GetLength(); i++) { ss << byteLogResult.GetItem(i); } //The log has lots of time stamps and other non-predictable information //We are just going to look for the presence of the log line printed by our function EXPECT_NE(std::string::npos,ss.str().find("TESTTEST")); ss.str(""); //Our 'happy case' script simply echos the input to the output, so we should get the same thing here that we //sent above auto jsonResponse = Aws::Utils::Json::JsonValue(result.GetPayload()); EXPECT_EQ("ThePayload", jsonResponse.View().GetString("input")); } TEST_F(FunctionTest, TestInvokeSyncUnhandledFunctionError) { CreateFunction(BuildResourceName(BASE_UNHANDLED_ERROR_FUNCTION), UNHANDLED_ERROR_FUNCTION_CODE); InvokeRequest invokeRequest; invokeRequest.SetFunctionName(BuildResourceName(BASE_UNHANDLED_ERROR_FUNCTION)); invokeRequest.SetInvocationType(InvocationType::RequestResponse); invokeRequest.SetContentType("application/javascript"); invokeRequest.SetLogType(LogType::Tail); std::shared_ptr payload = Aws::MakeShared("FunctionTest"); Aws::Utils::Json::JsonValue jsonPayload; jsonPayload.WithString("input", "ThePayload"); *payload << jsonPayload.View().WriteReadable(); invokeRequest.SetBody(payload); InvokeOutcome invokeOutcome = m_client->Invoke(invokeRequest); EXPECT_TRUE(invokeOutcome.IsSuccess()); const auto& result = invokeOutcome.GetResult(); EXPECT_EQ(200,result.GetStatusCode()); //This is the same as the last test, but we should have a FunctionError EXPECT_EQ("Unhandled", result.GetFunctionError()); DeleteFunction(BuildResourceName(BASE_UNHANDLED_ERROR_FUNCTION)); } TEST_F(FunctionTest, TestPermissions) { Aws::String simpleFunctionName = BuildResourceName(BASE_SIMPLE_FUNCTION); AddPermissionRequest addPermissionRequest; addPermissionRequest.SetFunctionName(simpleFunctionName); addPermissionRequest.SetAction("lambda:Invoke"); addPermissionRequest.SetPrincipal("s3.amazonaws.com"); addPermissionRequest.SetStatementId("12345"); auto outcome = m_client->AddPermission(addPermissionRequest); ASSERT_TRUE(outcome.IsSuccess()); AddPermissionResult result = outcome.GetResult(); auto statement = Aws::Utils::Json::JsonValue(result.GetStatement()); EXPECT_EQ("12345",statement.View().GetString("Sid")); EXPECT_NE(std::string::npos, statement.View().GetString("Resource").find(simpleFunctionName)); Aws::Lambda::Model::GetPolicyRequest getPolicyRequest; getPolicyRequest.SetFunctionName(simpleFunctionName); auto getPolicyOutcome = m_client->GetPolicy(getPolicyRequest); //If this fails, stop. ASSERT_TRUE(getPolicyOutcome.IsSuccess()); Aws::Lambda::Model::GetPolicyResult getPolicyResult = getPolicyOutcome.GetResult(); auto getPolicyStatement = Aws::Utils::Json::JsonValue(getPolicyResult.GetPolicy()); EXPECT_EQ("12345", getPolicyStatement.View().GetArray("Statement").GetItem(0).GetString("Sid")); EXPECT_EQ("lambda:Invoke", getPolicyStatement.View().GetArray("Statement").GetItem(0).GetString("Action")); RemovePermissionRequest removePermissionRequest; removePermissionRequest.SetFunctionName(simpleFunctionName); removePermissionRequest.SetStatementId("12345"); auto removePermissionOutcome = m_client->RemovePermission(removePermissionRequest); EXPECT_TRUE(removePermissionOutcome.IsSuccess()); auto getRemovedPolicyOutcome = m_client->GetPolicy(getPolicyRequest); //lambda used to return an empty string here, now it doesn't but we aren't entirely sure if this is the expected behavior //or a regression. For now just handle both gracefully. if (!getRemovedPolicyOutcome.IsSuccess()) { EXPECT_EQ(LambdaErrors::RESOURCE_NOT_FOUND, getRemovedPolicyOutcome.GetError().GetErrorType()); EXPECT_STREQ("User", getRemovedPolicyOutcome.GetError().GetType().c_str()); } //Now we should get an empty policy a GetPolicy because we just removed it else { Aws::Lambda::Model::GetPolicyResult getRemovedPolicyResult = getRemovedPolicyOutcome.GetResult(); auto getNewPolicy = Aws::Utils::Json::JsonValue(getRemovedPolicyResult.GetPolicy()); EXPECT_EQ(0uL, getNewPolicy.View().GetArray("Statement").GetLength()); } } TEST_F(FunctionTest, TestEventSources) { Aws::String simpleFunctionName = BuildResourceName(BASE_SIMPLE_FUNCTION); //Attempt to get the ARN of the stream we created during the init. DescribeStreamRequest describeStreamRequest; describeStreamRequest.SetStreamName(BASE_KINESIS_STREAM_NAME); Aws::String streamARN; bool done = false; while(!done) { DescribeStreamOutcome describeStreamOutcome = m_kinesis_client->DescribeStream(describeStreamRequest); if (describeStreamOutcome.IsSuccess()) { done = true; DescribeStreamResult result = describeStreamOutcome.GetResult(); const StreamDescription & description = result.GetStreamDescription(); streamARN = description.GetStreamARN(); } else { #if ENABLE_CURL_CLIENT ASSERT_FALSE(describeStreamOutcome.GetError().GetRemoteHostIpAddress().empty()); #endif ASSERT_FALSE(describeStreamOutcome.GetError().GetRequestId().empty()); auto errCode = describeStreamOutcome.GetError().GetErrorType(); ASSERT_TRUE(KinesisErrors::LIMIT_EXCEEDED == errCode); //If the limit was exceeded, wait and try again. std::this_thread::sleep_for(std::chrono::seconds(1)); } } CreateEventSourceMappingRequest createEventSourceMappingRequest; createEventSourceMappingRequest.SetFunctionName(simpleFunctionName); createEventSourceMappingRequest.SetEnabled(false); //Not a real stream createEventSourceMappingRequest.SetEventSourceArn(streamARN); createEventSourceMappingRequest.SetStartingPosition(EventSourcePosition::LATEST); createEventSourceMappingRequest.SetBatchSize(99); CreateEventSourceMappingOutcome createOutcome = m_client->CreateEventSourceMapping(createEventSourceMappingRequest); Aws::String createdMappingUUID; if(createOutcome.IsSuccess()) { CreateEventSourceMappingResult createResult = createOutcome.GetResult(); EXPECT_EQ(99,createResult.GetBatchSize()); EXPECT_NE(std::string::npos,createResult.GetFunctionArn().find(simpleFunctionName)); EXPECT_EQ("Creating",createResult.GetState()); EXPECT_EQ(streamARN,createResult.GetEventSourceArn()); createdMappingUUID = createResult.GetUUID(); } else { //This means the mapping still exists from a previous failed test. We'll skip the CreatedResult test, but continue with the existing mapping. //This should only happen during dev after test failures #if ENABLE_CURL_CLIENT ASSERT_FALSE(createOutcome.GetError().GetRemoteHostIpAddress().empty()); #endif ASSERT_FALSE(createOutcome.GetError().GetRequestId().empty()); auto message = createOutcome.GetError().GetMessage(); createdMappingUUID = message.substr(message.length()-36); } GetEventSourceMappingRequest getRequest; getRequest.SetUUID(createdMappingUUID); GetEventSourceMappingOutcome getEventSourceMappingOutcome = m_client->GetEventSourceMapping(getRequest); EXPECT_TRUE(getEventSourceMappingOutcome.IsSuccess()); GetEventSourceMappingResult getResult = getEventSourceMappingOutcome.GetResult(); EXPECT_EQ(99, getResult.GetBatchSize()); EXPECT_NE(std::string::npos, getResult.GetFunctionArn().find(simpleFunctionName)); //We're not actually changing anything, we're just testing a successful call and result UpdateEventSourceMappingRequest updateRequest; updateRequest.SetUUID(createdMappingUUID); updateRequest.SetEnabled(false); UpdateEventSourceMappingOutcome updateOutcome = m_client->UpdateEventSourceMapping(updateRequest); EXPECT_TRUE(updateOutcome.IsSuccess()); UpdateEventSourceMappingResult updateResult = updateOutcome.GetResult(); EXPECT_NE("Disabled",updateResult.GetState());//This may be 'Updating' or 'Disabling' DeleteEventSourceMappingRequest deleteRequest; deleteRequest.SetUUID(createdMappingUUID); DeleteEventSourceMappingOutcome deleteOutcome = m_client->DeleteEventSourceMapping(deleteRequest); EXPECT_TRUE(deleteOutcome.IsSuccess()); } } // anonymous namespace