Skip to content

Commit

Permalink
feat: #83 allow a node to join an existing cluster
Browse files Browse the repository at this point in the history
  • Loading branch information
bohdan-shulha committed Jul 30, 2024
1 parent 012785c commit 373e15d
Show file tree
Hide file tree
Showing 25 changed files with 380 additions and 32 deletions.
19 changes: 16 additions & 3 deletions api-nodes/Http/Controllers/EventController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,30 @@

namespace ApiNodes\Http\Controllers;

use ApiNodes\Models\AgentStartedEventData;
use App\Models\Node;
use App\Models\NodeData;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class EventController
{
public function started(Node $node, NodeData $data)
public function started(Node $node, AgentStartedEventData $data)
{
$node->data = $data;
DB::transaction(function () use ($node, $data) {
$node->data = $data->node;
$node->save();

$node->save();
if ($node->swarm && $data->swarm) {
$swarm = $node->swarm;

$swarm->data->joinTokens = $data->swarm->joinTokens;
$swarm->data->managerNodes = $data->swarm->managerNodes;

$swarm->save();
}
});

return [
'settings' => [
Expand Down
18 changes: 18 additions & 0 deletions api-nodes/Models/AgentStartedEventData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace ApiNodes\Models;

use ApiNodes\Models\AgentStartedEventData\SwarmData;
use App\Models\NodeData;
use Spatie\LaravelData\Data;

class AgentStartedEventData extends Data
{
public function __construct(
public NodeData $node,
public ?SwarmData $swarm,
)
{

}
}
21 changes: 21 additions & 0 deletions api-nodes/Models/AgentStartedEventData/SwarmData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace ApiNodes\Models\AgentStartedEventData;

use App\Models\SwarmData\JoinTokens;
use App\Models\SwarmData\ManagerNode;
use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;

class SwarmData extends Data
{
public function __construct(
public JoinTokens $joinTokens,
#[DataCollectionOf(ManagerNode::class)]
/* @var ManagerNode[] */
public array $managerNodes,
)
{

}
}
9 changes: 9 additions & 0 deletions app/Events/NodeTasks/JoinSwarm/JoinSwarmCompleted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Events\NodeTasks\JoinSwarm;

use App\Events\NodeTasks\BaseTaskEvent;

class JoinSwarmCompleted extends BaseTaskEvent
{
}
9 changes: 9 additions & 0 deletions app/Events/NodeTasks/JoinSwarm/JoinSwarmFailed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Events\NodeTasks\JoinSwarm;

use App\Events\NodeTasks\BaseTaskEvent;

class JoinSwarmFailed extends BaseTaskEvent
{
}
9 changes: 8 additions & 1 deletion app/Http/Controllers/NodeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Models\Node;
use App\Models\NodeTaskGroupType;
use App\Models\Service;
use App\Models\Swarm;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Inertia\Inertia;
Expand Down Expand Up @@ -54,6 +55,11 @@ public function show(Node $node)
$initTaskGroup = null;
}

$joinTaskGroup = $node->actualTaskGroup(NodeTaskGroupType::JoinSwarm);
if ($joinTaskGroup?->is_completed) {
$joinTaskGroup = null;
}

$lastAgentVersion = AgentRelease::latest()->first()?->tag_name;

$taskGroup = $node->actualTaskGroup(NodeTaskGroupType::SelfUpgrade);
Expand All @@ -67,8 +73,9 @@ public function show(Node $node)

return Inertia::render('Nodes/Show', [
'node' => $node,
'swarms' => Swarm::all(),
'isLastNode' => $node->team->nodes->count() === 1,
'initTaskGroup' => $initTaskGroup,
'initTaskGroup' => $initTaskGroup ?: $joinTaskGroup,
'lastAgentVersion' => $lastAgentVersion,
'agentUpgradeTaskGroup' => $taskGroup?->is_completed ? null : $taskGroup,
'registryUpdateTaskGroup' => $registryTaskGroup?->is_completed ? null : $registryTaskGroup
Expand Down
41 changes: 41 additions & 0 deletions app/Http/Controllers/SwarmTaskController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Http\Controllers;

use App\Http\Requests\NodeTask\InitClusterFormRequest;
use App\Http\Requests\NodeTask\JoinClusterFormRequest;
use App\Models\Deployment;
use App\Models\DeploymentData;
use App\Models\DeploymentData\LaunchMode;
Expand All @@ -13,6 +14,7 @@
use App\Models\NodeTaskGroupType;
use App\Models\NodeTasks\CreateNetwork\CreateNetworkMeta;
use App\Models\NodeTasks\InitSwarm\InitSwarmMeta;
use App\Models\NodeTasks\JoinSwarm\JoinSwarmMeta;
use App\Models\NodeTasks\UpdateCurrentNode\UpdateCurrentNodeMeta;
use App\Models\NodeTaskType;
use App\Models\Service;
Expand Down Expand Up @@ -201,4 +203,43 @@ public function initCluster(InitClusterFormRequest $request)
$taskGroup->tasks()->createMany($tasks);
});
}

public function joinCluster(JoinClusterFormRequest $request)
{
DB::transaction(function () use ($request) {
$taskGroup = NodeTaskGroup::create([
'type' => NodeTaskGroupType::JoinSwarm,
'swarm_id' => $request->swarm_id,
'team_id' => auth()->user()->current_team_id,
'node_id' => $request->node_id,
'invoker_id' => auth()->user()->id,
]);

$node = Node::findOrFail($request->node_id);
$node->swarm_id = $request->swarm_id;
$node->save();

$remoteAddrs = collect($taskGroup->swarm->data->managerNodes)->map(fn(SwarmData\ManagerNode $node) => $node->addr)->toArray();

$joinToken = match ($request->role) {
'manager' => $taskGroup->swarm->data->joinTokens->manager,
'worker' => $taskGroup->swarm->data->joinTokens->worker,
};

$taskGroup->tasks()->create([
'type' => NodeTaskType::JoinSwarm,
'meta' => JoinSwarmMeta::from(['swarmId' => $request->swarm_id, 'role' => $request->role]),
'payload' => [
'JoinSpec' => [
'ListenAddr' => '0.0.0.0:2377',
'AdvertiseAddr' => $request->advertise_addr,
'DataPathAddr' => $request->advertise_addr,
'RemoteAddrs' => $remoteAddrs,
'JoinToken' => $joinToken,
'Availability' => 'active',
],
]
]);
});
}
}
2 changes: 1 addition & 1 deletion app/Http/Requests/NodeTask/InitClusterFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public function authorize(): bool
public function rules(): array
{
return [
'node_id' => ['required'],
'node_id' => ['required', 'exists:nodes,id'],
'name' => ['required', 'string', 'max:255'],
'advertise_addr' => ['required', 'ipv4'],
'force_new_cluster' => ['boolean'],
Expand Down
23 changes: 23 additions & 0 deletions app/Http/Requests/NodeTask/JoinClusterFormRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Http\Requests\NodeTask;

use Illuminate\Foundation\Http\FormRequest;

class JoinClusterFormRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}

public function rules(): array
{
return [
'node_id' => ['required', 'exists:nodes,id'],
'swarm_id' => ['required', 'exists:swarms,id'],
'role' => ['required', 'in:manager,worker'],
'advertise_addr' => ['exclude_if:role,worker', 'required', 'ipv4'],
];
}
}
17 changes: 12 additions & 5 deletions app/Models/DeploymentData/Process.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,18 @@ public function asNodeTasks(Deployment $deployment): array
$args = array_slice($splitCmd, 1);
}

$dockerRegistry = $deployment->service->swarm->data->findRegistry($this->dockerRegistryId);
if ($dockerRegistry === null) {
$dockerRegistry = $this->dockerRegistryId
? $deployment->service->swarm->data->findRegistry($this->dockerRegistryId)
: null;

if ($this->dockerRegistryId && is_null($dockerRegistry)) {
throw new Exception("Docker registry '{$this->dockerRegistryId}' not found");
}

$authConfigName = $dockerRegistry
? $dockerRegistry->dockerName
: "";

$tasks[] = [
'type' => NodeTaskType::PullDockerImage,
'meta' => PullDockerImageMeta::from([
Expand All @@ -217,7 +224,7 @@ public function asNodeTasks(Deployment $deployment): array
'dockerImage' => $this->dockerImage,
]),
'payload' => [
'AuthConfigName' => $dockerRegistry->dockerName,
'AuthConfigName' => $authConfigName,
'Image' => $this->dockerImage,
'PullOptions' => (object) [],
],
Expand Down Expand Up @@ -285,7 +292,7 @@ public function asNodeTasks(Deployment $deployment): array
'type' => $actionUpdate ? NodeTaskType::UpdateService : NodeTaskType::CreateService,
'meta' => $actionUpdate ? UpdateServiceMeta::from($serviceTaskMeta) : CreateServiceMeta::from($serviceTaskMeta),
'payload' => [
'AuthConfigName' => $dockerRegistry->dockerName,
'AuthConfigName' => $authConfigName,
'ReleaseCommand' => $this->getReleaseCommandPayload($deployment, $labels),
'SecretVars' => (object) $this->getSecretVars($previous, $labels),
'SwarmServiceSpec' => [
Expand Down Expand Up @@ -365,7 +372,7 @@ public function asNodeTasks(Deployment $deployment): array
'type' => $actionUpdate ? NodeTaskType::UpdateService : NodeTaskType::CreateService,
'meta' => $actionUpdate ? UpdateServiceMeta::from($workerTaskMeta) : CreateServiceMeta::from($workerTaskMeta),
'payload' => [
'AuthConfigName' => $dockerRegistry->dockerName,
'AuthConfigName' => $authConfigName,
'ReleaseCommand' => (object) [],
'SecretVars' => (object) $this->getWorkerSecretVars($worker, $labels),
'SwarmServiceSpec' => [
Expand Down
6 changes: 5 additions & 1 deletion app/Models/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ protected static function booted(): void
$subscription->stopCancelation();
}

$subscription->updateQuantity($nodesCount);
if ($subscription->onTrial()) {
$subscription->doNotBill()->updateQuantity($nodesCount);
} else {
$subscription->updateQuantity($nodesCount);
}
}
});
}
Expand Down
4 changes: 3 additions & 1 deletion app/Models/NodeData.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

use App\Models\NodeData\DockerData;
use App\Models\NodeData\HostData;
use App\Models\NodeData\NodeRole;
use Spatie\LaravelData\Data;

class NodeData extends Data {
public function __construct(
public string $version,
public DockerData $docker,
public HostData $host
public HostData $host,
public NodeRole $role,
) {

}
Expand Down
9 changes: 9 additions & 0 deletions app/Models/NodeData/NodeRole.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Models\NodeData;

enum NodeRole: string
{
case Manager = 'manager';
case Worker = 'worker';
}
1 change: 1 addition & 0 deletions app/Models/NodeTaskGroupType.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ enum NodeTaskGroupType: int
case UpdateDockerRegistries = 5;
case UpdateS3Storages = 6;
case CreateBackup = 7;
case JoinSwarm = 8;
}
17 changes: 13 additions & 4 deletions app/Models/NodeTaskType.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
use App\Events\NodeTasks\DownloadAgentUpgrade\DownloadAgentUpgradeFailed;
use App\Events\NodeTasks\InitSwarm\InitSwarmCompleted;
use App\Events\NodeTasks\InitSwarm\InitSwarmFailed;
use App\Events\NodeTasks\JoinSwarm\JoinSwarmCompleted;
use App\Events\NodeTasks\JoinSwarm\JoinSwarmFailed;
use App\Events\NodeTasks\PullDockerImage\PullDockerImageCompleted;
use App\Events\NodeTasks\PullDockerImage\PullDockerImageFailed;
use App\Events\NodeTasks\RebuildCaddyConfig\ApplyCaddyConfigCompleted;
Expand Down Expand Up @@ -66,6 +68,8 @@
use App\Models\NodeTasks\DownloadAgentUpgrade\DownloadAgentUpgradeResult;
use App\Models\NodeTasks\InitSwarm\InitSwarmMeta;
use App\Models\NodeTasks\InitSwarm\InitSwarmResult;
use App\Models\NodeTasks\JoinSwarm\JoinSwarmMeta;
use App\Models\NodeTasks\JoinSwarm\JoinSwarmResult;
use App\Models\NodeTasks\PullDockerImage\PullDockerImageMeta;
use App\Models\NodeTasks\PullDockerImage\PullDockerImageResult;
use App\Models\NodeTasks\ServiceExec\ServiceExecMeta;
Expand Down Expand Up @@ -101,6 +105,7 @@ enum NodeTaskType: int
case CheckS3Storage = 16;
case ServiceExec = 17;
case UploadS3File = 18;
case JoinSwarm = 19;

public function meta(): string
{
Expand All @@ -123,7 +128,8 @@ public function meta(): string
self::CreateS3Storage => CreateS3StorageMeta::class,
self::CheckS3Storage => CheckS3StorageMeta::class,
self::ServiceExec => ServiceExecMeta::class,
self::UploadS3File => UploadS3FileMeta::class
self::UploadS3File => UploadS3FileMeta::class,
self::JoinSwarm => JoinSwarmMeta::class,
};
}

Expand All @@ -148,7 +154,8 @@ public function result(): string
self::CreateS3Storage => CreateS3StorageResult::class,
self::CheckS3Storage => CheckS3StorageResult::class,
self::ServiceExec => ServiceExecResult::class,
self::UploadS3File => UploadS3FileResult::class
self::UploadS3File => UploadS3FileResult::class,
self::JoinSwarm => JoinSwarmResult::class,
};
}

Expand All @@ -173,7 +180,8 @@ public function completed(): string
self::CreateS3Storage => CreateS3StorageCompleted::class,
self::CheckS3Storage => CheckS3StorageCompleted::class,
self::ServiceExec => ServiceExecCompleted::class,
self::UploadS3File => UploadS3FileCompleted::class
self::UploadS3File => UploadS3FileCompleted::class,
self::JoinSwarm => JoinSwarmCompleted::class,
};
}

Expand All @@ -198,7 +206,8 @@ public function failed(): string
self::CreateS3Storage => CreateS3StorageFailed::class,
self::CheckS3Storage => CheckS3StorageFailed::class,
self::ServiceExec => ServiceExecFailed::class,
self::UploadS3File => UploadS3FileFailed::class
self::UploadS3File => UploadS3FileFailed::class,
self::JoinSwarm => JoinSwarmFailed::class,
};
}
}
Loading

0 comments on commit 373e15d

Please sign in to comment.