diff --git a/config/api_platform/Client.yaml b/config/api_platform/Client.yaml index 6078953..f2322f6 100644 --- a/config/api_platform/Client.yaml +++ b/config/api_platform/Client.yaml @@ -52,15 +52,15 @@ resources: reboot_client: class: ApiPlatform\Metadata\Post method: POST - input: false - uriTemplate: /clients/server/{uuid}/reboot + input: App\Dto\Input\MultipleClientsInput + uriTemplate: /clients/server/reboot controller: App\Controller\OgAgent\RebootAction power_off_client: class: ApiPlatform\Metadata\Post method: POST - input: false - uriTemplate: /clients/server/{uuid}/power-off + input: App\Dto\Input\MultipleClientsInput + uriTemplate: /clients/server/power-off controller: App\Controller\OgAgent\PowerOffAction diff --git a/config/api_platform/ImageRepository.yaml b/config/api_platform/ImageRepository.yaml index f8879bb..d7b6e79 100644 --- a/config/api_platform/ImageRepository.yaml +++ b/config/api_platform/ImageRepository.yaml @@ -34,7 +34,7 @@ resources: class: ApiPlatform\Metadata\Post method: POST input: App\Dto\Input\WoLInput - uriTemplate: /image-repositories/{uuid}/wol + uriTemplate: /image-repositories/wol controller: App\Controller\OgRepository\WoLAction get_collection_images_ogrepository: @@ -55,6 +55,24 @@ resources: uriTemplate: /image-repositories/server/{uuid}/status controller: App\Controller\OgRepository\StatusAction + export_image_ogrepository: + shortName: OgRepository Server + description: Export Image in OgRepository + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\ExportImportImageRepositoryInput + uriTemplate: /image-repositories/{uuid}/export-image + controller: App\Controller\OgRepository\Image\ExportAction + + import_image_ogrepository: + shortName: OgRepository Server + description: Export Image in OgRepository + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\ExportImportImageRepositoryInput + uriTemplate: /image-repositories/{uuid}/import-image + controller: App\Controller\OgRepository\Image\ImportAction + properties: App\Entity\ImageRepository: id: diff --git a/config/services/api_platform.yaml b/config/services/api_platform.yaml index cc3b13b..528f3a1 100644 --- a/config/services/api_platform.yaml +++ b/config/services/api_platform.yaml @@ -178,6 +178,18 @@ services: arguments: [ { 'id': 'exact', 'name': 'partial', } ] tags: [ 'api_platform.filter' ] + api_platform.filter.repository.order: + parent: 'api_platform.doctrine.orm.order_filter' + arguments: + $properties: { 'id': ~, 'name': ~ } + $orderParameterName: 'order' + tags: [ 'api_platform.filter' ] + + api_platform.filter.repository.search: + parent: 'api_platform.doctrine.orm.search_filter' + arguments: [ { 'id': 'exact', 'name': 'partial'} ] + tags: [ 'api_platform.filter' ] + api_platform.filter.software.order: parent: 'api_platform.doctrine.orm.order_filter' arguments: diff --git a/migrations/Version20250203113932.php b/migrations/Version20250203113932.php new file mode 100644 index 0000000..f9e3246 --- /dev/null +++ b/migrations/Version20250203113932.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE image ADD is_global TINYINT(1) NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE image DROP is_global'); + } +} diff --git a/src/Controller/DeployImageAction.php b/src/Controller/DeployImageAction.php index 7b9778a..a48beec 100644 --- a/src/Controller/DeployImageAction.php +++ b/src/Controller/DeployImageAction.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Controller; +use ApiPlatform\Validator\ValidatorInterface; use App\Dto\Input\DeployImageInput; use App\Entity\Command; use App\Entity\Image; @@ -29,6 +30,7 @@ class DeployImageAction extends AbstractController protected readonly EntityManagerInterface $entityManager, protected readonly HttpClientInterface $httpClient, protected readonly CreateService $createService, + protected readonly ValidatorInterface $validator, public readonly \App\Controller\OgAgent\DeployImageAction $deployImageOgAgentAction, public readonly \App\Controller\OgRepository\Image\DeployImageAction $deployImageOgRepositoryAction, ) @@ -42,67 +44,78 @@ class DeployImageAction extends AbstractController */ public function __invoke(DeployImageInput $input, Image $image): JsonResponse { - /** @var Partition $partition */ - $partition = $input->partition->getEntity(); + $this->validator->validate($input); switch ($input->method){ case DeployMethodTypes::UNICAST: case DeployMethodTypes::UNICAST_DIRECT: - $inputData = [ - 'method' => $input->method, - 'client' => $input->client->getEntity()->getUuid(), - 'image' => $image->getUuid(), - 'numDisk' => (string) $partition->getDiskNumber(), - 'numPartition' => (string) $partition->getPartitionNumber(), - ]; + foreach ($input->clients as $client) { + $inputData = [ + 'method' => $input->method, + 'client' => $client->getEntity()->getUuid(), + 'image' => $image->getUuid(), + 'numDisk' => (string) $input->diskNumber, + 'numPartition' => (string) $input->partitionNumber, + ]; - $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, DeployMethodTypes::UNICAST); - $this->createService->__invoke($input->client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); + $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client->getEntity(), DeployMethodTypes::UNICAST); + $this->createService->__invoke($client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); + } + break; - break; case DeployMethodTypes::MULTICAST_UFTP: - case DeployMethodTypes::MULTICAST_UDPCAST: - case DeployMethodTypes::MULTICAST: case DeployMethodTypes::MULTICAST_UFTP_DIRECT: + case DeployMethodTypes::MULTICAST_UDPCAST: case DeployMethodTypes::MULTICAST_UDPCAST_DIRECT: + case DeployMethodTypes::MULTICAST: + foreach ($input->clients as $client) { + $inputData = [ + 'method' => $input->method, + 'client' => $client->getEntity()->getUuid(), + 'image' => $image->getUuid(), + 'mcastIp' => $input->mcastIp, + 'mcastPort' => $input->mcastPort, + 'mcastSpeed' => $input->mcastSpeed, + 'mcastMode' => $input->mcastMode, + 'numDisk' => (string) $input->diskNumber, + 'numPartition' => (string) $input->partitionNumber, + ]; - $inputData = [ - 'method' => $input->method, - 'client' => $input->client->getEntity()->getUuid(), - 'image' => $image->getUuid(), - 'mcastIp' => $input->mcastIp, - 'mcastPort' => $input->mcastPort, - 'mcastSpeed' => $input->mcastSpeed, - 'mcastMode' => $input->mcastMode, - 'numDisk' => (string) $partition->getDiskNumber(), - 'numPartition' => (string) $partition->getPartitionNumber(), - ]; + try { + $this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity(), $this->httpClient); + } catch (\Exception $e) { + //return new JsonResponse(data: ['error' => $e->getMessage()], status: Response::HTTP_INTERNAL_SERVER_ERROR); + continue; + } - try { - $this->deployImageOgRepositoryAction->__invoke($input, $image, $this->httpClient); - } catch (\Exception $e) { - return new JsonResponse(data: ['error' => $e->getMessage()], status: Response::HTTP_INTERNAL_SERVER_ERROR); + $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client->getEntity(), DeployMethodTypes::MULTICAST); + $this->createService->__invoke($client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); } - - $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, DeployMethodTypes::MULTICAST); - $this->createService->__invoke($input->client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); - - break; + break; case DeployMethodTypes::TORRENT: - $inputData = [ - 'method' => $input->method, - 'client' => $input->client->getEntity()->getUuid(), - 'image' => $image->getUuid(), - 'p2pMode' => $input->p2pMode, - 'p2pTime' => $input->p2pTime, - 'numDisk' => (string) $partition->getDiskNumber(), - 'numPartition' => (string) $partition->getPartitionNumber(), - ]; + foreach ($input->clients as $client) { + $inputData = [ + 'method' => $input->method, + 'client' => $client->getEntity()->getUuid(), + 'image' => $image->getUuid(), + 'p2pMode' => $input->p2pMode, + 'p2pTime' => $input->p2pTime, + 'numDisk' => (string) $input->diskNumber, + 'numPartition' => (string) $input->partitionNumber, + ]; - $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, DeployMethodTypes::TORRENT); - $this->createService->__invoke($input->client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); + try { + $this->deployImageOgRepositoryAction->__invoke($input, $image, $client->getEntity(), $this->httpClient); + } catch (\Exception $e) { + //return new JsonResponse(data: ['error' => $e->getMessage()], status: Response::HTTP_INTERNAL_SERVER_ERROR); + continue; + } + + $agentJobId = $this->deployImageOgAgentAction->__invoke($image, $input, $client->getEntity(), DeployMethodTypes::TORRENT); + $this->createService->__invoke($client->getEntity(), CommandTypes::DEPLOY_IMAGE, TraceStatus::IN_PROGRESS, $agentJobId, $inputData); + } break; } diff --git a/src/Controller/OgAgent/DeployImageAction.php b/src/Controller/OgAgent/DeployImageAction.php index 617505c..4cec527 100644 --- a/src/Controller/OgAgent/DeployImageAction.php +++ b/src/Controller/OgAgent/DeployImageAction.php @@ -38,7 +38,7 @@ class DeployImageAction extends AbstractController { } - public function __invoke(Image $image, DeployImageInput $input, string $method) + public function __invoke(Image $image, DeployImageInput $input, Client $client, string $method) { if (!$image->getClient()->getIp()) { throw new ValidatorException('IP is required'); @@ -46,12 +46,6 @@ class DeployImageAction extends AbstractController $partitionInfo = json_decode($image->getPartitionInfo(), true); - /** @var Client $client */ - $client = $input->client->getEntity(); - - /** @var Partition $partition */ - $partition = $input->partition->getEntity(); - $method = match ($input->method) { DeployMethodTypes::MULTICAST_UFTP_DIRECT, DeployMethodTypes::MULTICAST_UDPCAST_DIRECT, => 'multicast-direct', DeployMethodTypes::MULTICAST, DeployMethodTypes::MULTICAST_UFTP, DeployMethodTypes::MULTICAST_UDPCAST => 'multicast', @@ -61,7 +55,10 @@ class DeployImageAction extends AbstractController default => throw new ValidatorException('Invalid method'), }; - $ptcMulticastValue = "$method $input->mcastPort:$input->mcastMode:$input->mcastIp:$input->mcastSpeed:$input->maxClients:$input->maxTime"; + $mcastMode = $input->mcastMode.'-duplex'; + $mcastSpeed = $input->mcastSpeed.'M'; + + $ptcMulticastValue = "$method $input->mcastPort:$mcastMode:$input->mcastIp:$mcastSpeed:$input->maxClients:$input->maxTime"; $ptcTorrentValue = "$method $input->p2pMode:$input->p2pTime"; $ptcUnicastValue = $method; @@ -73,8 +70,8 @@ class DeployImageAction extends AbstractController }; $data = [ - 'dsk' => (string) $partition->getDiskNumber(), - 'par' => (string) $partition->getPartitionNumber(), + 'dsk' => (string) $input->diskNumber, + 'par' => (string) $input->partitionNumber, 'ifs' => "1", 'idi' => $image->getUuid(), 'nci' => $image->getName(), @@ -97,10 +94,6 @@ class DeployImageAction extends AbstractController } catch (TransportExceptionInterface $e) { $this->logger->error('Error deploying image', ['image' => $image->getId(), 'error' => $e->getMessage()]); - return new JsonResponse( - data: ['error' => $e->getMessage()], - status: Response::HTTP_INTERNAL_SERVER_ERROR - ); } $jobId = json_decode($response->getContent(), true)['job_id']; diff --git a/src/Controller/OgAgent/PartitionAssistantAction.php b/src/Controller/OgAgent/PartitionAssistantAction.php index 0044048..7e8021d 100644 --- a/src/Controller/OgAgent/PartitionAssistantAction.php +++ b/src/Controller/OgAgent/PartitionAssistantAction.php @@ -8,6 +8,7 @@ use App\Dto\Input\PartitionPostInput; use App\Entity\Client; use App\Entity\Command; use App\Entity\Image; +use App\Entity\Partition; use App\Entity\Trace; use App\Model\ClientStatus; use App\Model\CommandTypes; @@ -46,79 +47,88 @@ class PartitionAssistantAction extends AbstractController throw new ValidatorException('Partitions is required'); } - /** @var Client $client */ - $client = $input->partitions[0]->client->getEntity(); + foreach ($input->clients as $clientInput) { + $client = $clientInput->getEntity(); - $disks = []; - foreach ($partitions as $partition) { - $diskNumber = $partition->diskNumber; + $disks = []; + foreach ($partitions as $partition) { + $diskNumber = $partition->diskNumber; - if (!isset($disks[$diskNumber])) { - $disks[$diskNumber] = [ - 'diskData' => [], - 'partitionData' => [] - ]; - } - - if ($partition->filesystem === 'CACHE') { - $disks[$diskNumber]['diskData'] = [ - 'dis' => (string) $diskNumber, - 'che' => "0", - 'tch' => (string) ($partition->size * 1024), - ]; - } - - $disks[$diskNumber]['partitionData'][] = [ - 'par' => (string) $partition->partitionNumber, - 'cpt' => $partition->partitionCode, - 'sfi' => $partition->filesystem, - 'tam' => (string) (integer) ($partition->size * 1024), - 'ope' => $partition->format ? "1" : "0", - ]; - } - - foreach ($disks as $diskNumber => $diskInfo) { - $data = []; - if (!empty($diskInfo['diskData'])) { - $data[] = $diskInfo['diskData']; - } - $data = array_merge($data, $diskInfo['partitionData']); - - $result = [ - "nfn" => "Configurar", - "dsk" => (string) $diskNumber, - "cfg" => $data, - "ids" => "0" - ]; - - try { - $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/CloningEngine/Configurar', [ - 'verify_peer' => false, - 'verify_host' => false, - 'headers' => [ - 'Content-Type' => 'application/json', - ], - 'json' => $result, + $partitionEntity = $this->entityManager->getRepository(Partition::class)->findOneBy([ + 'client' => $client, + 'partitionNumber' => $partition->partitionNumber, + 'diskNumber' => $partition->diskNumber, ]); - $this->logger->info('Partitioning disk', ['client' => $client->getId(), 'disk' => $diskNumber]); - } catch (TransportExceptionInterface $e) { - $this->logger->error('Error partitioning disk', ['client' => $client->getId(), 'disk' => $diskNumber, 'error' => $e->getMessage()]); - return new JsonResponse( - data: ['error' => "Error en disco $diskNumber: " . $e->getMessage()], - status: Response::HTTP_INTERNAL_SERVER_ERROR - ); + + if ($partitionEntity) { + $partitionEntity->setClient($client); + $this->entityManager->persist($partitionEntity); + } + + if (!isset($disks[$diskNumber])) { + $disks[$diskNumber] = [ + 'diskData' => [], + 'partitionData' => [] + ]; + } + + if ($partition->filesystem === 'CACHE') { + $disks[$diskNumber]['diskData'] = [ + 'dis' => (string) $diskNumber, + 'che' => "0", + 'tch' => (string) ($partition->size * 1024), + ]; + } + + $disks[$diskNumber]['partitionData'][] = [ + 'par' => (string) $partition->partitionNumber, + 'cpt' => $partition->partitionCode, + 'sfi' => $partition->filesystem, + 'tam' => (string) (integer) ($partition->size * 1024), + 'ope' => $partition->format ? "1" : "0", + ]; } - $jobId = json_decode($response->getContent(), true)['job_id']; + foreach ($disks as $diskNumber => $diskInfo) { + $data = []; + if (!empty($diskInfo['diskData'])) { + $data[] = $diskInfo['diskData']; + } + $data = array_merge($data, $diskInfo['partitionData']); - $client->setStatus(ClientStatus::BUSY); - $this->entityManager->persist($client); - $this->entityManager->flush(); + $result = [ + "nfn" => "Configurar", + "dsk" => (string) $diskNumber, + "cfg" => $data, + "ids" => "0" + ]; - $this->createService->__invoke($client, CommandTypes::PARTITION_AND_FORMAT, TraceStatus::IN_PROGRESS, $jobId, []); + try { + $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/CloningEngine/Configurar', [ + 'verify_peer' => false, + 'verify_host' => false, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'json' => $result, + ]); + $this->logger->info('Partitioning disk', ['client' => $client->getId(), 'disk' => $diskNumber]); + } catch (TransportExceptionInterface $e) { + $this->logger->error('Error partitioning disk', ['client' => $client->getId(), 'disk' => $diskNumber, 'error' => $e->getMessage()]); + continue; + } + + $jobId = json_decode($response->getContent(), true)['job_id']; + + $client->setStatus(ClientStatus::BUSY); + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $this->createService->__invoke($client, CommandTypes::PARTITION_AND_FORMAT, TraceStatus::IN_PROGRESS, $jobId, []); + } } - return new JsonResponse(data: $client, status: Response::HTTP_OK); + return new JsonResponse(data: [], status: Response::HTTP_OK); } } diff --git a/src/Controller/OgAgent/PowerOffAction.php b/src/Controller/OgAgent/PowerOffAction.php index 766dd91..2f5f0b1 100644 --- a/src/Controller/OgAgent/PowerOffAction.php +++ b/src/Controller/OgAgent/PowerOffAction.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Controller\OgAgent; +use App\Dto\Input\MultipleClientsInput; use App\Entity\Client; use App\Entity\Command; use App\Entity\Image; @@ -37,44 +38,45 @@ class PowerOffAction extends AbstractController { } - public function __invoke(Client $client): JsonResponse + public function __invoke(MultipleClientsInput $input): JsonResponse { - if (!$client->getIp()) { - throw new ValidatorException('IP is required'); + foreach ($input->clients as $clientEntity) { + $client = $clientEntity->getEntity(); + + if (!$client->getIp()) { + throw new ValidatorException('IP is required'); + } + + $data = [ + 'nfn' => 'Apagar', + 'ids' => '0' + ]; + + try { + $response = $this->httpClient->request('POST', 'https://'.$client->getIp().':8000/ogAdmClient/Apagar', [ + 'verify_peer' => false, + 'verify_host' => false, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'json' => $data, + ]); + $this->logger->info('Powering off client', ['client' => $client->getId()]); + + } catch (TransportExceptionInterface $e) { + $this->logger->error('Error powering off client', ['client' => $client->getId(), 'error' => $e->getMessage()]); + continue; + } + + $jobId = json_decode($response->getContent(), true)['job_id']; + + $client->setStatus(ClientStatus::OFF); + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $this->createService->__invoke($client, CommandTypes::SHUTDOWN, TraceStatus::SUCCESS, $jobId, []); } - $data = [ - 'nfn' => 'Apagar', - 'ids' => '0' - ]; - - try { - $response = $this->httpClient->request('POST', 'https://'.$client->getIp().':8000/ogAdmClient/Apagar', [ - 'verify_peer' => false, - 'verify_host' => false, - 'headers' => [ - 'Content-Type' => 'application/json', - ], - 'json' => $data, - ]); - $this->logger->info('Powering off client', ['client' => $client->getId()]); - - } catch (TransportExceptionInterface $e) { - $this->logger->error('Error powering off client', ['client' => $client->getId(), 'error' => $e->getMessage()]); - return new JsonResponse( - data: ['error' => $e->getMessage()], - status: Response::HTTP_INTERNAL_SERVER_ERROR - ); - } - - $jobId = json_decode($response->getContent(), true)['job_id']; - - $client->setStatus(ClientStatus::OFF); - $this->entityManager->persist($client); - $this->entityManager->flush(); - - $this->createService->__invoke($client, CommandTypes::SHUTDOWN, TraceStatus::SUCCESS, $jobId, []); - return new JsonResponse(data: $client, status: Response::HTTP_OK); } } diff --git a/src/Controller/OgAgent/RebootAction.php b/src/Controller/OgAgent/RebootAction.php index 323f782..72d9a43 100644 --- a/src/Controller/OgAgent/RebootAction.php +++ b/src/Controller/OgAgent/RebootAction.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Controller\OgAgent; +use App\Dto\Input\MultipleClientsInput; use App\Entity\Client; use App\Entity\Command; use App\Entity\Image; @@ -37,44 +38,49 @@ class RebootAction extends AbstractController { } - public function __invoke(Client $client): JsonResponse + public function __invoke(MultipleClientsInput $input): JsonResponse { - if (!$client->getIp()) { - throw new ValidatorException('IP is required'); + foreach ($input->clients as $clientEntity) { + $client = $clientEntity->getEntity(); + + + if (!$client->getIp()) { + throw new ValidatorException('IP is required'); + } + + $data = [ + 'nfn' => 'Reiniciar', + 'ids' => '0' + ]; + + try { + $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/ogAdmClient/Reiniciar', [ + 'verify_peer' => false, + 'verify_host' => false, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'json' => $data, + ]); + $this->logger->info('Rebooting client', ['client' => $client->getId()]); + + } catch (TransportExceptionInterface $e) { + $this->logger->error('Error rebooting client', ['client' => $client->getId(), 'error' => $e->getMessage()]); + return new JsonResponse( + data: ['error' => $e->getMessage()], + status: Response::HTTP_INTERNAL_SERVER_ERROR + ); + } + + $jobId = json_decode($response->getContent(), true)['job_id']; + + $client->setStatus(ClientStatus::INITIALIZING); + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $this->createService->__invoke($client, CommandTypes::REBOOT, TraceStatus::SUCCESS, $jobId, []); } - $data = [ - 'nfn' => 'Reiniciar', - 'ids' => '0' - ]; - - try { - $response = $this->httpClient->request('POST', 'https://'.$client->getIp().':8000/ogAdmClient/Reiniciar', [ - 'verify_peer' => false, - 'verify_host' => false, - 'headers' => [ - 'Content-Type' => 'application/json', - ], - 'json' => $data, - ]); - $this->logger->info('Rebooting client', ['client' => $client->getId()]); - - } catch (TransportExceptionInterface $e) { - $this->logger->error('Error rebooting client', ['client' => $client->getId(), 'error' => $e->getMessage()]); - return new JsonResponse( - data: ['error' => $e->getMessage()], - status: Response::HTTP_INTERNAL_SERVER_ERROR - ); - } - - $jobId = json_decode($response->getContent(), true)['job_id']; - - $client->setStatus(ClientStatus::INITIALIZING); - $this->entityManager->persist($client); - $this->entityManager->flush(); - - $this->createService->__invoke($client, CommandTypes::REBOOT, TraceStatus::SUCCESS, $jobId, []); - return new JsonResponse(data: $client, status: Response::HTTP_OK); } } diff --git a/src/Controller/OgAgent/Webhook/ClientsController.php b/src/Controller/OgAgent/Webhook/ClientsController.php index e88e536..3a779b4 100644 --- a/src/Controller/OgAgent/Webhook/ClientsController.php +++ b/src/Controller/OgAgent/Webhook/ClientsController.php @@ -53,7 +53,7 @@ class ClientsController extends AbstractController public function index(Request $request): JsonResponse { $data = $request->toArray(); - $requiredFields = ['nfn', 'ids', 'res', 'der', 'job_id']; + $requiredFields = ['nfn', 'res', 'der', 'job_id']; foreach ($requiredFields as $field) { if (!isset($data[$field])) { diff --git a/src/Controller/OgBoot/PxeBootFile/PostAction.php b/src/Controller/OgBoot/PxeBootFile/PostAction.php index f869b76..1fa1d2c 100644 --- a/src/Controller/OgBoot/PxeBootFile/PostAction.php +++ b/src/Controller/OgBoot/PxeBootFile/PostAction.php @@ -30,11 +30,11 @@ class PostAction extends AbstractOgBootController { $ogRepoIp = $this->ogBootApiUrl; - if ($client->getRepository()) { - $ogRepoIp = $client->getRepository()->getIp(); - } else if ($client->getOrganizationalUnit()->getNetworkSettings()->getRepository()) { - $ogRepoIp = $client->getOrganizationalUnit()->getNetworkSettings()->getRepository()->getIp(); - } + $ogRepoIp = $client->getRepository()?->getIp() + ?? $client->getOrganizationalUnit()?->getNetworkSettings()?->getRepository()?->getIp(); + + $ogLive = $client->getOgLive()?->getFilename() + ?? $client->getOrganizationalUnit()?->getNetworkSettings()?->getOgLive()->getFilename(); $params = [ 'json' => [ @@ -54,7 +54,7 @@ class PostAction extends AbstractOgBootController 'oglog' => $this->ogLogIp, 'ogshare' => $client->getOrganizationalUnit()->getNetworkSettings()?->getOgShare() ? $client->getOrganizationalUnit()->getNetworkSettings()?->getOgShare(): $this->ogBootApiUrl, - 'oglivedir' => $client->getOgLive()->getFilename(), + 'oglivedir' => $ogLive, 'ogprof' => 'false', 'hardprofile' => $client->getHardwareProfile() ? $client->getHardwareProfile()->getDescription() : 'default', 'ogntp' => $client->getOrganizationalUnit()->getNetworkSettings()?->getNtp(), diff --git a/src/Controller/OgRepository/AbstractOgRepositoryController.php b/src/Controller/OgRepository/AbstractOgRepositoryController.php index 0d1f141..9cb4cb5 100644 --- a/src/Controller/OgRepository/AbstractOgRepositoryController.php +++ b/src/Controller/OgRepository/AbstractOgRepositoryController.php @@ -4,8 +4,11 @@ declare(strict_types=1); namespace App\Controller\OgRepository; +use App\Controller\OgRepository\Image\CreateAuxFilesAction; +use App\Controller\OgRepository\Image\GetAction; use App\Service\Trace\CreateService; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -24,6 +27,7 @@ abstract class AbstractOgRepositoryController extends AbstractController protected readonly EntityManagerInterface $entityManager, protected readonly HttpClientInterface $httpClient, protected readonly CreateService $createService, + protected readonly LoggerInterface $logger, ) { } @@ -50,6 +54,7 @@ abstract class AbstractOgRepositoryController extends AbstractController } catch (ClientExceptionInterface | ServerExceptionInterface $e) { $response = $e->getResponse(); $content = json_decode($response->getContent(false), true); + $this->logger->error(json_encode($content)); throw new HttpException($response->getStatusCode(), $content['error'] ?? 'An error occurred'); } catch (TransportExceptionInterface $e) { throw new HttpException(Response::HTTP_INTERNAL_SERVER_ERROR, $e->getMessage()); diff --git a/src/Controller/OgRepository/Image/CreateAuxFilesAction.php b/src/Controller/OgRepository/Image/CreateAuxFilesAction.php index 70c9461..36b6327 100644 --- a/src/Controller/OgRepository/Image/CreateAuxFilesAction.php +++ b/src/Controller/OgRepository/Image/CreateAuxFilesAction.php @@ -40,6 +40,8 @@ class CreateAuxFilesAction extends AbstractOgRepositoryController ] ]; + $this->logger->info('Creating aux files', ['image' => $data->getName()]); + $content = $this->createRequest('POST', 'http://'.$data->getRepository()->getIp().':8006/ogrepository/v1/images/torrentsum', $params); $inputData = [ @@ -49,6 +51,8 @@ class CreateAuxFilesAction extends AbstractOgRepositoryController $this->createService->__invoke($data->getClient(), CommandTypes::CREATE_IMAGE_AUX_FILE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData); + $this->logger->info('Aux files created successfully', ['image' => $data->getName()]); + $data->setStatus(ImageStatus::IN_PROGRESS); $this->entityManager->persist($data); $this->entityManager->flush(); diff --git a/src/Controller/OgRepository/Image/DeletePermanentAction.php b/src/Controller/OgRepository/Image/DeletePermanentAction.php index de36d77..815f87e 100644 --- a/src/Controller/OgRepository/Image/DeletePermanentAction.php +++ b/src/Controller/OgRepository/Image/DeletePermanentAction.php @@ -29,7 +29,11 @@ class DeletePermanentAction extends AbstractOgRepositoryController throw new ValidatorException('Fullsum is required'); } - $content = $this->createRequest( 'DELETE', 'http://'.$data->getRepository()->getIp().':8006/ogrepository/v1/images/'.$data->getImageFullsum().'?method=trash'); + $this->logger->info('Deleting image', ['image' => $data->getName()]); + + $content = $this->createRequest( 'DELETE', 'http://'.$data->getRepository()->getIp().':8006/ogrepository/v1/images/'.$data->getImageFullsum().'?method=permanent'); + + $this->logger->info('Image deleted', ['image' => $data->getName()]); $this->entityManager->remove($data); $this->entityManager->flush(); diff --git a/src/Controller/OgRepository/Image/DeleteTrashAction.php b/src/Controller/OgRepository/Image/DeleteTrashAction.php index 80a5c76..e4635ee 100644 --- a/src/Controller/OgRepository/Image/DeleteTrashAction.php +++ b/src/Controller/OgRepository/Image/DeleteTrashAction.php @@ -30,8 +30,12 @@ class DeleteTrashAction extends AbstractOgRepositoryController throw new ValidatorException('Fullsum is required'); } + $this->logger->info('Deleting image', ['image' => $data->getName()]); + $content = $this->createRequest('DELETE', 'http://'.$data->getRepository()->getIp().':8006/ogrepository/v1/images/'.$data->getImageFullsum().'?method=trash'); + $this->logger->info('Image deleted', ['image' => $data->getName()]); + $data->setStatus(ImageStatus::TRASH); $this->entityManager->persist($data); $this->entityManager->flush(); diff --git a/src/Controller/OgRepository/Image/DeployImageAction.php b/src/Controller/OgRepository/Image/DeployImageAction.php index 4a50fd5..404dcd5 100644 --- a/src/Controller/OgRepository/Image/DeployImageAction.php +++ b/src/Controller/OgRepository/Image/DeployImageAction.php @@ -4,6 +4,7 @@ namespace App\Controller\OgRepository\Image; use App\Controller\OgRepository\AbstractOgRepositoryController; use App\Dto\Input\DeployImageInput; +use App\Entity\Client; use App\Entity\Command; use App\Entity\Image; use App\Model\CommandTypes; @@ -28,10 +29,8 @@ class DeployImageAction extends AbstractOgRepositoryController * @throws ClientExceptionInterface * @throws TransportExceptionInterface */ - public function __invoke(DeployImageInput $input, Image $data, HttpClientInterface $httpClient): JsonResponse + public function __invoke(DeployImageInput $input, Image $data, Client $client, HttpClientInterface $httpClient): JsonResponse { - $client = $input->client; - $params = [ 'json' => [ 'ID_img' => $data->getImageFullsum(), diff --git a/src/Controller/OgRepository/Image/ExportAction.php b/src/Controller/OgRepository/Image/ExportAction.php new file mode 100644 index 0000000..ef449e3 --- /dev/null +++ b/src/Controller/OgRepository/Image/ExportAction.php @@ -0,0 +1,69 @@ +images; + + foreach ($images as $imageEntity) { + /** @var Image $image */ + $image = $imageEntity->getEntity(); + + if (!$image->getImageFullsum()) { + throw new ValidatorException('Fullsum is required'); + } + + $params = [ + 'json' => [ + 'ID_img' => $image->getImageFullsum(), + 'repo_ip' => $repository->getIp(), + 'user' => 'opengnsys', + ] + ]; + + $this->logger->info('Exporting image', ['image' => $image->getName(), 'repository' => $repository->getIp()]); + + $content = $this->createRequest('PUT', 'http://'.$image->getRepository()->getIp().':8006/ogrepository/v1/repo/images', $params); + + $inputData = [ + 'imageName' => $image->getName(), + 'imageUuid' => $image->getUuid(), + 'repositoryUuid' => $repository->getUuid(), + ]; + + $this->createService->__invoke($image->getClient(), CommandTypes::EXPORT_IMAGE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData); + + $image->setStatus(ImageStatus::TRANSFERING); + $this->entityManager->persist($image); + $this->entityManager->flush(); + } + + return new JsonResponse(data: [], status: Response::HTTP_OK); + } +} \ No newline at end of file diff --git a/src/Controller/OgRepository/Image/ImportAction.php b/src/Controller/OgRepository/Image/ImportAction.php new file mode 100644 index 0000000..075ff5d --- /dev/null +++ b/src/Controller/OgRepository/Image/ImportAction.php @@ -0,0 +1,69 @@ +images; + + foreach ($images as $imageEntity) { + /** @var Image $image */ + $image = $imageEntity->getEntity(); + + if (!$image->getImageFullsum()) { + throw new ValidatorException('Fullsum is required'); + } + + $params = [ + 'json' => [ + 'image' => $image->getName().'.img', + 'repo_ip' => $repository->getIp(), + 'user' => 'opengnsys', + ] + ]; + + $this->logger->info('Importing image', ['image' => $image->getName(), 'repository' => $repository->getIp()]); + + $content = $this->createRequest('POST', 'http://'.$image->getRepository()->getIp().':8006/ogrepository/v1/repo/images', $params); + + $inputData = [ + 'imageName' => $image->getName(), + 'imageUuid' => $image->getUuid(), + 'repositoryUuid' => $repository->getUuid(), + ]; + + $this->createService->__invoke($image->getClient(), CommandTypes::IMPORT_IMAGE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData); + + $image->setStatus(ImageStatus::TRANSFERING); + $this->entityManager->persist($image); + $this->entityManager->flush(); + } + + return new JsonResponse(data: [], status: Response::HTTP_OK); + } +} \ No newline at end of file diff --git a/src/Controller/OgRepository/Image/RecoverAction.php b/src/Controller/OgRepository/Image/RecoverAction.php index 4e6b820..ef65613 100644 --- a/src/Controller/OgRepository/Image/RecoverAction.php +++ b/src/Controller/OgRepository/Image/RecoverAction.php @@ -39,9 +39,12 @@ class RecoverAction extends AbstractOgRepositoryController ] ]; + $this->logger->info('Recovering image', ['image' => $data->getName()]); $content = $this->createRequest('POST', 'http://'.$data->getRepository()->getIp().':8006/ogrepository/v1/trash/images', $params); + $this->logger->info('Image recovered successfully', ['image' => $data->getName()]); + $data->setStatus(ImageStatus::SUCCESS); $this->entityManager->persist($data); $this->entityManager->flush(); diff --git a/src/Controller/OgRepository/Webhook/ResponseController.php b/src/Controller/OgRepository/Webhook/ResponseController.php index 7a7d0d2..57b76dc 100644 --- a/src/Controller/OgRepository/Webhook/ResponseController.php +++ b/src/Controller/OgRepository/Webhook/ResponseController.php @@ -2,58 +2,138 @@ namespace App\Controller\OgRepository\Webhook; +use App\Controller\OgRepository\AbstractOgRepositoryController; use App\Entity\Image; +use App\Entity\ImageRepository; use App\Entity\Trace; +use App\Model\CommandTypes; use App\Model\ImageStatus; use App\Model\TraceStatus; use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\AsController; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; #[AsController] -class ResponseController extends AbstractController +class ResponseController extends AbstractOgRepositoryController { - public function __construct( - protected readonly EntityManagerInterface $entityManager - ) - { - } - + /** + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ #[Route('/og-repository/webhook', name: 'og_repository_webhook', methods: ['POST'])] public function repositoryWebhook(Request $request): JsonResponse { $data = json_decode($request->getContent(), true); + if (!isset($data['job_id'])) { + return new JsonResponse(['message' => 'Invalid request'], Response::HTTP_BAD_REQUEST); + } + + $action = $data['job_id']; + + if (str_starts_with($action, "CreateAuxiliarFiles_")) { + $this->handleCreateAuxFiles($data); + } elseif (str_starts_with($action, "ExportImage_")) { + $this->processImageAction($data, 'export'); + } elseif (str_starts_with($action, "ImportImage_")) { + $this->processImageAction($data, 'import'); + } else { + return new JsonResponse(['message' => 'Invalid action'], Response::HTTP_BAD_REQUEST); + } + + return new JsonResponse($data, Response::HTTP_OK); + } + + private function handleCreateAuxFiles(array $data): void + { $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); $imageUuid = $trace->getInput()['imageUuid']; $image = $this->entityManager->getRepository(Image::class)->findOneBy(['uuid' => $imageUuid]); if ($image === null) { - $trace->setStatus(TraceStatus::FAILED); - $trace->setFinishedAt(new \DateTime()); - $trace->setOutput('Image not found'); - $this->entityManager->persist($trace); - $this->entityManager->flush(); - - return new JsonResponse(['message' => 'Image not found'], Response::HTTP_NOT_FOUND); + $this->updateTraceStatus($trace, TraceStatus::FAILED, 'Image not found'); + return; } $image->setImageFullsum($data['image_id']); $image->setStatus(ImageStatus::SUCCESS); $this->entityManager->persist($image); - $trace->setStatus(TraceStatus::SUCCESS); + $this->updateTraceStatus($trace, TraceStatus::SUCCESS); + } + + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ + private function processImageAction(array $data, string $actionType): void + { + $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); + $imageUuid = $trace->getInput()['imageUuid']; + $repositoryUuid = $trace->getInput()['repositoryUuid']; + + $image = $this->entityManager->getRepository(Image::class)->findOneBy(['uuid' => $imageUuid]); + $repository = $this->entityManager->getRepository(ImageRepository::class)->findOneBy(['uuid' => $repositoryUuid]); + + if ($image === null) { + $this->updateTraceStatus($trace, TraceStatus::FAILED, 'Image not found'); + return; + } + + $this->logger->info("Image $actionType", ['image' => $image->getName()]); + + $params = [ + 'json' => [ + 'image' => $image->getName().'.img' + ] + ]; + + $this->logger->info('Creating aux files', ['image' => $image->getName()]); + $content = $this->createRequest('POST', 'http://'.$repository->getIp().':8006/ogrepository/v1/images/torrentsum', $params); + + $inputData = [ + 'imageName' => $image->getName(), + 'imageUuid' => $image->getUuid(), + ]; + + $this->createService->__invoke($image->getClient(), CommandTypes::CREATE_IMAGE_AUX_FILE, TraceStatus::IN_PROGRESS, $content['job_id'], $inputData); + + $image->setRepository($repository); + $image->setStatus(ImageStatus::SUCCESS); + $this->entityManager->persist($image); + + $this->updateTraceStatus($trace, TraceStatus::SUCCESS); + } + + private function updateTraceStatus(Trace $trace, string $status, string $output = null): void + { + $trace->setStatus($status); $trace->setFinishedAt(new \DateTime()); + if ($output !== null) { + $trace->setOutput($output); + } + $this->entityManager->persist($trace); $this->entityManager->flush(); - return new JsonResponse($data, Response::HTTP_OK); - + if ($status === TraceStatus::FAILED) { + new JsonResponse(['message' => $output], Response::HTTP_NOT_FOUND); + } } -} \ No newline at end of file +} diff --git a/src/Controller/OgRepository/WoLAction.php b/src/Controller/OgRepository/WoLAction.php index 4669dc4..e486651 100644 --- a/src/Controller/OgRepository/WoLAction.php +++ b/src/Controller/OgRepository/WoLAction.php @@ -36,31 +36,35 @@ class WoLAction extends AbstractOgRepositoryController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(WoLInput $input, ImageRepository $repository): JsonResponse + public function __invoke(WoLInput $input): JsonResponse { - /** @var Client $client */ - $client = $input->client->getEntity(); + foreach ($input->clients as $client) { + /** @var Client $client */ + $client = $client->getEntity(); + $repository = $client->getRepository(); + if (!$repository->getIp()) { + throw new ValidatorException('IP is required'); + } - if (!$repository->getIp()) { - throw new ValidatorException('IP is required'); + $params = [ + 'json' => [ + 'broadcast_ip' => '255.255.255.255', + 'mac' => $client->getMac() + ] + ]; + + $this->logger->info('Sending WoL to client', ['mac' => $client->getMac()]); + + $content = $this->createRequest('POST', 'http://'.$repository->getIp(). ':8006/ogrepository/v1/wol', $params); + + $client->setStatus(ClientStatus::INITIALIZING); + $this->entityManager->persist($client); + $this->entityManager->flush(); + + $this->createService->__invoke($client, CommandTypes::SHUTDOWN, TraceStatus::SUCCESS, '', []); } - $params = [ - 'json' => [ - 'broadcast_ip' => '255.255.255.255', - 'mac' => $client->getMac() - ] - ]; - - $content = $this->createRequest('POST', 'http://'.$repository->getIp(). ':8006/ogrepository/v1/wol', $params); - - $client->setStatus(ClientStatus::INITIALIZING); - $this->entityManager->persist($client); - $this->entityManager->flush(); - - $this->createService->__invoke($client, CommandTypes::SHUTDOWN, TraceStatus::SUCCESS, '', []); - - return new JsonResponse(data: $client, status: Response::HTTP_OK); + return new JsonResponse(data: [], status: Response::HTTP_OK); } } diff --git a/src/Dto/Input/DeployImageInput.php b/src/Dto/Input/DeployImageInput.php index 86012cc..fb51bac 100644 --- a/src/Dto/Input/DeployImageInput.php +++ b/src/Dto/Input/DeployImageInput.php @@ -6,11 +6,13 @@ use ApiPlatform\Metadata\ApiProperty; use App\Dto\Output\ClientOutput; use App\Dto\Output\ImageOutput; use App\Dto\Output\PartitionOutput; +use App\Validator\Constraints\ClientsHaveSamePartitionCount; use App\Validator\Constraints\OrganizationalUnitMulticastMode; use App\Validator\Constraints\OrganizationalUnitMulticastPort; use App\Validator\Constraints\OrganizationalUnitP2PMode; use Symfony\Component\Serializer\Annotation\Groups; +#[ClientsHaveSamePartitionCount] class DeployImageInput { #[Groups(['image:write'])] @@ -21,19 +23,24 @@ class DeployImageInput #[ApiProperty(description: 'The type of the image deployment', example: "")] public ?string $method = null; + /** + * @var ClientOutput[] + */ #[Groups(['image:write'])] #[ApiProperty(description: 'The client to deploy the image')] - public ?ClientOutput $client = null; + public ?array $clients = []; #[Groups(['image:write'])] - #[ApiProperty(description: 'The partition to deploy the image')] - public ?PartitionOutput $partition = null; + public ?int $diskNumber = null; + + #[Groups(['image:write'])] + public ?int $partitionNumber = null; #[OrganizationalUnitP2PMode] #[Groups(['image:write'])] public ?string $p2pMode = null; - #[Groups(['organizational-unit:write'])] + #[Groups(['image:write'])] public ?int $p2pTime = null; #[Groups(['image:write'])] diff --git a/src/Dto/Input/ExportImportImageRepositoryInput.php b/src/Dto/Input/ExportImportImageRepositoryInput.php new file mode 100644 index 0000000..690500b --- /dev/null +++ b/src/Dto/Input/ExportImportImageRepositoryInput.php @@ -0,0 +1,18 @@ +comments = $image->getComments(); $this->type = $image->getType(); $this->remotePc = $image->isRemotePc(); + $this->isGlobal = $image->isGlobal(); + $this->status = $image->getStatus(); if ($image->getSoftwareProfile()) { $this->softwareProfile = new SoftwareProfileOutput($image->getSoftwareProfile()); @@ -95,13 +106,13 @@ final class ImageInput { if (!$image) { $image = new Image(); + $image->setStatus(ImageStatus::PENDING); } $image->setName($this->name); $image->setDescription($this->description); $image->setComments($this->comments); $image->setType($this->type); - $image->setStatus(ImageStatus::PENDING); if ($this->softwareProfile) { $image->setSoftwareProfile($this->softwareProfile->getEntity()); @@ -119,6 +130,7 @@ final class ImageInput } $image->setRemotePc($this->remotePc); + $image->setIsGlobal($this->isGlobal); $image->setCreated(false); $partitionInfo = []; diff --git a/src/Dto/Input/MultipleClientsInput.php b/src/Dto/Input/MultipleClientsInput.php new file mode 100644 index 0000000..c3dffde --- /dev/null +++ b/src/Dto/Input/MultipleClientsInput.php @@ -0,0 +1,16 @@ +operativeSystem = new OperativeSystemOutput($partition->getOperativeSystem()); } - if ($partition->getClient()) { - $this->client = new ClientOutput($partition->getClient()); - } $this->memoryUsage = $partition->getMemoryUsage(); if ($partition->getImage()) { @@ -114,7 +106,6 @@ final class PartitionInput if ($this->operativeSystem) { $partition->setOperativeSystem($this->operativeSystem->getEntity()); } - $partition->setClient($this->client->getEntity()); $partition->setMemoryUsage($this->memoryUsage * 100); if ($this->image) { diff --git a/src/Dto/Input/PartitionPostInput.php b/src/Dto/Input/PartitionPostInput.php index 4b52486..947d894 100644 --- a/src/Dto/Input/PartitionPostInput.php +++ b/src/Dto/Input/PartitionPostInput.php @@ -12,5 +12,11 @@ final class PartitionPostInput */ #[Groups(['partition:write'])] public array $partitions = []; + + /** + * @var ClientOutput[] + */ + #[Groups(['partition:write'])] + public array $clients = []; } diff --git a/src/Dto/Input/WoLInput.php b/src/Dto/Input/WoLInput.php index 7ba430c..82d36ca 100644 --- a/src/Dto/Input/WoLInput.php +++ b/src/Dto/Input/WoLInput.php @@ -8,7 +8,10 @@ use Symfony\Component\Serializer\Annotation\Groups; class WoLInput { + /** + * @var ClientOutput[]|null + */ #[Groups(['repository:write'])] #[ApiProperty(description: 'The client to wol')] - public ?ClientOutput $client = null; + public ?array $clients = null; } \ No newline at end of file diff --git a/src/Dto/Output/ClientOutput.php b/src/Dto/Output/ClientOutput.php index 6f275fd..f625812 100644 --- a/src/Dto/Output/ClientOutput.php +++ b/src/Dto/Output/ClientOutput.php @@ -103,10 +103,19 @@ final class ClientOutput extends AbstractOutput $this->menu = $client->getMenu() ? new MenuOutput($client->getMenu()) : null; $this->position = $client->getPosition(); $this->template = $client->getTemplate() ? new PxeTemplateOutput($client->getTemplate()) : null; - $this->repository = $client->getRepository() ? new ImageRepositoryOutput($client->getRepository()) : null; + + $repository = $client->getRepository() + ?? $client->getOrganizationalUnit()?->getNetworkSettings()?->getRepository(); + + $this->repository = $repository ? new ImageRepositoryOutput($repository) : null; + + $ogLive = $client->getOgLive() + ?? $client->getOrganizationalUnit()?->getNetworkSettings()?->getOgLive(); + + $this->ogLive = $ogLive ? new OgLiveOutput($ogLive) : null; + $this->hardwareProfile = $client->getHardwareProfile() ? new HardwareProfileOutput($client->getHardwareProfile()) : null; $this->subnet = $client->getSubnet()?->getIpAddress(); - $this->ogLive = $client->getOgLive() ? new OgLiveOutput($client->getOgLive()) : null; $this->status = $client->getStatus(); $this->createdAt = $client->getCreatedAt(); $this->createdBy = $client->getCreatedBy(); diff --git a/src/Dto/Output/ImageOutput.php b/src/Dto/Output/ImageOutput.php index 235e88b..54ecbd0 100644 --- a/src/Dto/Output/ImageOutput.php +++ b/src/Dto/Output/ImageOutput.php @@ -36,6 +36,9 @@ final class ImageOutput extends AbstractOutput #[Groups(['image:read'])] public ?bool $remotePc = null; + #[Groups(['image:read'])] + public ?bool $isGlobal = null; + #[Groups(['image:read'])] public ?bool $created = null; @@ -78,6 +81,7 @@ final class ImageOutput extends AbstractOutput $this->imageRepository = $image->getRepository() ? new ImageRepositoryOutput($image->getRepository()) : null; $this->partitionInfo = json_decode($image->getPartitionInfo(), true); $this->remotePc = $image->isRemotePc(); + $this->isGlobal = $image->isGlobal(); $this->created = $image->isCreated(); $this->createdAt = $image->getCreatedAt(); $this->createdBy = $image->getCreatedBy(); diff --git a/src/Entity/Image.php b/src/Entity/Image.php index dc2c8ba..694102b 100644 --- a/src/Entity/Image.php +++ b/src/Entity/Image.php @@ -65,6 +65,9 @@ class Image extends AbstractEntity #[ORM\Column(length: 255)] private ?string $status = null; + #[ORM\Column] + private ?bool $isGlobal = null; + public function __construct() { @@ -262,4 +265,16 @@ class Image extends AbstractEntity return $this; } + + public function isGlobal(): ?bool + { + return $this->isGlobal; + } + + public function setIsGlobal(bool $isGlobal): static + { + $this->isGlobal = $isGlobal; + + return $this; + } } diff --git a/src/Model/CommandTypes.php b/src/Model/CommandTypes.php index fd2570e..aac478f 100644 --- a/src/Model/CommandTypes.php +++ b/src/Model/CommandTypes.php @@ -8,6 +8,8 @@ final class CommandTypes public const string RESTORE_IMAGE = 'restore-image'; public const string CREATE_IMAGE = 'create-image'; public const string CREATE_IMAGE_AUX_FILE = 'create-image-aux-file'; + public const string IMPORT_IMAGE = 'import-image'; + public const string EXPORT_IMAGE = 'export-image'; public const string POWER_ON = 'power-on'; public const string REBOOT = 'reboot'; public const string SHUTDOWN = 'shutdown'; @@ -20,6 +22,8 @@ final class CommandTypes self::RESTORE_IMAGE => 'Update Cache', self::CREATE_IMAGE => 'Create Image', self::CREATE_IMAGE_AUX_FILE => 'Crear fichero auxiliar en repositorio', + self::IMPORT_IMAGE => 'Importar imagen', + self::EXPORT_IMAGE => 'Exportar imagen', self::POWER_ON => 'Encender', self::REBOOT => 'Reiniciar', self::SHUTDOWN => 'Apagar', diff --git a/src/Model/ImageStatus.php b/src/Model/ImageStatus.php index 3ef24f1..db13d5d 100644 --- a/src/Model/ImageStatus.php +++ b/src/Model/ImageStatus.php @@ -10,6 +10,7 @@ final class ImageStatus public const string SUCCESS = 'success'; public const string TRASH = 'trash'; public const string FAILED = 'failed'; + public const string TRANSFERING = 'transfering'; private const array STATUS = [ self::PENDING => 'Pendiente', @@ -18,6 +19,7 @@ final class ImageStatus self::TRASH => 'Papelera', self::SUCCESS => 'Completado', self::FAILED => 'Fallido', + self::TRANSFERING => 'Transferiendo', ]; public static function getStatus(): array diff --git a/src/Model/OrganizationalUnitP2PModes.php b/src/Model/OrganizationalUnitP2PModes.php index 49d7c0d..a6335f5 100644 --- a/src/Model/OrganizationalUnitP2PModes.php +++ b/src/Model/OrganizationalUnitP2PModes.php @@ -4,9 +4,9 @@ namespace App\Model; final class OrganizationalUnitP2PModes { - public const string P2P_MODE_LEECHER = 'p2p-mode-leecher'; - public const string P2P_MODE_PEER = 'p2p-mode-peer'; - public const string P2P_MODE_SEEDER = 'p2p-mode-seeder'; + public const string P2P_MODE_LEECHER = 'leecher'; + public const string P2P_MODE_PEER = 'peer'; + public const string P2P_MODE_SEEDER = 'seeder'; private const array P2P_MODE_NAMES = [ self::P2P_MODE_LEECHER => 'El cliente no comparte mientras descarga la imagen', diff --git a/src/Validator/Constraints/ClientsHaveSamePartitionCount.php b/src/Validator/Constraints/ClientsHaveSamePartitionCount.php new file mode 100644 index 0000000..9ea7093 --- /dev/null +++ b/src/Validator/Constraints/ClientsHaveSamePartitionCount.php @@ -0,0 +1,23 @@ +message = 'All clients must have the same number of partitions.'; + } + + public function getTargets(): string + { + return self::CLASS_CONSTRAINT; + } +} \ No newline at end of file diff --git a/src/Validator/Constraints/ClientsHaveSamePartitionCountValidator.php b/src/Validator/Constraints/ClientsHaveSamePartitionCountValidator.php new file mode 100644 index 0000000..896dc3e --- /dev/null +++ b/src/Validator/Constraints/ClientsHaveSamePartitionCountValidator.php @@ -0,0 +1,39 @@ +clients) && is_array($value->clients)) { + $partitionCounts = []; + foreach ($value->clients as $client) { + $partitionCount = $client->getEntity()->getPartitions()->count(); + $partitionCounts[(string) $client->getEntity()->getIp()] = $partitionCount; + } + + if (count(array_unique($partitionCounts)) > 1) { + $errorDetails = []; + foreach ($partitionCounts as $clientIp => $partitionCount) { + $errorDetails[] = "Cliente $clientIp tiene $partitionCount particiones."; + } + + $detailedMessage = implode(" ", $errorDetails); + + $this->context->buildViolation($constraint->message . ' Detalles: ' . $detailedMessage) + ->addViolation(); + } + } + } +} \ No newline at end of file