Skip to content

Commit 53fc97f

Browse files
authored
Merge pull request #210 from open-runtimes/refactor-network-manager
refactor: network manager
2 parents 2b6c57c + 9427aad commit 53fc97f

File tree

7 files changed

+143
-113
lines changed

7 files changed

+143
-113
lines changed

.github/workflows/tests.yml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
name: "Tests"
22

33
on: [pull_request]
4+
5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
49
jobs:
510
unit-tests:
611
name: Unit Tests
712
runs-on: ubuntu-latest
813

914
steps:
1015
- name: Check out the repo
11-
uses: actions/checkout@v2
16+
uses: actions/checkout@v4
1217

1318
- name: Run Unit Tests
1419
run: |
@@ -29,18 +34,12 @@ jobs:
2934

3035
steps:
3136
- name: Check out the repo
32-
uses: actions/checkout@v2
37+
uses: actions/checkout@v4
3338

3439
- name: Start Test Stack
3540
run: |
36-
export COMPOSE_INTERACTIVE_NO_CLI
37-
export DOCKER_BUILDKIT=1
38-
export COMPOSE_DOCKER_CLI_BUILD=1
39-
export BUILDKIT_PROGRESS=plain
40-
docker pull composer:2.0
4141
docker compose build
42-
docker compose up -d
43-
sleep 60
42+
docker compose up -d --wait --wait-timeout 300
4443
4544
- name: Doctor
4645
run: |

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ COPY ./src /usr/local/src
2222
# Extensions and libraries
2323
COPY --from=composer /usr/local/src/vendor /usr/local/vendor
2424

25-
HEALTHCHECK --interval=30s --timeout=15s --start-period=60s --retries=3 CMD curl -s -H "Authorization: Bearer ${OPR_EXECUTOR_SECRET}" --fail http://127.0.0.1:80/v1/health
25+
HEALTHCHECK --interval=30s --timeout=15s --start-period=60s --retries=3 CMD curl -sf http://127.0.0.1:80/v1/health
2626

2727
CMD [ "php", "app/http.php" ]

app/controllers.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,6 @@ function (
308308
);
309309

310310
Http::get('/v1/health')
311-
->groups(['api'])
312311
->desc("Get health status of host machine and runtimes.")
313312
->inject('runner')
314313
->inject('response')

app/http.php

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use OpenRuntimes\Executor\Runner\Docker;
1111
use OpenRuntimes\Executor\Runner\Repository\Runtimes;
12+
use OpenRuntimes\Executor\Runner\NetworkManager;
1213
use Swoole\Runtime;
1314
use Utopia\Console;
1415
use Utopia\Http\Http;
@@ -23,6 +24,12 @@
2324
// Unlimited memory limit to handle as many coroutines/requests as possible
2425
ini_set('memory_limit', '-1');
2526

27+
$payloadSize = 22 * (1024 * 1024);
28+
$settings = [
29+
'package_max_length' => $payloadSize,
30+
'buffer_output_size' => $payloadSize,
31+
];
32+
2633
Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
2734

2835
Http::setMode((string)System::getEnv('OPR_EXECUTOR_ENV', Http::MODE_TYPE_PRODUCTION));
@@ -33,22 +40,25 @@
3340
$response->addHeader('Server', 'Executor');
3441
});
3542

36-
37-
run(function () {
43+
run(function () use ($settings) {
3844
$orchestration = new Orchestration(new DockerAPI(
3945
System::getEnv('OPR_EXECUTOR_DOCKER_HUB_USERNAME', ''),
4046
System::getEnv('OPR_EXECUTOR_DOCKER_HUB_PASSWORD', '')
4147
));
48+
49+
/* Create desired networks if they don't exist */
4250
$networks = explode(',', System::getEnv('OPR_EXECUTOR_NETWORK') ?: 'openruntimes-runtimes');
43-
$runner = new Docker($orchestration, new Runtimes(), $networks);
51+
$networkManager = new NetworkManager($orchestration, $networks);
4452

45-
Http::setResource('runner', fn () => $runner);
53+
/* Add the current executor to the networks */
54+
$hostname = gethostname() ?: throw new \RuntimeException('Could not determine hostname');
55+
$selfContainer = $orchestration->list(['name' => $hostname])[0] ?? throw new \RuntimeException('Own container not found');
56+
$networkManager->connectAll($selfContainer);
4657

47-
$payloadSize = 22 * (1024 * 1024);
48-
$settings = [
49-
'package_max_length' => $payloadSize,
50-
'buffer_output_size' => $payloadSize,
51-
];
58+
/* Runner service, used to manage runtimes */
59+
$runtimes = new Runtimes();
60+
$runner = new Docker($orchestration, $runtimes, $networkManager);
61+
Http::setResource('runner', fn () => $runner);
5262

5363
$server = new Server('0.0.0.0', '80', $settings);
5464
$http = new Http($server, 'UTC');

docker-compose.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ services:
2121
- runtimes
2222
ports:
2323
- 9900:80
24+
healthcheck:
25+
test: ["CMD", "curl", "-sf", "http://localhost:80/v1/health"]
26+
interval: 10s
27+
timeout: 5s
28+
retries: 12
29+
start_period: 120s
2430
volumes:
2531
- /var/run/docker.sock:/var/run/docker.sock
2632
- ./app:/usr/local/app:rw

src/Executor/Runner/Docker.php

Lines changed: 11 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -27,46 +27,27 @@
2727
class Docker extends Adapter
2828
{
2929
private Stats $stats;
30-
/**
31-
* @var string[]
32-
*/
33-
private array $networks;
3430

3531
/**
3632
* @param Orchestration $orchestration
3733
* @param Runtimes $runtimes
38-
* @param string[] $networks
34+
* @param NetworkManager $networkManager
3935
*/
4036
public function __construct(
4137
private readonly Orchestration $orchestration,
4238
private readonly Runtimes $runtimes,
43-
array $networks
39+
private readonly NetworkManager $networkManager
4440
) {
4541
$this->stats = new Stats();
46-
$this->init($networks);
42+
$this->init();
4743
}
4844

4945
/**
50-
* @param string[] $networks
5146
* @return void
5247
* @throws \Utopia\Http\Exception
5348
*/
54-
private function init(array $networks): void
49+
private function init(): void
5550
{
56-
/*
57-
* Remove residual runtimes and networks
58-
*/
59-
Console::info('Removing orphan runtimes and networks...');
60-
$this->cleanUp();
61-
Console::success("Orphan runtimes and networks removal finished.");
62-
63-
/**
64-
* Create and store Docker Bridge networks used for communication between executor and runtimes
65-
*/
66-
Console::info('Creating networks...');
67-
$createdNetworks = $this->createNetworks($networks);
68-
$this->networks = $createdNetworks;
69-
7051
/**
7152
* Warmup: make sure images are ready to run fast 🚀
7253
*/
@@ -170,10 +151,10 @@ private function init(array $networks): void
170151

171152
Console::success('Stats interval started.');
172153

173-
Process::signal(SIGINT, fn () => $this->cleanUp($this->networks));
174-
Process::signal(SIGQUIT, fn () => $this->cleanUp($this->networks));
175-
Process::signal(SIGKILL, fn () => $this->cleanUp($this->networks));
176-
Process::signal(SIGTERM, fn () => $this->cleanUp($this->networks));
154+
Process::signal(SIGINT, fn () => $this->cleanUp());
155+
Process::signal(SIGQUIT, fn () => $this->cleanUp());
156+
Process::signal(SIGKILL, fn () => $this->cleanUp());
157+
Process::signal(SIGTERM, fn () => $this->cleanUp());
177158
}
178159

179160
/**
@@ -485,7 +466,7 @@ public function createRuntime(
485466
$codeMountPath = $version === 'v2' ? '/usr/code' : '/mnt/code';
486467
$workdir = $version === 'v2' ? '/usr/code' : '';
487468

488-
$network = $this->networks[array_rand($this->networks)];
469+
$network = $this->networkManager->getAvailable()[array_rand($this->networkManager->getAvailable())];
489470

490471
$volumes = [
491472
\dirname($tmpSource) . ':/tmp:rw',
@@ -1199,10 +1180,9 @@ public function createExecution(
11991180
}
12001181

12011182
/**
1202-
* @param string[] $networks
12031183
* @return void
12041184
*/
1205-
private function cleanUp(array $networks = []): void
1185+
private function cleanUp(): void
12061186
{
12071187
Console::log('Cleaning up containers and networks...');
12081188

@@ -1233,73 +1213,11 @@ private function cleanUp(array $networks = []): void
12331213
}
12341214
batch($jobsRuntimes);
12351215

1236-
$jobsNetworks = [];
1237-
foreach ($networks as $network) {
1238-
$jobsNetworks[] = function () use ($network) {
1239-
try {
1240-
$this->orchestration->removeNetwork($network);
1241-
Console::success("Removed network: $network");
1242-
} catch (Exception $e) {
1243-
Console::error("Failed to remove network $network: " . $e->getMessage());
1244-
}
1245-
};
1246-
}
1247-
batch($jobsNetworks);
1216+
$this->networkManager->removeAll();
12481217

12491218
Console::success('Cleanup finished.');
12501219
}
12511220

1252-
/**
1253-
* @param string[] $networks
1254-
* @return string[]
1255-
*/
1256-
private function createNetworks(array $networks): array
1257-
{
1258-
$jobs = [];
1259-
$createdNetworks = [];
1260-
foreach ($networks as $network) {
1261-
$jobs[] = function () use ($network, &$createdNetworks) {
1262-
if (!$this->orchestration->networkExists($network)) {
1263-
try {
1264-
$this->orchestration->createNetwork($network, false);
1265-
Console::success("Created network: $network");
1266-
$createdNetworks[] = $network;
1267-
} catch (\Throwable $e) {
1268-
Console::error("Failed to create network $network: " . $e->getMessage());
1269-
}
1270-
} else {
1271-
Console::info("Network $network already exists");
1272-
$createdNetworks[] = $network;
1273-
}
1274-
};
1275-
}
1276-
batch($jobs);
1277-
1278-
$image = System::getEnv('OPR_EXECUTOR_IMAGE', '');
1279-
$containers = $this->orchestration->list(['label' => "com.openruntimes.executor.image=$image"]);
1280-
1281-
if (count($containers) < 1) {
1282-
$containerName = '';
1283-
Console::warning('No matching executor found. Please check the value of OPR_EXECUTOR_IMAGE. Executor will need to be connected to the runtime network manually.');
1284-
} else {
1285-
$containerName = $containers[0]->getName();
1286-
Console::success('Found matching executor. Executor will be connected to runtime network automatically.');
1287-
}
1288-
1289-
if (!empty($containerName)) {
1290-
foreach ($createdNetworks as $network) {
1291-
try {
1292-
$this->orchestration->networkConnect($containerName, $network);
1293-
Console::success("Successfully connected executor '$containerName' to network '$network'");
1294-
} catch (\Throwable $e) {
1295-
Console::error("Failed to connect executor '$containerName' to network '$network': " . $e->getMessage());
1296-
}
1297-
}
1298-
}
1299-
1300-
return $createdNetworks;
1301-
}
1302-
13031221
public function getRuntimes(): mixed
13041222
{
13051223
$runtimes = [];
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace OpenRuntimes\Executor\Runner;
4+
5+
use Utopia\Console;
6+
use Utopia\Orchestration\Container;
7+
use Utopia\Orchestration\Orchestration;
8+
9+
use function Swoole\Coroutine\batch;
10+
11+
class NetworkManager
12+
{
13+
/** @var string[] Networks available for use */
14+
private array $available = [];
15+
16+
/**
17+
* @param string[] $networks Networks to ensure exist
18+
*/
19+
public function __construct(
20+
private readonly Orchestration $orchestration,
21+
array $networks,
22+
) {
23+
if (empty($networks)) {
24+
return;
25+
}
26+
27+
$jobs = array_map(
28+
fn (string $network) => fn (): ?string => $this->ensure($network),
29+
$networks
30+
);
31+
32+
$this->available = array_values(array_filter(
33+
batch($jobs),
34+
fn ($v) => \is_string($v) && $v !== ''
35+
));
36+
}
37+
38+
/** @return string[] */
39+
public function getAvailable(): array
40+
{
41+
return $this->available;
42+
}
43+
44+
public function connectAll(Container $container): void
45+
{
46+
foreach ($this->available as $network) {
47+
try {
48+
$this->orchestration->networkConnect($container->getName(), $network);
49+
} catch (\Throwable) {
50+
// TODO: Orchestration library should throw a distinct exception for "already connected"
51+
}
52+
}
53+
}
54+
55+
public function removeAll(): void
56+
{
57+
if (empty($this->available)) {
58+
return;
59+
}
60+
61+
batch(array_map(
62+
fn ($network) => fn () => $this->remove($network),
63+
$this->available
64+
));
65+
}
66+
67+
private function remove(string $network): void
68+
{
69+
if (!$this->orchestration->networkExists($network)) {
70+
Console::error("Network {$network} does not exist");
71+
return;
72+
}
73+
74+
try {
75+
$this->orchestration->removeNetwork($network);
76+
Console::success("Removed network: {$network}");
77+
} catch (\Throwable $e) {
78+
Console::error("Failed to remove network {$network}: {$e->getMessage()}");
79+
}
80+
}
81+
82+
private function ensure(string $network): ?string
83+
{
84+
if ($this->orchestration->networkExists($network)) {
85+
Console::info("Network {$network} already exists");
86+
return $network;
87+
}
88+
89+
try {
90+
$this->orchestration->createNetwork($network, false);
91+
Console::success("Created network: {$network}");
92+
return $network;
93+
} catch (\Throwable $e) {
94+
Console::error("Failed to create network {$network}: {$e->getMessage()}");
95+
return null;
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)