Merge pull request 'develop' (#20) from develop into main
testing/ogcore-api/pipeline/head This commit looks good Details
testing/ogcore-api/pipeline/tag There was a failure building this commit Details

Reviewed-on: #20
hotfix-timeout 0.7.3
Manuel Aranda Rosales 2025-02-04 09:34:28 +01:00
commit 391c455dd0
36 changed files with 743 additions and 268 deletions

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250203113932 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->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');
}
}

View File

@ -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;
}

View File

@ -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'];

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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])) {

View File

@ -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(),

View File

@ -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());

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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(),

View File

@ -0,0 +1,69 @@
<?php
namespace App\Controller\OgRepository\Image;
use App\Controller\OgRepository\AbstractOgRepositoryController;
use App\Dto\Input\ExportImportImageRepositoryInput;
use App\Entity\Image;
use App\Entity\ImageRepository;
use App\Model\CommandTypes;
use App\Model\ImageStatus;
use App\Model\TraceStatus;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Validator\Exception\ValidatorException;
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 ExportAction extends AbstractOgRepositoryController
{
/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function __invoke(ExportImportImageRepositoryInput $input, ImageRepository $repository): JsonResponse
{
$images = $input->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);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\Controller\OgRepository\Image;
use App\Controller\OgRepository\AbstractOgRepositoryController;
use App\Dto\Input\ExportImportImageRepositoryInput;
use App\Entity\Image;
use App\Entity\ImageRepository;
use App\Model\CommandTypes;
use App\Model\ImageStatus;
use App\Model\TraceStatus;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\Validator\Exception\ValidatorException;
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 ImportAction extends AbstractOgRepositoryController
{
/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function __invoke(ExportImportImageRepositoryInput $input, ImageRepository $repository): JsonResponse
{
$images = $input->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);
}
}

View File

@ -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();

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View File

@ -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'])]

View File

@ -0,0 +1,18 @@
<?php
namespace App\Dto\Input;
use App\Dto\Output\ImageOutput;
use App\Dto\Output\ImageRepositoryOutput;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
class ExportImportImageRepositoryInput
{
/**
* @var ImageOutput[]
*/
#[Assert\NotNull]
#[Groups(['repository:write'])]
public array $images = [];
}

View File

@ -38,6 +38,10 @@ final class ImageInput
#[ApiProperty(description: 'The type of the image', example: "Server")]
public ?string $source = 'input';
#[Groups(['image:write'])]
#[ApiProperty(description: 'The status of the image', example: "PENDING")]
public ?string $status = ImageStatus::PENDING;
#[Groups(['image:write'])]
#[ApiProperty(description: 'The software profile of the image')]
public ?SoftwareProfileOutput $softwareProfile = null;
@ -62,6 +66,11 @@ final class ImageInput
#[ApiProperty(description: 'The remote pc of the image')]
public ?bool $remotePc = false;
#[Groups(['image:write'])]
#[ApiProperty(description: 'The global property of the image')]
public ?bool $isGlobal = false;
public function __construct(?Image $image = null)
{
if (!$image) {
@ -73,6 +82,8 @@ final class ImageInput
$this->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 = [];

View File

@ -0,0 +1,16 @@
<?php
namespace App\Dto\Input;
use App\Dto\Output\ClientOutput;
use Symfony\Component\Serializer\Annotation\Groups;
final class MultipleClientsInput
{
/**
* @var ClientOutput[]
*/
#[Groups(['client:write'])]
public array $clients = [];
}

View File

@ -58,11 +58,6 @@ final class PartitionInput
#[ApiProperty(description: 'The operative system name of the partition', example: "Ubuntu")]
public ?OperativeSystemOutput $operativeSystem = null;
#[Assert\NotNull()]
#[Groups(['partition:write'])]
#[ApiProperty(description: 'The client of the partition')]
public ?ClientOutput $client = null;
#[Groups(['partition:write'])]
#[ApiProperty(description: 'The memory usage of the partition', example: 100)]
public ?int $memoryUsage = null;
@ -88,9 +83,6 @@ final class PartitionInput
$this->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) {

View File

@ -12,5 +12,11 @@ final class PartitionPostInput
*/
#[Groups(['partition:write'])]
public array $partitions = [];
/**
* @var ClientOutput[]
*/
#[Groups(['partition:write'])]
public array $clients = [];
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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',

View File

@ -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

View File

@ -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',

View File

@ -0,0 +1,23 @@
<?php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
#[\Attribute]
class ClientsHaveSamePartitionCount extends Constraint
{
public string $message;
public function __construct(mixed $options = null, ?array $groups = null, mixed $payload = null)
{
parent::__construct($options, $groups, $payload);
$this->message = 'All clients must have the same number of partitions.';
}
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Validator\Constraints;
use App\Dto\Input\DeployImageInput;
use App\Dto\Output\ClientOutput;
use App\Entity\Client;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Constraint;
class ClientsHaveSamePartitionCountValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint): void
{
if (!$value instanceof DeployImageInput) {
return;
}
if (isset($value->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();
}
}
}
}