update
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\VirtualMachine;
|
||||
|
||||
use App\Models\ModuleDatacenter;
|
||||
use App\Models\ModuleDatastore;
|
||||
use App\Models\ModuleHost;
|
||||
use App\Models\ModuleNetwork;
|
||||
use App\Models\vCenter;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Single source of truth for the "active" provisioning chain:
|
||||
*
|
||||
* vCenter (status=1, global)
|
||||
* └── ModuleDatacenter (status=1 per vCenter)
|
||||
* └── ModuleHost (status=1 per datacenter)
|
||||
* ├── ModuleDatastore (status=1 per host)
|
||||
* └── ModuleNetwork (status=1 per host)
|
||||
*
|
||||
* Rules enforced here:
|
||||
* - At most ONE row per parent scope may have status=1.
|
||||
* - Setting status=1 on row X auto-deactivates all siblings in same parent.
|
||||
* - Setting status=0 on row X cascade-deactivates ALL descendants below it.
|
||||
* - resolveChain() returns the full active chain or throws if any layer is missing.
|
||||
*
|
||||
* Settings provider mirroring:
|
||||
* - activate('vcenter', $id) also writes provider.active_vcenter_id into the
|
||||
* setting table so the Settings → Provider page reflects the same value
|
||||
* without owning the truth.
|
||||
*/
|
||||
class VmActiveProviderService
|
||||
{
|
||||
public const TYPE_VCENTER = 'vcenter';
|
||||
public const TYPE_DATACENTER = 'datacenter';
|
||||
public const TYPE_HOST = 'host';
|
||||
public const TYPE_DATASTORE = 'datastore';
|
||||
public const TYPE_NETWORK = 'network';
|
||||
|
||||
/** type → [modelClass, parentColumn|null (null = global), childTypes[]] */
|
||||
private const MAP = [
|
||||
self::TYPE_VCENTER => [vCenter::class, null, [self::TYPE_DATACENTER]],
|
||||
self::TYPE_DATACENTER => [ModuleDatacenter::class, 'vcenter_id', [self::TYPE_HOST]],
|
||||
self::TYPE_HOST => [ModuleHost::class, 'module_datacenter_id', [self::TYPE_DATASTORE, self::TYPE_NETWORK]],
|
||||
self::TYPE_DATASTORE => [ModuleDatastore::class, 'module_host_id', []],
|
||||
self::TYPE_NETWORK => [ModuleNetwork::class, 'module_host_id', []],
|
||||
];
|
||||
|
||||
public function __construct(private readonly VmProviderSettingsService $providerSettings)
|
||||
{
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Resolve helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public function activeVcenter(): ?vCenter
|
||||
{
|
||||
return vCenter::where('status', 1)->first();
|
||||
}
|
||||
|
||||
public function activeDatacenter(int $vcenterId): ?ModuleDatacenter
|
||||
{
|
||||
return ModuleDatacenter::where('vcenter_id', $vcenterId)
|
||||
->where('status', 1)
|
||||
->first();
|
||||
}
|
||||
|
||||
public function activeHost(int $datacenterId): ?ModuleHost
|
||||
{
|
||||
return ModuleHost::where('module_datacenter_id', $datacenterId)
|
||||
->where('status', 1)
|
||||
->first();
|
||||
}
|
||||
|
||||
public function activeDatastore(int $hostId): ?ModuleDatastore
|
||||
{
|
||||
return ModuleDatastore::where('module_host_id', $hostId)
|
||||
->where('status', 1)
|
||||
->first();
|
||||
}
|
||||
|
||||
public function activeNetwork(int $hostId): ?ModuleNetwork
|
||||
{
|
||||
return ModuleNetwork::where('module_host_id', $hostId)
|
||||
->where('status', 1)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full active chain. Throws if any layer has no active row.
|
||||
*
|
||||
* @return array{vcenter:vCenter,datacenter:ModuleDatacenter,host:ModuleHost,datastore:ModuleDatastore,network:ModuleNetwork}
|
||||
*/
|
||||
public function resolveChain(): array
|
||||
{
|
||||
$vcenter = $this->activeVcenter();
|
||||
if (!$vcenter) {
|
||||
throw new RuntimeException('Không có vCenter nào đang active. Vui lòng kích hoạt 1 vCenter trong admin.');
|
||||
}
|
||||
|
||||
$datacenter = $this->activeDatacenter($vcenter->id);
|
||||
if (!$datacenter) {
|
||||
throw new RuntimeException("vCenter active id={$vcenter->id} chưa có Datacenter nào active.");
|
||||
}
|
||||
|
||||
$host = $this->activeHost($datacenter->id);
|
||||
if (!$host) {
|
||||
throw new RuntimeException("Datacenter active id={$datacenter->id} chưa có Host nào active.");
|
||||
}
|
||||
|
||||
$datastore = $this->activeDatastore($host->id);
|
||||
if (!$datastore) {
|
||||
throw new RuntimeException("Host active id={$host->id} chưa có Datastore nào active.");
|
||||
}
|
||||
|
||||
$network = $this->activeNetwork($host->id);
|
||||
if (!$network) {
|
||||
throw new RuntimeException("Host active id={$host->id} chưa có Network nào active.");
|
||||
}
|
||||
|
||||
return compact('vcenter', 'datacenter', 'host', 'datastore', 'network');
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Mutators
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Set this row active, deactivating all siblings in the same parent scope.
|
||||
*/
|
||||
public function activate(string $type, int $id): Model
|
||||
{
|
||||
[$modelClass, $parentColumn] = $this->mapOrFail($type);
|
||||
|
||||
return DB::transaction(function () use ($modelClass, $parentColumn, $type, $id) {
|
||||
/** @var Model $row */
|
||||
$row = $modelClass::query()->lockForUpdate()->findOrFail($id);
|
||||
|
||||
// Deactivate siblings under same parent (or all rows if global scope)
|
||||
$siblings = $modelClass::query()->where('id', '!=', $row->getKey());
|
||||
if ($parentColumn) {
|
||||
$siblings->where($parentColumn, $row->{$parentColumn});
|
||||
}
|
||||
$siblings->update(['status' => 0]);
|
||||
|
||||
$row->status = 1;
|
||||
$row->save();
|
||||
|
||||
if ($type === self::TYPE_VCENTER) {
|
||||
$this->providerSettings->setActiveVcenterId((int) $row->getKey());
|
||||
}
|
||||
|
||||
return $row->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this row inactive AND cascade-deactivate every descendant beneath it.
|
||||
*/
|
||||
public function deactivate(string $type, int $id): Model
|
||||
{
|
||||
[$modelClass] = $this->mapOrFail($type);
|
||||
|
||||
return DB::transaction(function () use ($modelClass, $type, $id) {
|
||||
/** @var Model $row */
|
||||
$row = $modelClass::query()->lockForUpdate()->findOrFail($id);
|
||||
|
||||
$row->status = 0;
|
||||
$row->save();
|
||||
|
||||
$this->cascadeDeactivate($type, (int) $row->getKey());
|
||||
|
||||
if ($type === self::TYPE_VCENTER) {
|
||||
$this->providerSettings->setActiveVcenterId(null);
|
||||
}
|
||||
|
||||
return $row->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively set status=0 on every descendant of $parentId under $type.
|
||||
*/
|
||||
private function cascadeDeactivate(string $type, int $parentId): void
|
||||
{
|
||||
$childTypes = self::MAP[$type][2] ?? [];
|
||||
|
||||
foreach ($childTypes as $childType) {
|
||||
[$childModel, $childParentColumn] = $this->mapOrFail($childType);
|
||||
if (!$childParentColumn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$childIds = $childModel::query()
|
||||
->where($childParentColumn, $parentId)
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
if (!$childIds) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$childModel::query()->whereIn('id', $childIds)->update(['status' => 0]);
|
||||
|
||||
foreach ($childIds as $childId) {
|
||||
$this->cascadeDeactivate($childType, (int) $childId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:class-string<Model>,1:?string,2:string[]}
|
||||
*/
|
||||
private function mapOrFail(string $type): array
|
||||
{
|
||||
if (!isset(self::MAP[$type])) {
|
||||
throw new RuntimeException("Unknown infra type: {$type}");
|
||||
}
|
||||
return self::MAP[$type];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user