1
0
Fork 0
mirror of synced 2024-06-13 16:24:47 +12:00

Improvements to executor

- Executor now loads runtimes from php-runtimes package
- Executor now handles timeouts correctly
- Executor can now shutdown and remove containers before shutting down itself preventing a `docker-compose stop` failure due to active network endpoints.
- Fixed a issue with JWT's not working
- Improved general executor reliability
- Tests now pass!
This commit is contained in:
Bradley Schofield 2021-09-06 01:37:20 +01:00
parent 35ed296b75
commit bca326dc8d
15 changed files with 129 additions and 119 deletions

View file

@ -2,8 +2,6 @@
use Utopia\App;
use Appwrite\Runtimes\Runtimes;
use Appwrite\Runtimes\Runtime;
use Utopia\System\System;
/**
* List of Appwrite Cloud Functions supported runtimes
@ -12,15 +10,6 @@ $runtimes = new Runtimes();
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));
$node = new Runtime('node', 'Node.js');
$node->addVersion('NG-Latest', 'node:16-alpine-nx', 'node-runtime', [System::X86, System::PPC, System::ARM]);
$deno = new Runtime('deno', 'Deno');
$deno->addVersion('NG-Latest', 'deno:latest-alpine-nx', 'deno-runtime', [System::X86, System::PPC, System::ARM]);
$runtimes->add($node);
$runtimes->add($deno);
$runtimes = $runtimes->getAll(true, $allowList);
return $runtimes;

View file

@ -354,6 +354,8 @@ App::patch('/v1/functions/:functionId/tag')
/** @var Appwrite\Database\Database $projectDB */
/** @var Appwrite\Database\Document $project */
$function = $projectDB->getDocument($functionId);
$ch = \curl_init();
\curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor:8080/v1/tag");
\curl_setopt($ch, CURLOPT_POST, true);
@ -362,7 +364,7 @@ App::patch('/v1/functions/:functionId/tag')
'tagId' => $tag
]));
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
\curl_setopt($ch, CURLOPT_TIMEOUT, 10);
\curl_setopt($ch, CURLOPT_TIMEOUT, 900);
\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
\curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
@ -372,13 +374,20 @@ App::patch('/v1/functions/:functionId/tag')
$executorResponse = \curl_exec($ch);
$error = \curl_error($ch);
if (!empty($error)) {
throw new Exception('Curl error: ' . $error, 500);
}
// Check status code
$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (200 !== $statusCode) {
throw new Exception('Executor error: ' . $executorResponse, $statusCode);
}
\curl_close($ch);
$response->dynamic(new Document(json_decode($executorResponse, true)), Response::MODEL_EXECUTION);
$response->dynamic(new Document(json_decode($executorResponse, true)), Response::MODEL_FUNCTION);
});
App::delete('/v1/functions/:functionId')

View file

@ -13,6 +13,7 @@ use Utopia\App;
use Utopia\Swoole\Request;
use Appwrite\Utopia\Response;
use Utopia\CLI\Console;
use Swoole\Process;
use Swoole\Http\Server;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
@ -24,8 +25,7 @@ use Utopia\Config\Config;
use Utopia\Validator\ArrayList;
use Utopia\Validator\JSON;
use Utopia\Validator\Text;
use function PHPUnit\Framework\isEmpty;
use Cron\CronExpression;
require_once __DIR__ . '/workers.php';
@ -44,7 +44,7 @@ Swoole\Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_CURL);
// go(function() use ($runtime, $orchestration) {
// Console::info('Warming up '.$runtime['name'].' '.$runtime['version'].' environment...');
// $response = $orchestration->pull($runtime['image']);
// $response = $orchestration->pull($runtime['image']);
// if ($response) {
// Console::success("Successfully Warmed up {$runtime['name']} {$runtime['version']}!");
@ -83,12 +83,12 @@ App::post('/v1/execute') // Define Route
->param('data', '', new Text(1024), '', true)
->param('webhooks', [], new ArrayList(new JSON()), [], true)
->param('userId', '', new Text(1024), '', true)
->param('JWT', '', new Text(1024), '', true)
->param('jwt', '', new Text(1024), '', true)
->inject('response')
->action(
function ($trigger, $projectId, $executionId, $functionId, $event, $eventData, $data, $webhooks, $userId, $JWT, $request, $response) {
function ($trigger, $projectId, $executionId, $functionId, $event, $eventData, $data, $webhooks, $userId, $jwt, $request, $response) {
try {
$data = execute($trigger, $projectId, $executionId, $functionId, $event, $eventData, $data, $webhooks, $userId, $JWT);
$data = execute($trigger, $projectId, $executionId, $functionId, $event, $eventData, $data, $webhooks, $userId, $jwt);
return $response->json($data);
} catch (Exception $e) {
return $response
@ -451,12 +451,19 @@ function execute(string $trigger, string $projectId, string $executionId, string
$stderr = 'Failed to connect to executor runtime after 5 attempts.';
$exitCode = 124;
}
// If timeout error
if ($errNo == CURLE_OPERATION_TIMEDOUT) {
$exitCode = 124;
}
if ($errNo !== 0 && $errNo != CURLE_COULDNT_CONNECT) {
if ($errNo !== 0 && $errNo != CURLE_COULDNT_CONNECT && $errNo != CURLE_OPERATION_TIMEDOUT) {
throw new Exception('Curl error: ' . $error, 500);
}
$executionData = json_decode($executorResponse, true);
if (!empty($executorResponse)) {
$executionData = json_decode($executorResponse, true);
}
if (isset($executionData['code'])) {
$exitCode = $executionData['code'];
@ -529,6 +536,28 @@ App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
$http = new Server("0.0.0.0", 8080);
$http->on('start', function ($http) {
Process::signal(SIGINT, function () use ($http) {
handleShutdown();
$http->shutdown();
});
Process::signal(SIGQUIT, function () use ($http) {
handleShutdown();
$http->shutdown();
});
Process::signal(SIGKILL, function () use ($http) {
handleShutdown();
$http->shutdown();
});
Process::signal(SIGTERM, function () use ($http) {
handleShutdown();
$http->shutdown();
});
});
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
global $register;
@ -624,4 +653,21 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
}
});
$http->start();
$http->start();
function handleShutdown() {
Console::info('Cleaning up containers before shutdown...');
// Remove all containers.
global $activeFunctions;
global $orchestration;
foreach ($activeFunctions as $container) {
try {
$orchestration->remove($container->getId(), true);
Console::info('Removed container '.$container->getName());
} catch (Exception $e) {
Console::error('Failed to remove container: '.$container->getName());
}
}
}

View file

@ -254,9 +254,9 @@ class FunctionsV1 extends Worker
'executionId' => null,
'trigger' => 'schedule',
'scheduleOriginal' => $function->getAttribute('schedule', ''),
]); // Async task rescheduale
]); // Async task reschedule
$this->execute($trigger, $projectId, $executionId, $database, $function, /*$event*/'', /*$eventData*/'', $data, $webhooks, $userId, $jwt);
$this->execute($trigger, $projectId, $executionId, $database, $function, $event, $eventData, $data, $webhooks, $userId, $jwt);
break;
case 'http':
@ -268,7 +268,7 @@ class FunctionsV1 extends Worker
throw new Exception('Function not found ('.$functionId.')');
}
$this->execute($trigger, $projectId, $executionId, $database, $function, /*$event*/'', /*$eventData*/'', $data, $webhooks, $userId, $jwt);
$this->execute($trigger, $projectId, $executionId, $database, $function, $event, $eventData, $data, $webhooks, $userId, $jwt);
break;
default:

View file

@ -24,6 +24,10 @@
{
"url": "https://github.com/PineappleIOnic/orchestration.git",
"type": "git"
},
{
"url": "https://github.com/PineappleIOnic/php-runtimes.git",
"type": "git"
}
],
"require": {
@ -42,7 +46,7 @@
"ext-sockets": "*",
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.4.*",
"appwrite/php-runtimes": "dev-new-runtimes",
"utopia-php/framework": "0.18.*",
"utopia-php/abuse": "0.5.*",

46
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "9d5d3107489a374a84612765ee3ae4c5",
"content-hash": "67e012ba43c42585ebaaf21f3e2ab840",
"packages": [
{
"name": "adhocore/jwt",
@ -115,17 +115,11 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.4.0",
"version": "dev-new-runtimes",
"source": {
"type": "git",
"url": "https://github.com/appwrite/php-runtimes.git",
"reference": "cc7090a67d8824c779190b38873f0f8154f906b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/php-runtimes/zipball/cc7090a67d8824c779190b38873f0f8154f906b2",
"reference": "cc7090a67d8824c779190b38873f0f8154f906b2",
"shasum": ""
"url": "https://github.com/PineappleIOnic/php-runtimes.git",
"reference": "d633a896c4e5c20fd166f4b5461a869b0db2616e"
},
"require": {
"php": ">=8.0",
@ -142,7 +136,6 @@
"Appwrite\\Runtimes\\": "src/Runtimes"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
@ -162,11 +155,7 @@
"php",
"runtimes"
],
"support": {
"issues": "https://github.com/appwrite/php-runtimes/issues",
"source": "https://github.com/appwrite/php-runtimes/tree/0.4.0"
},
"time": "2021-06-23T07:17:12+00:00"
"time": "2021-09-02T09:13:23+00:00"
},
{
"name": "chillerlan/php-qrcode",
@ -5096,16 +5085,16 @@
},
{
"name": "symfony/console",
"version": "v5.3.6",
"version": "v5.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2"
"reference": "8b1008344647462ae6ec57559da166c2bfa5e16a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
"reference": "51b71afd6d2dc8f5063199357b9880cea8d8bfe2",
"url": "https://api.github.com/repos/symfony/console/zipball/8b1008344647462ae6ec57559da166c2bfa5e16a",
"reference": "8b1008344647462ae6ec57559da166c2bfa5e16a",
"shasum": ""
},
"require": {
@ -5175,7 +5164,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.3.6"
"source": "https://github.com/symfony/console/tree/v5.3.7"
},
"funding": [
{
@ -5191,7 +5180,7 @@
"type": "tidelift"
}
],
"time": "2021-07-27T19:10:22+00:00"
"time": "2021-08-25T20:02:16+00:00"
},
{
"name": "symfony/deprecation-contracts",
@ -5748,16 +5737,16 @@
},
{
"name": "symfony/string",
"version": "v5.3.3",
"version": "v5.3.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1"
"reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
"reference": "bd53358e3eccec6a670b5f33ab680d8dbe1d4ae1",
"url": "https://api.github.com/repos/symfony/string/zipball/8d224396e28d30f81969f083a58763b8b9ceb0a5",
"reference": "8d224396e28d30f81969f083a58763b8b9ceb0a5",
"shasum": ""
},
"require": {
@ -5811,7 +5800,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v5.3.3"
"source": "https://github.com/symfony/string/tree/v5.3.7"
},
"funding": [
{
@ -5827,7 +5816,7 @@
"type": "tidelift"
}
],
"time": "2021-06-27T11:44:38+00:00"
"time": "2021-08-26T08:00:08+00:00"
},
{
"name": "theseer/tokenizer",
@ -6117,6 +6106,7 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"appwrite/php-runtimes": 20,
"utopia-php/orchestration": 20
},
"prefer-stable": false,

View file

@ -39,7 +39,7 @@ services:
build:
context: .
args:
- DEBUG=false
- DEBUG=true
- TESTING=true
- VERSION=dev
ports:
@ -319,12 +319,13 @@ services:
- -e
- app/executor.php
- -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
stop_signal: SIGINT
ports:
- "8080:8080"
build:
context: .
args:
- DEBUG=true
- DEBUG=false
- TESTING=true
- VERSION=dev
networks:

View file

@ -191,7 +191,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(201, $tag['headers']['status-code']);
$this->assertNotEmpty($tag['body']['$id']);
$this->assertIsInt($tag['body']['dateCreated']);
$this->assertEquals('php index.php', $tag['body']['entrypoint']);
$this->assertEquals('index.php', $tag['body']['entrypoint']);
$this->assertGreaterThan(10000, $tag['body']['size']);
/**
@ -327,7 +327,6 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('PHP', $execution['body']['stdout']);
$this->assertStringContainsString('8.0', $execution['body']['stdout']);
$this->assertEquals('', $execution['body']['stderr']);
$this->assertGreaterThan(0.05, $execution['body']['time']);
$this->assertLessThan(0.500, $execution['body']['time']);
/**
@ -454,7 +453,7 @@ class FunctionsCustomServerTest extends Scope
{
$name = 'php-8.0';
$code = realpath(__DIR__ . '/../../../resources/functions').'/timeout.tar.gz';
$entrypoint = 'php index.php';
$entrypoint = 'index.php';
$timeout = 2;
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
@ -488,6 +487,7 @@ class FunctionsCustomServerTest extends Scope
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'functionId' => $functionId,
'tag' => $tagId,
]);
@ -518,7 +518,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertEquals($executions['body']['executions'][0]['status'], 'failed');
$this->assertEquals($executions['body']['executions'][0]['exitCode'], 1);
$this->assertEquals($executions['body']['executions'][0]['exitCode'], 124);
$this->assertGreaterThan(2, $executions['body']['executions'][0]['time']);
$this->assertLessThan(3, $executions['body']['executions'][0]['time']);
$this->assertEquals($executions['body']['executions'][0]['stdout'], '');

View file

@ -1,34 +1,17 @@
<?php
include './vendor/autoload.php';
use Appwrite\Client;
use Appwrite\Services\Storage;
// $client = new Client();
// $client
// ->setEndpoint($_ENV['APPWRITE_ENDPOINT']) // Your API Endpoint
// ->setProject($_ENV['APPWRITE_PROJECT']) // Your project ID
// ->setKey($_ENV['APPWRITE_SECRET']) // Your secret API key
// ;
// $storage = new Storage($client);
// $result = $storage->getFile($_ENV['APPWRITE_FILEID']);
$output = [
'APPWRITE_FUNCTION_ID' => $_ENV['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $_ENV['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_TAG' => $_ENV['APPWRITE_FUNCTION_TAG'],
'APPWRITE_FUNCTION_TRIGGER' => $_ENV['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => $_ENV['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $_ENV['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => $_ENV['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => $_ENV['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' => $_ENV['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' => $_ENV['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' => $_ENV['APPWRITE_FUNCTION_JWT'],
];
echo json_encode($output);
return function ($request, $response) {
$response->json([
'APPWRITE_FUNCTION_ID' => $request->env['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request->env['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_TAG' => $request->env['APPWRITE_FUNCTION_TAG'],
'APPWRITE_FUNCTION_TRIGGER' => $request->env['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => $request->env['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $request->env['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => $request->env['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => $request->env['APPWRITE_FUNCTION_EVENT_DATA'],
'APPWRITE_FUNCTION_DATA' => $request->env['APPWRITE_FUNCTION_DATA'],
'APPWRITE_FUNCTION_USER_ID' => $request->env['APPWRITE_FUNCTION_USER_ID'],
'APPWRITE_FUNCTION_JWT' => $request->env['APPWRITE_FUNCTION_JWT'],
]);
};

Binary file not shown.

View file

@ -1,28 +1,14 @@
<?php
include './vendor/autoload.php';
use Appwrite\Client;
use Appwrite\Services\Storage;
$client = new Client();
$client
->setEndpoint($_ENV['APPWRITE_ENDPOINT']) // Your API Endpoint
->setProject($_ENV['APPWRITE_PROJECT']) // Your project ID
->setKey($_ENV['APPWRITE_SECRET']) // Your secret API key
;
$storage = new Storage($client);
// $result = $storage->getFile($_ENV['APPWRITE_FILEID']);
echo $_ENV['APPWRITE_FUNCTION_ID']."\n";
echo $_ENV['APPWRITE_FUNCTION_NAME']."\n";
echo $_ENV['APPWRITE_FUNCTION_TAG']."\n";
echo $_ENV['APPWRITE_FUNCTION_TRIGGER']."\n";
echo $_ENV['APPWRITE_FUNCTION_RUNTIME_NAME']."\n";
echo $_ENV['APPWRITE_FUNCTION_RUNTIME_VERSION']."\n";
// echo $result['$id'];
echo $_ENV['APPWRITE_FUNCTION_EVENT']."\n";
echo $_ENV['APPWRITE_FUNCTION_EVENT_DATA']."\n";
return function ($request, $response) {
$response->json([
'APPWRITE_FUNCTION_ID' => $request->env['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request->env['APPWRITE_FUNCTION_NAME'],
'APPWRITE_FUNCTION_TAG' => $request->env['APPWRITE_FUNCTION_TAG'],
'APPWRITE_FUNCTION_TRIGGER' => $request->env['APPWRITE_FUNCTION_TRIGGER'],
'APPWRITE_FUNCTION_RUNTIME_NAME' => $request->env['APPWRITE_FUNCTION_RUNTIME_NAME'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $request->env['APPWRITE_FUNCTION_RUNTIME_VERSION'],
'APPWRITE_FUNCTION_EVENT' => $request->env['APPWRITE_FUNCTION_EVENT'],
'APPWRITE_FUNCTION_EVENT_DATA' => $request->env['APPWRITE_FUNCTION_EVENT_DATA'],
]);
};

View file

@ -1,3 +1,5 @@
<?php
sleep(5);
return function ($request, $response) {
sleep(5);
};