Compare commits

...
Author SHA1 Message Date
Bradley Schofield 6e521165da Update composer.lock 2024-07-10 17:11:53 +09:00
Bradley Schofield 32dc40aaff Merge branch '1.6.x' into feat-runtime-controls 2024-07-10 17:11:48 +09:00
Bradley Schofield 16716382f5 Merge branch 'feat-runtime-controls' of https://github.com/appwrite/appwrite into feat-runtime-controls 2024-07-10 16:58:47 +09:00
Bradley Schofield d7f02fc72b Update Executor.php 2024-07-10 16:58:33 +09:00
119e25c0b4 Update docs/references/functions/get-specs.md
Co-authored-by: Matej Bačo <matejbacocom@gmail.com>
2024-07-10 16:37:10 +09:00
Bradley Schofield 59e782c94d Fix Tests, increase default max's for memory and CPUs 2024-07-10 07:04:25 +00:00
Bradley Schofield cb7a77aa98 Run Linter 2024-07-10 14:56:58 +09:00
Bradley Schofield f5968a1c78 Merge branch 'main' into feat-runtime-controls 2024-07-10 14:54:38 +09:00
Bradley Schofield c7f3f5640a Address Matej's Changes, Add tests for API 2024-07-10 14:52:17 +09:00
b15dc0c4a8 Update src/Appwrite/Utopia/Response/Model/Func.php
Co-authored-by: Matej Bačo <matejbacocom@gmail.com>
2024-07-09 15:26:55 +09:00
Bradley Schofield 06c8efa34a Update defaults, Switch to integer 2024-07-04 12:38:36 +09:00
Bradley Schofield 2dffddf161 Add Function Specs API 2024-06-12 14:20:10 +09:00
Bradley Schofield f92d5ed93b Allow for equals in filterbelowthreshold 2024-06-11 17:25:59 +09:00
Bradley Schofield d0896ee3b3 Implement plan parameters and fix TS Enum issue 2024-06-11 17:08:45 +09:00
Bradley Schofield 539b9fb1d8 Implement CPU and Memory runtime controls 2024-06-10 12:46:15 +09:00
22 changed files with 420 additions and 25 deletions
+1 -1
View File
@@ -68,7 +68,7 @@ _APP_STORAGE_PREVIEW_LIMIT=20000000
_APP_FUNCTIONS_SIZE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
_APP_FUNCTIONS_CPUS=1
_APP_FUNCTIONS_CPUS=2
_APP_FUNCTIONS_MEMORY=1024
_APP_FUNCTIONS_INACTIVE_THRESHOLD=600
_APP_FUNCTIONS_MAINTENANCE_INTERVAL=600
+22
View File
@@ -3055,6 +3055,28 @@ $projectCollections = array_merge([
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('memory'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 512,
'signed' => false,
'required' => true,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('cpus'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 1,
'signed' => false,
'required' => true,
'default' => null,
'filters' => [],
],
[
'$id' => ID::custom('scopes'),
'type' => Database::VAR_STRING,
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+36 -3
View File
@@ -10,6 +10,8 @@ use Appwrite\Event\Usage;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\Validator\Cpus;
use Appwrite\Functions\Validator\Memory;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Tasks\ScheduleExecutions;
use Appwrite\Task\Validator\Cron;
@@ -163,6 +165,8 @@ App::post('/v1/functions')
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.', true)
->param('templateBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function template.', true)
->param('memory', 512, new Memory(), 'Memory in MB allocated for the function.', true)
->param('cpus', 1, new Cpus(), 'CPU cores allocated for the function.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
@@ -172,7 +176,7 @@ App::post('/v1/functions')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, int $memory, int $cpus, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
@@ -233,6 +237,8 @@ App::post('/v1/functions')
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'memory' => $memory,
'cpus' => $cpus
]));
$schedule = Authorization::skip(
@@ -446,6 +452,25 @@ App::get('/v1/functions/runtimes')
]), Response::MODEL_RUNTIME_LIST);
});
App::get('/v1/functions/specs')
->groups(['api', 'functions'])
->desc('Get available function specs')
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getSpecs')
->label('sdk.description', '/docs/references/functions/get-specs.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SPECS)
->inject('response')
->action(function (Response $response) {
$response->dynamic(new Document([
'memory' => Memory::getAllowedValues(),
'cpus' => Cpus::getAllowedValues()
]), Response::MODEL_SPECS);
});
App::get('/v1/functions/:functionId')
->groups(['api', 'functions'])
->desc('Get function')
@@ -692,6 +717,8 @@ App::put('/v1/functions/:functionId')
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
->param('memory', 512, new Memory(), 'Memory in MB allocated for the function.', true)
->param('cpus', 1, new Cpus(), 'CPU cores allocated for the function.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
@@ -700,7 +727,7 @@ App::put('/v1/functions/:functionId')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, int $memory, int $cpus, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
// TODO: If only branch changes, re-deploy
$function = $dbForProject->getDocument('functions', $functionId);
@@ -795,7 +822,9 @@ App::put('/v1/functions/:functionId')
$function->getAttribute('entrypoint') !== $entrypoint ||
$function->getAttribute('commands') !== $commands ||
$function->getAttribute('providerRootDirectory') !== $providerRootDirectory ||
$function->getAttribute('runtime') !== $runtime
$function->getAttribute('runtime') !== $runtime ||
$function->getAttribute('memory') !== $memory ||
$function->getAttribute('cpus') !== $cpus
) {
$live = false;
}
@@ -822,6 +851,8 @@ App::put('/v1/functions/:functionId')
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'search' => implode(' ', [$functionId, $name, $runtime]),
'memory' => $memory,
'cpus' => $cpus,
])));
// Redeploy logic
@@ -1847,6 +1878,8 @@ App::post('/v1/functions/:functionId/executions')
path: $path,
method: $method,
headers: $headers,
cpus: $function->getAttribute('cpus', 1),
memory: $function->getAttribute('memory', 512),
runtimeEntrypoint: $command,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
+2
View File
@@ -272,6 +272,8 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
path: $path,
method: $method,
headers: $headers,
cpus: $function->getAttribute('cpus', 1),
memory: $function->getAttribute('memory', 512),
runtimeEntrypoint: $command,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
Generated
+6 -6
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": "06f391b62842a79736fe3fe77ec82adf",
"content-hash": "f5f5f624d7edf2e0a405f4669ae8f672",
"packages": [
{
"name": "adhocore/jwt",
@@ -3157,16 +3157,16 @@
},
{
"name": "laravel/pint",
"version": "v1.16.1",
"version": "v1.16.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "9266a47f1b9231b83e0cfd849009547329d871b1"
"reference": "51f1ba679a6afe0315621ad143d788bd7ded0eca"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/9266a47f1b9231b83e0cfd849009547329d871b1",
"reference": "9266a47f1b9231b83e0cfd849009547329d871b1",
"url": "https://api.github.com/repos/laravel/pint/zipball/51f1ba679a6afe0315621ad143d788bd7ded0eca",
"reference": "51f1ba679a6afe0315621ad143d788bd7ded0eca",
"shasum": ""
},
"require": {
@@ -3219,7 +3219,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2024-06-18T16:50:05+00:00"
"time": "2024-07-09T15:58:08+00:00"
},
{
"name": "matthiasmullie/minify",
+1
View File
@@ -0,0 +1 @@
Get allowed function memory and cpus values on this instance.
+92
View File
@@ -0,0 +1,92 @@
<?php
namespace Appwrite\Functions\Validator;
use Utopia\System\System;
use Utopia\Validator;
const CPU_VALUES = [1, 2, 4, 8, 16];
class Cpus extends Validator
{
private static function filterBelowThreshold(array $inputArray, int $threshold): array
{
return \array_filter($inputArray, function ($value) use ($threshold) {
return $value <= $threshold;
});
}
/**
* Allowed Values.
*
* Get allowed values taking into account the limits set by the environment variables.
*
* @return array
*/
public static function getAllowedValues(): array
{
return self::filterBelowThreshold(CPU_VALUES, System::getEnv('_APP_FUNCTIONS_CPUS', 4));
}
/**
* Get Description.
*
* Returns validator description.
*
* @return string
*/
public function getDescription(): string
{
return 'Integer must be a valid memory value of ' . self::filterBelowThreshold(CPU_VALUES, System::getEnv('_APP_FUNCTIONS_MEMORY', 1024));
}
/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
if (empty($value)) {
return false;
}
if (!\is_numeric($value)) {
return false;
}
if (!\in_array($value, self::filterBelowThreshold(CPU_VALUES, System::getEnv('_APP_FUNCTIONS_CPUS', 4)))) {
return false;
}
return true;
}
/**
* Is array.
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type.
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_INTEGER;
}
}
@@ -0,0 +1,92 @@
<?php
namespace Appwrite\Functions\Validator;
use Utopia\System\System;
use Utopia\Validator;
const MEMORY_VALUES = [512, 1024, 2048, 4096, 8192, 16384];
class Memory extends Validator
{
private static function filterBelowThreshold(array $inputArray, int $threshold): array
{
return \array_filter($inputArray, function ($value) use ($threshold) {
return $value <= $threshold;
});
}
/**
* Get Allowed Values.
*
* Get allowed values taking into account the limits set by the environment variables.
*
* @return array
*/
public static function getAllowedValues(): array
{
return self::filterBelowThreshold(MEMORY_VALUES, System::getEnv('_APP_FUNCTIONS_MEMORY', 1024));
}
/**
* Get Description.
*
* Returns validator description.
*
* @return string
*/
public function getDescription(): string
{
return 'Integer must be a valid memory value of ' . self::filterBelowThreshold(MEMORY_VALUES, System::getEnv('_APP_FUNCTIONS_MEMORY', 1024));
}
/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
if (empty($value)) {
return false;
}
if (!\is_numeric($value)) {
return false;
}
if (!\in_array($value, self::filterBelowThreshold(MEMORY_VALUES, System::getEnv('_APP_FUNCTIONS_MEMORY', 1024)))) {
return false;
}
return true;
}
/**
* Is array.
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type.
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_INTEGER;
}
}
+2
View File
@@ -425,6 +425,8 @@ class Builds extends Action
source: $source,
image: $runtime['image'],
version: $version,
cpus: 2,
memory: 1024,
remove: true,
entrypoint: $deployment->getAttribute('entrypoint'),
destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}",
@@ -486,6 +486,8 @@ class Functions extends Action
path: $path,
method: $method,
headers: $headers,
cpus: $function->getAttribute('cpus', 1),
memory: $function->getAttribute('memory', 512),
runtimeEntrypoint: $command,
logging: $function->getAttribute('logging', true),
);
@@ -428,7 +428,7 @@ class Swagger2 extends Format
}
}
if ($allowed) {
if ($allowed && $validator->getType() === 'string') {
$node['enum'] = $validator->getList();
$node['x-enum-name'] = $this->getEnumName($route->getLabel('sdk.namespace', ''), $method, $name);
$node['x-enum-keys'] = $this->getEnumKeys($route->getLabel('sdk.namespace', ''), $method, $name);
+3
View File
@@ -83,6 +83,7 @@ use Appwrite\Utopia\Response\Model\ProviderRepository;
use Appwrite\Utopia\Response\Model\Rule;
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\Session;
use Appwrite\Utopia\Response\Model\Specs;
use Appwrite\Utopia\Response\Model\Subscriber;
use Appwrite\Utopia\Response\Model\Target;
use Appwrite\Utopia\Response\Model\Team;
@@ -251,6 +252,7 @@ class Response extends SwooleResponse
public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
public const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
public const MODEL_HEADERS = 'headers';
public const MODEL_SPECS = 'specs';
// Proxy
public const MODEL_PROXY_RULE = 'proxyRule';
@@ -418,6 +420,7 @@ class Response extends SwooleResponse
->setModel(new Deployment())
->setModel(new Execution())
->setModel(new Build())
->setModel(new Specs())
->setModel(new Project())
->setModel(new Webhook())
->setModel(new Key())
@@ -152,6 +152,18 @@ class Func extends Model
'default' => false,
'example' => false,
])
->addRule('memory', [
'type' => self::TYPE_INTEGER,
'description' => 'Function execution memory limit in MB.',
'default' => 512,
'example' => 1024,
])
->addRule('cpus', [
'type' => self::TYPE_INTEGER,
'description' => 'Function execution CPU cores.',
'default' => 1,
'example' => 2,
])
;
}
@@ -0,0 +1,49 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Specs extends Model
{
public function __construct()
{
$this
->addRule('cpus', [
'type' => self::TYPE_INTEGER,
'description' => 'Amount of CPU cores available.',
'default' => '',
'example' => [1, 2, 4, 8],
'array' => true,
])
->addRule('memory', [
'type' => self::TYPE_INTEGER,
'description' => 'Amount of memory available in MB.',
'default' => '',
'example' => [512, 1024, 2048, 4096, 8192, 16384],
'array' => true,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Specs';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_SPECS;
}
}
+8 -8
View File
@@ -25,10 +25,6 @@ class Executor
protected array $headers;
protected int $cpus;
protected int $memory;
public function __construct(string $endpoint)
{
if (!filter_var($endpoint, FILTER_VALIDATE_URL)) {
@@ -66,6 +62,8 @@ class Executor
string $source,
string $image,
string $version,
int $cpus = 1,
int $memory = 512,
bool $remove = false,
string $entrypoint = '',
string $destination = '',
@@ -84,10 +82,10 @@ class Executor
'variables' => $variables,
'remove' => $remove,
'command' => $command,
'cpus' => $this->cpus,
'memory' => $this->memory,
'version' => $version,
'timeout' => $timeout,
'cpus' => $cpus,
'memory' => $memory,
];
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
@@ -178,6 +176,8 @@ class Executor
string $path,
string $method,
array $headers,
int $cpus,
int $memory,
string $runtimeEntrypoint = null,
bool $logging,
int $requestTimeout = null
@@ -198,8 +198,8 @@ class Executor
'image' => $image,
'source' => $source,
'entrypoint' => $entrypoint,
'cpus' => $this->cpus,
'memory' => $this->memory,
'cpus' => $cpus,
'memory' => $memory,
'version' => $version,
'runtimeEntrypoint' => $runtimeEntrypoint,
'logging' => $logging,
@@ -456,6 +456,91 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals('cli', $functionDetails['body']['type']);
}
/**
* @depends testUpdate
*/
public function testUpdateSpecs($data): array
{
/**
* Test for SUCCESS
*/
$response1 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Test1',
'events' => [
'users.*.update.name',
'users.*.update.email',
],
'schedule' => '0 0 1 1 *',
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'memory' => 1024,
'cpus' => 2
]);
$this->assertEquals(200, $response1['headers']['status-code']);
$this->assertNotEmpty($response1['body']['$id']);
$this->assertEquals('Test1', $response1['body']['name']);
$dateValidator = new DatetimeValidator();
$this->assertEquals(true, $dateValidator->isValid($response1['body']['$createdAt']));
$this->assertEquals(true, $dateValidator->isValid($response1['body']['$updatedAt']));
$this->assertEquals('', $response1['body']['deployment']);
$this->assertEquals([
'users.*.update.name',
'users.*.update.email',
], $response1['body']['events']);
$this->assertEquals('0 0 1 1 *', $response1['body']['schedule']);
$this->assertEquals(15, $response1['body']['timeout']);
$this->assertEquals(1024, $response1['body']['memory']);
$this->assertEquals(2, $response1['body']['cpus']);
/**
* Test for FAILURE
*/
$response2 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Test1',
'events' => [
'users.*.update.name',
'users.*.update.email',
],
'schedule' => '0 0 1 1 *',
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'memory' => 9999,
'cpus' => 4
]);
$this->assertEquals(400, $response2['headers']['status-code']);
$response3 = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Test1',
'events' => [
'users.*.update.name',
'users.*.update.email',
],
'schedule' => '0 0 1 1 *',
'timeout' => 15,
'runtime' => 'php-8.0',
'entrypoint' => 'index.php',
'memory' => 3212,
'cpus' => 5
]);
$this->assertEquals(400, $response3['headers']['status-code']);
return $data;
}
/**
* @depends testUpdate
*/