From 3d5d45d676b064fec3008852dce0cb38d5a5bb86 Mon Sep 17 00:00:00 2001 From: Matej Baco Date: Mon, 11 Jul 2022 10:22:48 +0200 Subject: [PATCH] WIP: Functions proxy --- .env | 3 +- app/functionsProxy.php | 163 ++++++++++++++++++++++++++++++++++++++++- docker-compose.yml | 50 +------------ 3 files changed, 164 insertions(+), 52 deletions(-) diff --git a/.env b/.env index 1aa230b2e6..f9b12d3a93 100644 --- a/.env +++ b/.env @@ -83,4 +83,5 @@ _APP_LOGGING_CONFIG= DOCKERHUB_PULL_USERNAME= DOCKERHUB_PULL_PASSWORD= DOCKERHUB_PULL_EMAIL= -_APP_EXECUTORS=exec_fra_01=http://appwrite-executor/v1,exec_fra_02=http://appwrite-executor2/v1 \ No newline at end of file +#_APP_EXECUTORS=exec_fra_01=http://appwrite-executor/v1,exec_fra_02=http://appwrite-executor2/v1 +_APP_EXECUTORS=exec_fra_01=http://appwrite-executor/v1 \ No newline at end of file diff --git a/app/functionsProxy.php b/app/functionsProxy.php index 67366e357c..4c1bc19d94 100644 --- a/app/functionsProxy.php +++ b/app/functionsProxy.php @@ -1,14 +1,23 @@ fetchExecutorsState($params[0]), [$redisPool]); + Swoole\Event::wait(); +} + +/** + * Create logger instance + */ +$providerName = App::getEnv('_APP_LOGGING_PROVIDER', ''); +$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', ''); +$logger = null; + +if (!empty($providerName) && !empty($providerConfig) && Logger::hasProvider($providerName)) { + $classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName); + $adapter = new $classname($providerConfig); + $logger = new Logger($adapter); +} + +function logError(Throwable $error, string $action, Utopia\Route $route = null) +{ + global $logger; + + if ($logger) { + $version = App::getEnv('_APP_VERSION', 'UNKNOWN'); + + $log = new Log(); + $log->setNamespace("executor"); + $log->setServer(\gethostname()); + $log->setVersion($version); + $log->setType(Log::TYPE_ERROR); + $log->setMessage($error->getMessage()); + + if ($route) { + $log->addTag('method', $route->getMethod()); + $log->addTag('url', $route->getPath()); + } + + $log->addTag('code', $error->getCode()); + $log->addTag('verboseType', get_class($error)); + + $log->addExtra('file', $error->getFile()); + $log->addExtra('line', $error->getLine()); + $log->addExtra('trace', $error->getTraceAsString()); + $log->addExtra('detailedTrace', $error->getTrace()); + + $log->setAction($action); + + $isProduction = App::getEnv('_APP_ENV', 'development') === 'production'; + $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); + + $responseCode = $logger->addLog($log); + Console::info('Executor log pushed with status code: ' . $responseCode); + } + + Console::error('[Error] Type: ' . get_class($error)); + Console::error('[Error] Message: ' . $error->getMessage()); + Console::error('[Error] File: ' . $error->getFile()); + Console::error('[Error] Line: ' . $error->getLine()); +} Console::success("Waiting for executors to start..."); \sleep(5); // Wait a little so executors can start -fetchExecutorsState($redisPool); +// fetchExecutorsState($redisPool); +// Timer::tick(30000, fn (int $timerId, array $params) => fetchExecutorsState($params[0]), [$redisPool]); Console::success("Functions proxy is ready."); + +App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode + +$http = new Server("0.0.0.0", 80); + +/** Set callbacks */ +App::error(function ($utopia, $error, $request, $response) { + $route = $utopia->match($request); + logError($error, "httpError", $route); + + switch ($error->getCode()) { + case 400: // Error allowed publicly + case 401: // Error allowed publicly + case 402: // Error allowed publicly + case 403: // Error allowed publicly + case 404: // Error allowed publicly + case 406: // Error allowed publicly + case 409: // Error allowed publicly + case 412: // Error allowed publicly + case 425: // Error allowed publicly + case 429: // Error allowed publicly + case 501: // Error allowed publicly + case 503: // Error allowed publicly + $code = $error->getCode(); + break; + default: + $code = 500; // All other errors get the generic 500 server error status code + } + + $output = [ + 'message' => $error->getMessage(), + 'code' => $error->getCode(), + 'file' => $error->getFile(), + 'line' => $error->getLine(), + 'trace' => $error->getTrace(), + 'version' => App::getEnv('_APP_VERSION', 'UNKNOWN') + ]; + + $response + ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate') + ->addHeader('Expires', '0') + ->addHeader('Pragma', 'no-cache') + ->setStatusCode($code); + + $response->json($output); +}, ['utopia', 'error', 'request', 'response']); + +App::get('/') + ->desc("Proxy request into executor") + ->inject('response') + ->action(function (Response $response) { + $response + ->setStatusCode(Response::STATUS_CODE_OK) + ->json([ 'works' => true ]); + }); + +App::init(function ($request, $response) { + $secretKey = $request->getHeader('x-appwrite-functions-proxy-key', ''); + + if (empty($secretKey)) { + throw new Exception('Missing proxy key', 401); + } + + if ($secretKey !== App::getEnv('_APP_FUNCTIONS_PROXY_SECRET', '')) { + throw new Exception('Missing proxy key', 401); + } +}, ['request', 'response']); + +$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) { + $request = new Request($swooleRequest); + $response = new Response($swooleResponse); + $app = new App('UTC'); + + try { + $app->run($request, $response); + } catch (\Throwable $th) { + logError($th, "serverError"); + $swooleResponse->setStatusCode(500); + $output = [ + 'message' => 'Error: ' . $th->getMessage(), + 'code' => 500, + 'file' => $th->getFile(), + 'line' => $th->getLine(), + 'trace' => $th->getTrace() + ]; + $swooleResponse->end(\json_encode($output)); + } +}); + +$http->start(); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index cc9c604f5e..c475f51bd9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -420,6 +420,8 @@ services: container_name: appwrite-functions-proxy build: context: . + ports: + - 5544:80 networks: - appwrite volumes: @@ -436,6 +438,7 @@ services: - _APP_REDIS_PASS - _APP_EXECUTORS - _APP_EXECUTOR_SECRET + - _APP_FUNCTIONS_PROXY_SECRET appwrite-worker-functions: entrypoint: worker-functions @@ -518,53 +521,6 @@ services: - DOCKERHUB_PULL_USERNAME - DOCKERHUB_PULL_PASSWORD - appwrite-executor2: - container_name: appwrite-executor2 - <<: *x-logging - entrypoint: executor - stop_signal: SIGINT - build: - context: . - args: - - DEBUG=false - - TESTING=true - - VERSION=dev - networks: - appwrite: - runtimes: - ports: - - 9529:80 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./app:/usr/src/code/app - - ./src:/usr/src/code/src - - appwrite-functions:/storage/functions:rw - - appwrite-builds:/storage/builds:rw - - /tmp:/tmp:rw - depends_on: - - redis - - mariadb - - appwrite - - appwrite-functions-proxy - environment: - - _APP_ENV - - _APP_VERSION - - _APP_FUNCTIONS_TIMEOUT - - _APP_FUNCTIONS_BUILD_TIMEOUT - - _APP_FUNCTIONS_CONTAINERS - - _APP_FUNCTIONS_RUNTIMES - - _APP_FUNCTIONS_CPUS - - _APP_FUNCTIONS_MEMORY - - _APP_FUNCTIONS_MEMORY_SWAP - - _APP_FUNCTIONS_INACTIVE_THRESHOLD - - _APP_EXECUTOR_SECRET - - OPEN_RUNTIMES_NETWORK - - _APP_LOGGING_PROVIDER - - _APP_LOGGING_CONFIG - - *x-env-storage - - DOCKERHUB_PULL_USERNAME - - DOCKERHUB_PULL_PASSWORD - appwrite-worker-mails: entrypoint: worker-mails <<: *x-logging