* Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <filesystem>
#include <stdexcept>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "Constant.h"
#include "FunctionError.h"
#include "common/mock_libruntime.h"
#include "api/cpp/src/utils/utils.h"
#include "src/libruntime/libruntime_manager.h"
#include "src/utility/id_generator.h"
#define private public
#include "api/cpp/src/faas/context_impl.h"
#include "api/cpp/src/faas/faas_executor.h"
#include "api/cpp/src/faas/register_runtime_handler.h"
namespace YR {
namespace test {
using namespace testing;
using namespace YR::internal;
using namespace Function;
using namespace YR::utility;
namespace fs = std::filesystem;
namespace {
YR::Libruntime::ErrorInfo MockAllocReturnObject(std::shared_ptr<YR::Libruntime::DataObject> &returnObj, size_t metaSize,
size_t dataSize, const std::vector<std::string> &, uint64_t &)
{
returnObj = std::make_shared<YR::Libruntime::DataObject>(metaSize, dataSize);
return {};
}
}
class FaasExecutorTest : public testing::Test {
public:
FaasExecutorTest(){};
~FaasExecutorTest(){};
void SetUp() override
{
auto lc = std::make_shared<YR::Libruntime::LibruntimeConfig>();
lc->jobId = YR::utility::IDGenerator::GenApplicationId();
auto clientsMgr = std::make_shared<YR::Libruntime::ClientsManager>();
auto metricsAdaptor = std::make_shared<YR::Libruntime::MetricsAdaptor>();
auto sec = std::make_shared<YR::Libruntime::Security>();
auto socketClient = std::make_shared<YR::Libruntime::DomainSocketClient>("/home/snuser/socket/runtime.sock");
lr = std::make_shared<YR::Libruntime::MockLibruntime>(lc, clientsMgr, metricsAdaptor, sec, socketClient);
YR::Libruntime::LibruntimeManager::Instance().SetLibRuntime(lr);
exec_ = std::make_shared<FaasExecutor>();
}
void TearDown() override
{
YR::Libruntime::LibruntimeManager::Instance().Finalize();
lr.reset();
exec_.reset();
}
private:
std::shared_ptr<YR::Libruntime::MockLibruntime> lr;
std::shared_ptr<FaasExecutor> exec_;
};
TEST_F(FaasExecutorTest, LoadFunctionsTest)
{
auto err = exec_->LoadFunctions({});
ASSERT_TRUE(err.OK()) << err.Msg();
}
TEST_F(FaasExecutorTest, SignalTest)
{
auto payload = std::make_shared<YR::Libruntime::NativeBuffer>(1);
auto err = exec_->Signal(65, payload);
ASSERT_TRUE(err.OK()) << err.Msg();
}
std::string contextMetaStr = R"(
{"funcMetaData":
{
"layers":[],"name":"0@test@cpp","description":"this is my app",
"functionUrn":"sn:cn:yrk:default:function:0@test@cpp",
"tenantId":"default","tags":null,"functionUpdateTime":"",
"functionVersionUrn":"sn:cn:yrk:default:function:0@test@cpp:latest",
"revisionId":"20241017135254074","codeSize":0,"codeSha512":"","handler":"bin/start.sh",
"runtime":"posix-custom-runtime","timeout":600,"version":"latest","deadLetterConfig":"",
"businessId":"yrk","functionType":"","func_id":"","func_name":"cpp","domain_id":"",
"project_name":"","service":"test","poolLabel":"","dependencies":"",
"enable_cloud_debug":"","isStatefulFunction":false,"isBridgeFunction":false,
"isStreamEnable":false,"type":"","enable_auth_in_header":false,"dns_domain_cfg":null,"vpcTriggerImage":"",
"stateConfig":{"lifeCycle":""}},
"s3MetaData":{"appId":"","bucketId":"","objectId":"","bucketUrl":"","code_type":"","code_url":"",
"code_filename":"",
"func_code":{"file":"","link":""}},
"codeMetaData":{"sha512":"","storage_type":"s3","code_path":"",
"appId":"","bucketId":"test","objectId":"cpp.zip","bucketUrl":"https://127.0.0,1:30110","code_type":"",
"code_url":"","code_filename":"","func_code":{"file":"","link":""}},
"envMetaData":{"environment":"fa3f8de0d7ac57ed34babf52:db67e7193a6d9ee35a15f92dc8de08f16a58d7810806921ad404bdccde",
"encrypted_user_data":"",
"envKey":"d79a80e56bd11a37c35ea5e7:d31ee89d52ab7f36094faa015a599149f1d27e295010bd328b319d3316c80bcfdeeefeffe1a3b622fb3c1ea8ffa86511dc7682086c711dc33bdb67d3c21dc93dbfdf487e90010b1905c1d168c1fe57c3",
"cryptoAlgorithm":"GCM"},
"stsMetaData":{"enableSts":false},
"resourceMetaData":{"cpu":1000,"memory":1024,"gpu_memory":0,"enable_dynamic_memory":false,"customResources":"",
"enable_tmp_expansion":false,"ephemeral_storage":0,"CustomResourcesSpec":""},
"instanceMetaData":{"maxInstance":100,"minInstance":0,"concurrentNum":100,"diskLimit":0,"instanceType":"",
"schedulePolicy":"concurrency","scalePolicy":"","idleMode":false},
"extendedMetaData":{"image_name":"","role":{"xrole":"","app_xrole":""},
"func_vpc":null,"endpoint_tenant_vpc":null,"mount_config":null,
"strategy_config":{"concurrency":0},"extend_config":"",
"initializer":{"initializer_handler":"","initializer_timeout":0},
"heartbeat":{"heartbeat_handler":""},"enterprise_project_id":"",
"log_tank_service":{"logGroupId":"","logStreamId":""},
"tracing_config":{"tracing_ak":"","tracing_sk":"","project_name":""},
"custom_container_config":{"control_path":"","image":"","command":null,"args":null,
"working_dir":"","uid":0,"gid":0},"async_config_loaded":false,"restore_hook":{},
"network_controller":{"disable_public_network":false,"trigger_access_vpcs":null},
"user_agency":{"accessKey":"","secretKey":"","token":"","securityAk":"","securitySk":"",
"securityToken":""},"custom_filebeat_config":{"sidecarConfigInfo":null,"cpu":0,"memory":0,
"version":"","imageAddress":""},"custom_health_check":{"timeoutSeconds":0,"periodSeconds":0,
"failureThreshold":0},"dynamic_config":{"enabled":false,"update_time":"","config_content":null},
"runtime_graceful_shutdown":{"maxShutdownTimeout":0},"pre_stop":{"pre_stop_handler":"",
"pre_stop_timeout":0},"rasp_config":{"init-image":"","rasp-image":"","rasp-server-ip":"",
"rasp-server-port":""}}}
)";
std::string createParamStr = R"(
{"instanceLabel": "aaaaa"}
)";
std::string deleteDecryptStr = R"(
{
"environment": "{\"key\":\"value\"}",
"encrypted_user_data": "{\"aaa\":\"bbb\"}"
}
)";
TEST_F(FaasExecutorTest, ExecuteFunctionSuccessFullyTest)
{
auto handlerPtr = std::make_unique<RegisterRuntimeHandler>();
handlerPtr->RegisterHandler([](const std::string &event, Context &context) -> std::string {
Function::FunctionLogger logger = context.GetLogger();
logger.setLevel("INFO");
logger.Info("hello cpp %s ", "user info log");
logger.Error("hello cpp %s ", "user error log");
logger.Warn("hello cpp %s ", "user warn log");
logger.Debug("hello cpp %s ", "user debug log");
EXPECT_EQ(context.GetAccessKey(), "");
EXPECT_EQ(context.GetSecretKey(), "");
EXPECT_EQ(context.GetSecurityAccessKey(), "");
EXPECT_EQ(context.GetSecuritySecretKey(), "");
EXPECT_EQ(context.GetToken(), "");
EXPECT_EQ(context.GetAlias(), "");
EXPECT_EQ(context.GetTraceId(), "traceid");
EXPECT_EQ(context.GetInvokeId(), "initializer");
EXPECT_EQ(context.GetState(), "");
EXPECT_EQ(context.GetInstanceId(), "");
EXPECT_EQ(context.GetInvokeProperty(), "");
EXPECT_EQ(context.GetRequestID(), "traceid");
EXPECT_EQ(context.GetUserData("aaa"), "bbb");
EXPECT_EQ(context.GetFunctionName(), "cpp");
EXPECT_EQ(context.GetInstanceLabel(), "aaaaa");
EXPECT_EQ(context.GetRemainingTimeInMilliSeconds(), 0);
EXPECT_EQ(context.GetRunningTimeInSeconds(), 0);
EXPECT_EQ(context.GetVersion(), "latest");
EXPECT_EQ(context.GetMemorySize(), 1024);
EXPECT_EQ(context.GetCPUNumber(), 1000);
EXPECT_EQ(context.GetProjectID(), "default");
EXPECT_EQ(context.GetPackage(), "test");
return event;
});
std::string traceId = "traceid";
handlerPtr->RegisterInitializerFunction([](Context &context) {});
SetRuntimeHandler(std::move(handlerPtr));
YR::SetEnv("key", "1");
YR::SetEnv("ENV_DELEGATE_DECRYPT", deleteDecryptStr);
YR::Libruntime::ErrorInfo err;
YR::Libruntime::FunctionMeta function;
std::vector<std::shared_ptr<YR::Libruntime::DataObject>> rawArgs;
std::vector<std::shared_ptr<YR::Libruntime::DataObject>> returnObjects;
returnObjects.push_back(std::make_shared<YR::Libruntime::DataObject>());
auto contextMetaObj = std::make_shared<YR::Libruntime::DataObject>(0, contextMetaStr.size());
contextMetaObj->data->MemoryCopy(contextMetaStr.data(), contextMetaStr.size());
rawArgs.push_back(contextMetaObj);
auto createParamObj = std::make_shared<YR::Libruntime::DataObject>(0, createParamStr.size());
createParamObj->data->MemoryCopy(createParamStr.data(), createParamStr.size());
rawArgs.push_back(createParamObj);
err = exec_->ExecuteFunction(function, libruntime::InvokeType::CreateInstance, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
rawArgs.pop_back();
std::string eventStr = "{\"body\":\"event\", \"header\": {\"X-Trace-Id\":\"traceid\"}}";
auto eventObj = std::make_shared<YR::Libruntime::DataObject>(0, eventStr.size());
eventObj->data->MemoryCopy(eventStr.data(), eventStr.size());
rawArgs.push_back(eventObj);
auto traceIdObj = std::make_shared<YR::Libruntime::DataObject>(0, traceId.size());
traceIdObj->data->MemoryCopy(traceId.data(), traceId.size());
rawArgs.push_back(traceIdObj);
EXPECT_CALL(*lr.get(), AllocReturnObject(Matcher<std::shared_ptr<YR::Libruntime::DataObject> &>(_), _, _, _, _))
.WillOnce(Invoke(MockAllocReturnObject));
err = exec_->ExecuteFunction(function, libruntime::InvokeType::InvokeFunction, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
auto result = std::string(static_cast<const char *>(returnObjects[0]->data->ImmutableData()),
returnObjects[0]->data->GetSize());
ASSERT_TRUE(result.find("event")) << result;
ASSERT_EQ(YR::GetEnv("key"), "value");
}
TEST_F(FaasExecutorTest, ExecuteFunctionFailedTest)
{
YR::Libruntime::ErrorInfo err;
YR::Libruntime::FunctionMeta function;
std::vector<std::shared_ptr<YR::Libruntime::DataObject>> rawArgs;
std::vector<std::shared_ptr<YR::Libruntime::DataObject>> returnObjects;
returnObjects.push_back(std::make_shared<YR::Libruntime::DataObject>());
auto contextMetaObj = std::make_shared<YR::Libruntime::DataObject>(0, contextMetaStr.size());
contextMetaObj->data->MemoryCopy(contextMetaStr.data(), contextMetaStr.size());
rawArgs.push_back(contextMetaObj);
EXPECT_CALL(*lr.get(), AllocReturnObject(Matcher<std::shared_ptr<YR::Libruntime::DataObject> &>(_), _, _, _, _))
.WillRepeatedly(Invoke(MockAllocReturnObject));
auto handlerPtr = std::make_unique<RegisterRuntimeHandler>();
SetRuntimeHandler(std::move(handlerPtr));
auto eventObj = std::make_shared<YR::Libruntime::DataObject>(0, 0);
rawArgs.push_back(eventObj);
err = exec_->ExecuteFunction(function, libruntime::InvokeType::InvokeFunction, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
auto result = std::string(static_cast<const char *>(returnObjects[0]->data->ImmutableData()),
returnObjects[0]->data->GetSize());
ASSERT_TRUE(result.find("call req is empty"));
std::string eventStr = "{}";
eventObj = std::make_shared<YR::Libruntime::DataObject>(0, eventStr.size());
eventObj->data->MemoryCopy(eventStr.data(), eventStr.size());
rawArgs[1] = eventObj;
err = exec_->ExecuteFunction(function, libruntime::InvokeType::InvokeFunction, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
result = std::string(static_cast<const char *>(returnObjects[0]->data->ImmutableData()),
returnObjects[0]->data->GetSize());
ASSERT_TRUE(result.find("can not find body"));
eventStr = "{\"body\": 1}";
eventObj = std::make_shared<YR::Libruntime::DataObject>(0, eventStr.size());
eventObj->data->MemoryCopy(eventStr.data(), eventStr.size());
rawArgs[1] = eventObj;
err = exec_->ExecuteFunction(function, libruntime::InvokeType::InvokeFunction, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
result = std::string(static_cast<const char *>(returnObjects[0]->data->ImmutableData()),
returnObjects[0]->data->GetSize());
ASSERT_TRUE(result.find("event type is not string"));
eventStr = "{\"body\": \"event\"}";
eventObj = std::make_shared<YR::Libruntime::DataObject>(0, eventStr.size());
eventObj->data->MemoryCopy(eventStr.data(), eventStr.size());
rawArgs[1] = eventObj;
err = exec_->ExecuteFunction(function, libruntime::InvokeType::InvokeFunction, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
result = std::string(static_cast<const char *>(returnObjects[0]->data->ImmutableData()),
returnObjects[0]->data->GetSize());
ASSERT_TRUE(result.find("can not call handlerequest before initialize"));
contextMetaObj = std::make_shared<YR::Libruntime::DataObject>(0, 0);
rawArgs[0] = contextMetaObj;
err = exec_->ExecuteFunction(function, libruntime::InvokeType::CreateInstance, rawArgs, returnObjects);
ASSERT_FALSE(err.OK()) << err.Msg();
contextMetaObj = std::make_shared<YR::Libruntime::DataObject>(0, contextMetaStr.size());
contextMetaObj->data->MemoryCopy(contextMetaStr.data(), contextMetaStr.size());
rawArgs[0] = contextMetaObj;
handlerPtr = std::make_unique<RegisterRuntimeHandler>();
handlerPtr->RegisterInitializerFunction(
[](Context &context) { throw FunctionError(ErrorCode::FUNCTION_EXCEPTION, "function error"); });
SetRuntimeHandler(std::move(handlerPtr));
err = exec_->ExecuteFunction(function, libruntime::InvokeType::CreateInstance, rawArgs, returnObjects);
ASSERT_FALSE(err.OK()) << err.Msg();
ASSERT_TRUE(result.find("function error"));
handlerPtr = std::make_unique<RegisterRuntimeHandler>();
handlerPtr->RegisterInitializerFunction([](Context &context) { throw std::runtime_error("runtime error"); });
SetRuntimeHandler(std::move(handlerPtr));
err = exec_->ExecuteFunction(function, libruntime::InvokeType::CreateInstance, rawArgs, returnObjects);
ASSERT_FALSE(err.OK()) << err.Msg();
handlerPtr = std::make_unique<RegisterRuntimeHandler>();
SetRuntimeHandler(std::move(handlerPtr));
err = exec_->ExecuteFunction(function, libruntime::InvokeType::InvokeFunction, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
result = std::string(static_cast<const char *>(returnObjects[0]->data->ImmutableData()),
returnObjects[0]->data->GetSize());
ASSERT_TRUE(result.find("undefined HandleRequest"));
handlerPtr = std::make_unique<RegisterRuntimeHandler>();
handlerPtr->RegisterHandler([](const std::string &event, Context &context) -> std::string {
throw FunctionError(ErrorCode::FUNCTION_EXCEPTION, "function error");
});
SetRuntimeHandler(std::move(handlerPtr));
err = exec_->ExecuteFunction(function, libruntime::InvokeType::InvokeFunction, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
result = std::string(static_cast<const char *>(returnObjects[0]->data->ImmutableData()),
returnObjects[0]->data->GetSize());
ASSERT_TRUE(result.find("function error"));
handlerPtr = std::make_unique<RegisterRuntimeHandler>();
handlerPtr->RegisterHandler(
[](const std::string &event, Context &context) -> std::string { throw std::runtime_error("runtime error"); });
SetRuntimeHandler(std::move(handlerPtr));
err = exec_->ExecuteFunction(function, libruntime::InvokeType::InvokeFunction, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
result = std::string(static_cast<const char *>(returnObjects[0]->data->ImmutableData()),
returnObjects[0]->data->GetSize());
ASSERT_TRUE(result.find("runtime error"));
handlerPtr = std::make_unique<RegisterRuntimeHandler>();
handlerPtr->RegisterHandler([](const std::string &event, Context &context) -> std::string {
std::string a;
a.resize(6 * 1024 * 1024 + 1, '0');
return a;
});
SetRuntimeHandler(std::move(handlerPtr));
std::string expectResult =
"{\"body\":\"function result size: 6291457, exceed limit(6291456)\",\"innerCode\":\"4004\"}";
err = exec_->ExecuteFunction(function, libruntime::InvokeType::InvokeFunction, rawArgs, returnObjects);
ASSERT_TRUE(err.OK()) << err.Msg();
result = std::string(static_cast<const char *>(returnObjects[0]->data->ImmutableData()), expectResult.size());
auto resultJson = nlohmann::json::parse(result);
std::string value = resultJson["body"];
ASSERT_TRUE(value.find("exceed limit("));
}
TEST_F(FaasExecutorTest, ExecuteShutdownFunctionTest)
{
YR::Libruntime::ErrorInfo err;
auto handlerPtr = std::make_unique<RegisterRuntimeHandler>();
SetRuntimeHandler(std::move(handlerPtr));
err = exec_->ExecuteShutdownFunction(1);
ASSERT_FALSE(err.OK());
ASSERT_TRUE(err.Msg().find("can not call prestop before initialize"));
exec_->contextEnv_ = std::make_shared<ContextEnv>();
std::unordered_map<std::string, std::string> params;
exec_->contextInvokeParams_ = std::make_shared<ContextInvokeParams>(params);
err = exec_->ExecuteShutdownFunction(1);
ASSERT_TRUE(err.OK());
handlerPtr = std::make_unique<RegisterRuntimeHandler>();
handlerPtr->RegisterPreStopFunction(
[](Context &context) { throw FunctionError(ErrorCode::FUNCTION_EXCEPTION, "function error"); });
SetRuntimeHandler(std::move(handlerPtr));
err = exec_->ExecuteShutdownFunction(1);
ASSERT_FALSE(err.OK());
ASSERT_TRUE(err.Msg().find("function error"));
handlerPtr = std::make_unique<RegisterRuntimeHandler>();
handlerPtr->RegisterPreStopFunction([](Context &context) { throw std::runtime_error("runtime error"); });
SetRuntimeHandler(std::move(handlerPtr));
err = exec_->ExecuteShutdownFunction(1);
ASSERT_FALSE(err.OK());
ASSERT_TRUE(err.Msg().find("runtime error"));
handlerPtr = std::make_unique<RegisterRuntimeHandler>();
handlerPtr->RegisterPreStopFunction([](Context &context) { return; });
SetRuntimeHandler(std::move(handlerPtr));
err = exec_->ExecuteShutdownFunction(1);
ASSERT_TRUE(err.OK()) << err.Msg();
}
TEST_F(FaasExecutorTest, CheckpointRecoverSuccessfullyTest)
{
YR::Libruntime::ErrorInfo err;
std::shared_ptr<YR::Libruntime::Buffer> data;
err = exec_->Checkpoint("instanceid", data);
ASSERT_EQ(err.Code(), YR::Libruntime::ErrorCode::ERR_OK) << err.Msg();
err = exec_->Recover(data);
ASSERT_EQ(err.Code(), YR::Libruntime::ErrorCode::ERR_OK) << err.Msg();
}
TEST_F(FaasExecutorTest, ContextImplTest)
{
std::unordered_map<std::string, std::string> params;
auto contextInvokeParams = std::make_shared<ContextInvokeParams>(params);
auto contextEnv = std::make_shared<ContextEnv>();
ContextImpl ctx(contextInvokeParams, contextEnv);
ContextImpl ctx2 = ctx;
ctx.SetFuncStartTime(1000000);
ctx.SetStateId("stateId");
ASSERT_EQ(ctx.GetInstanceId(), "stateId");
ctx.SetState("state");
ASSERT_EQ(ctx.GetState(), "state");
ctx.SetInvokeProperty("prop");
ASSERT_EQ(ctx.GetInvokeProperty(), "prop");
}
TEST_F(FaasExecutorTest, RuntimeTest)
{
std::shared_ptr<Runtime> rt = std::make_shared<Runtime>();
rt->InitRuntimeLogger();
rt->RegisterHandler([](const std::string &request, Function::Context &context) -> std::string { return ""; });
rt->RegisterInitializerFunction([](Function::Context &context) { return; });
rt->RegisterPreStopFunction([](Function::Context &context) { return; });
rt->InitState([](const std::string &, Function::Context &) { return; });
ASSERT_NO_THROW(rt->BuildRegisterRuntimeHandler());
rt->ReleaseRuntimeLogger();
}
}
}