Refactor VM provisioning pipeline into focused services and add IP pool

management
This commit is contained in:
baodev0
2026-05-29 12:03:35 +07:00
parent 6bb4a47bfb
commit 7ae237ee9d
32 changed files with 1170 additions and 1200 deletions
@@ -12,10 +12,23 @@ return new class extends Migration {
{
Schema::create('ip_pools', function (Blueprint $table) {
$table->id();
$table->uuid('uuid')->unique();
$table->string('ip_address');
$table->unsignedBigInteger('module_network_id');
$table->foreign('module_network_id')->references('id')->on('module_network');
$table->enum('status', ['free', 'assigned', 'reserved'])->default('free');
$table->unsignedBigInteger('service_id')->nullable();
$table->timestamps();
$table->softDeletes();
$table->index(['module_network_id', 'status']);
$table->index('service_id');
});
}
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
if (!Schema::hasColumn('products', 'thumbnail')) {
Schema::table('products', function (Blueprint $table) {
$table->string('thumbnail')->nullable()->after('description');
});
}
}
public function down(): void
{
// The base create_products migration already defines this nullable column.
// Keep rollback as a no-op so rolling back this compatibility migration
// cannot remove an existing column from fresh databases.
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
return new class extends Migration {
public function up(): void
{
Schema::table('ip_pools', function (Blueprint $table) {
$table->uuid('uuid')->nullable()->unique()->after('id');
});
// Backfill existing rows
DB::table('ip_pools')->whereNull('uuid')->orderBy('id')->each(function ($row) {
DB::table('ip_pools')->where('id', $row->id)->update(['uuid' => (string) Str::uuid()]);
});
}
public function down(): void
{
Schema::table('ip_pools', function (Blueprint $table) {
$table->dropColumn('uuid');
});
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::table('virtual_machines', function (Blueprint $table) {
$table->unsignedBigInteger('ip_pool_id')->nullable()->after('host_id');
$table->string('ip', 45)->nullable()->after('ip_pool_id');
$table->foreign('ip_pool_id')
->references('id')
->on('ip_pools')
->nullOnDelete();
});
}
public function down(): void
{
Schema::table('virtual_machines', function (Blueprint $table) {
$table->dropForeign(['ip_pool_id']);
$table->dropColumn(['ip_pool_id', 'ip']);
});
}
};
@@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ip_pools', function (Blueprint $table) {
$table->string('gateway')->nullable()->after('ip_address');
$table->unsignedTinyInteger('cidr')->default(24)->after('gateway');
$table
->enum('status', ['free', 'assigned', 'reserved'])
->default('free')
->after('module_network_id');
$table->foreignId('service_id')->nullable()->after('status')->constrained('services')->nullOnDelete();
$table->index(['status', 'module_network_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('ip_pools', function (Blueprint $table) {
$table->dropForeign(['service_id']);
$table->dropIndex(['status', 'module_network_id']);
$table->dropColumn(['gateway', 'cidr', 'status', 'service_id']);
});
}
};
+44 -6
View File
@@ -15,7 +15,7 @@ use Illuminate\Support\Facades\Log;
* Cách cấu hình pools() bên dưới:
*
* // Theo CIDR:
* ['network' => 'LAN-VPS-01', 'cidr' => '103.90.224.0/26']
* ['network' => 'LAN-VPS-01', 'cidr' => '103.90.224.0/26', 'gateway' => '103.90.224.1']
*
* // Theo range:
* ['network' => 'LAN-VPS-01', 'start' => '103.90.224.10', 'end' => '103.90.224.60']
@@ -26,7 +26,7 @@ use Illuminate\Support\Facades\Log;
* // Có thể dùng module_network_id thay vì tên network:
* ['network_id' => 1, 'cidr' => '103.90.224.0/26']
*
* IP đầu (network address) và IP cuối (broadcast) trong CIDR tự động bị loại bỏ.
* IP đầu (network address), IP cuối (broadcast), và gateway trong CIDR tự động bị loại bỏ.
* Khi VM được tạo, hệ thống sẽ lấy IP đầu tiên chưa được assign trong module_ip_address.
*/
class IpPoolSeeder extends Seeder
@@ -42,7 +42,8 @@ class IpPoolSeeder extends Seeder
// --- Pool 1: VPS Public Network (module_network id=1, name="VM Network") ---
[
'network_id' => 1, // module_network.id
'cidr' => '103.90.224.0/26', // /26 = 62 usable IPs (.1 → .62)
'cidr' => '103.90.224.0/26', // /26, gateway .1, cấp phát .2 → .62
'gateway' => '103.90.224.1',
],
// --- Pool 2: thêm range thủ công ---
@@ -91,6 +92,10 @@ class IpPoolSeeder extends Seeder
[
'ip_address' => $ip,
'module_network_id' => $network->id,
],
[
'gateway' => $pool['gateway'] ?? null,
'cidr' => $this->resolveCidr($pool),
]
);
@@ -130,20 +135,53 @@ class IpPoolSeeder extends Seeder
private function generateIps(array $pool): array
{
if (!empty($pool['ips'])) {
return array_values(array_filter($pool['ips'], fn($ip) => filter_var($ip, FILTER_VALIDATE_IP)));
return $this->withoutGateway(
array_values(array_filter($pool['ips'], fn($ip) => filter_var($ip, FILTER_VALIDATE_IP))),
$pool
);
}
if (!empty($pool['cidr'])) {
return $this->ipsFromCidr($pool['cidr']);
return $this->withoutGateway($this->ipsFromCidr($pool['cidr']), $pool);
}
if (!empty($pool['start']) && !empty($pool['end'])) {
return $this->ipsFromRange($pool['start'], $pool['end']);
return $this->withoutGateway($this->ipsFromRange($pool['start'], $pool['end']), $pool);
}
return [];
}
private function resolveCidr(array $pool): int
{
if (!empty($pool['prefix'])) {
return (int) $pool['prefix'];
}
if (!empty($pool['cidr']) && str_contains($pool['cidr'], '/')) {
return (int) explode('/', $pool['cidr'])[1];
}
return 24;
}
/**
* Không đưa gateway vào IP pool để tránh cấp gateway cho VM.
*
* @param string[] $ips
* @return string[]
*/
private function withoutGateway(array $ips, array $pool): array
{
$gateway = $pool['gateway'] ?? null;
if (!$gateway || !filter_var($gateway, FILTER_VALIDATE_IP)) {
return $ips;
}
return array_values(array_filter($ips, fn(string $ip): bool => $ip !== $gateway));
}
/**
* Lấy tất cả usable IPs từ CIDR (bỏ network address và broadcast).
* Ví dụ: 103.90.224.0/26 → .1 đến .62