From 03e36f621834451307ff8a3f8b51d211d80a5b0e Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Fri, 15 Nov 2024 09:39:35 +0100 Subject: [PATCH] refs #1087. CreateImage. Output and webhooks controllers --- config/api_platform/Image.yaml | 43 ++++++++++ config/api_platform/ImageRepository.yaml | 26 ++++++ config/packages/security.yaml | 1 + config/services/api_platform.yaml | 2 +- src/Controller/OgAgent/DeployImageAction.php | 82 +++++++++++++++++++ src/Controller/OgAgent/StatusAction.php | 4 +- .../OgAgent/Webhook/ClientsController.php | 41 +++++++++- .../OgAgent/Webhook/OgAgentController.php | 16 ++++ src/Dto/Input/ClientInput.php | 2 +- src/Dto/Input/ImageInput.php | 2 + src/Dto/Output/ImageOutput.php | 8 ++ src/Dto/Output/TraceOutput.php | 4 + src/Entity/Client.php | 2 + src/Entity/Image.php | 35 +++++++- src/Entity/NetworkSettings.php | 30 +++++++ src/Entity/Trace.php | 15 ++++ src/Factory/ImageFactory.php | 1 + src/OpenApi/OpenApiFactory.php | 54 +++++++++++- 18 files changed, 358 insertions(+), 10 deletions(-) create mode 100644 src/Controller/OgAgent/DeployImageAction.php diff --git a/config/api_platform/Image.yaml b/config/api_platform/Image.yaml index 3bb12ef..a338c1e 100644 --- a/config/api_platform/Image.yaml +++ b/config/api_platform/Image.yaml @@ -23,6 +23,49 @@ resources: ApiPlatform\Metadata\Post: ~ ApiPlatform\Metadata\Delete: ~ + get_image_ogrepository: + shortName: OgRepository Server + description: Get image in OgRepository + class: ApiPlatform\Metadata\Get + method: GET + input: false + uriTemplate: /images/server/{uuid}/get + controller: App\Controller\OgRepository\Image\GetAction + + create_aux_files_image_ogrepository: + shortName: OgRepository Server + class: ApiPlatform\Metadata\Post + method: POST + input: false + uriTemplate: /images/server/{uuid}/create-aux-files + controller: App\Controller\OgRepository\Image\CreateAuxFilesAction + + deploy_image_ogrepository: + shortName: OgRepository Server + class: ApiPlatform\Metadata\Post + method: POST + input: App\Dto\Input\DeployImageInput + uriTemplate: /images/{uuid}/deploy-image + controller: App\Controller\DeployImageAction + + trash_delete_image_ogrepository: + shortName: OgRepository Server + description: Delete Image in OgRepository + class: ApiPlatform\Metadata\Delete + method: DELETE + input: false + uriTemplate: /images/server/{uuid}/delete-trash + controller: App\Controller\OgRepository\Image\DeleteTrashAction + + permanent_delete_image_ogrepository: + shortName: OgRepository Server + description: Delete Image in OgRepository + class: ApiPlatform\Metadata\Delete + method: DELETE + input: false + uriTemplate: /images/server/{uuid}/delete-permanent + controller: App\Controller\OgRepository\Image\DeletePermanentAction + properties: App\Entity\Image: id: diff --git a/config/api_platform/ImageRepository.yaml b/config/api_platform/ImageRepository.yaml index e6cb84c..43010ef 100644 --- a/config/api_platform/ImageRepository.yaml +++ b/config/api_platform/ImageRepository.yaml @@ -22,6 +22,32 @@ resources: ApiPlatform\Metadata\Post: ~ ApiPlatform\Metadata\Delete: ~ + image_ogrepository_sync: + shortName: OgRepository Server + class: ApiPlatform\Metadata\Post + method: POST + input: false + uriTemplate: /image-repositories/server/sync + controller: App\Controller\OgRepository\SyncAction + + get_collection_images_ogrepository: + shortName: OgRepository Server + description: Get collection of image in OgRepository + class: ApiPlatform\Metadata\Get + method: GET + input: false + uriTemplate: /image-repositories/server/get-collection + controller: App\Controller\OgRepository\GetCollectionAction + + images_ogrepository_status: + shortName: OgRepository Server + description: Get status of OgRepository + class: ApiPlatform\Metadata\Get + method: GET + input: false + uriTemplate: /image-repositories/server/{uuid}/status + controller: App\Controller\OgRepository\StatusAction + properties: App\Entity\ImageRepository: id: diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 568ba93..ac8c830 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -30,6 +30,7 @@ security: - { path: ^/docs, roles: PUBLIC_ACCESS } # Allows accessing the Swagger UI docs - { path: ^/auth/login, roles: PUBLIC_ACCESS } - { path: ^/opengnsys/rest, roles: PUBLIC_ACCESS } + - { path: ^/og-repository/webhook, roles: PUBLIC_ACCESS } - { path: ^/og-lives/install/webhook, roles: PUBLIC_ACCESS } - { path: ^/auth/refresh, roles: PUBLIC_ACCESS } - { path: ^/, roles: IS_AUTHENTICATED_FULLY } diff --git a/config/services/api_platform.yaml b/config/services/api_platform.yaml index 04feaea..5987770 100644 --- a/config/services/api_platform.yaml +++ b/config/services/api_platform.yaml @@ -61,7 +61,7 @@ services: api_platform.filter.image.search: parent: 'api_platform.doctrine.orm.search_filter' - arguments: [ { 'id': 'exact', 'name': 'partial', } ] + arguments: [ { 'id': 'exact', 'name': 'partial', 'repository.id': 'exact'} ] tags: [ 'api_platform.filter' ] api_platform.filter.image.boolean: diff --git a/src/Controller/OgAgent/DeployImageAction.php b/src/Controller/OgAgent/DeployImageAction.php new file mode 100644 index 0000000..0d3e7d3 --- /dev/null +++ b/src/Controller/OgAgent/DeployImageAction.php @@ -0,0 +1,82 @@ +getClient()->getIp()) { + throw new ValidatorException('IP is required'); + } + + $partitionInfo = json_decode($image->getPartitionInfo(), true); + + $data = [ + 'dsk' => (string) $partitionInfo['numDisk'], + 'par' => (string) $partitionInfo['numPartition'], + 'ifs' => "1", + 'idi' => $image->getUuid(), + 'nci' => $image->getName(), + 'ipr' => $image->getRepository()->getIp(), + 'nfn' => 'RestaurarImagen', + 'ptc' => 'unicast', + 'ids' => '0' + ]; + + try { + $response = $this->httpClient->request('POST', 'https://'.$client->getIp().':8000/CloningEngine/RestaurarImagen', [ + 'verify_peer' => false, + 'verify_host' => false, + 'headers' => [ + 'Content-Type' => 'application/json', + ], + 'json' => $data, + ]); + + } catch (TransportExceptionInterface $e) { + return new JsonResponse( + data: ['error' => $e->getMessage()], + status: Response::HTTP_INTERNAL_SERVER_ERROR + ); + } + + $jobId = json_decode($response->getContent(), true)['job_id']; + + $client->setStatus(ClientStatus::BUSY); + $this->entityManager->persist($client); + $this->entityManager->flush(); + + return $jobId; + } +} diff --git a/src/Controller/OgAgent/StatusAction.php b/src/Controller/OgAgent/StatusAction.php index 9478b47..5428a33 100644 --- a/src/Controller/OgAgent/StatusAction.php +++ b/src/Controller/OgAgent/StatusAction.php @@ -42,7 +42,7 @@ class StatusAction extends AbstractController } try { - $response = $this->httpClient->request('POST', 'https://localhost:4444/ogAdmClient/status', [ + $response = $this->httpClient->request('POST', 'https://' . $client->getIp() . ':8000/ogAdmClient/status', [ 'verify_peer' => false, 'verify_host' => false, 'timeout' => 10, @@ -90,7 +90,7 @@ class StatusAction extends AbstractController $partitionEntity->setDiskNumber($cfg['disk']); $partitionEntity->setPartitionNumber($cfg['par']); $partitionEntity->setSize($cfg['tam']); - $partitionEntity->setMemoryUsage($cfg['uso']); + $partitionEntity->setMemoryUsage(((int) $cfg['uso']) * 100); $this->entityManager->persist($partitionEntity); } } diff --git a/src/Controller/OgAgent/Webhook/ClientsController.php b/src/Controller/OgAgent/Webhook/ClientsController.php index a5bb67a..eb79d72 100644 --- a/src/Controller/OgAgent/Webhook/ClientsController.php +++ b/src/Controller/OgAgent/Webhook/ClientsController.php @@ -2,9 +2,13 @@ namespace App\Controller\OgAgent\Webhook; +use App\Controller\OgRepository\Image\CreateAuxFilesAction; use App\Entity\Image; +use App\Entity\OperativeSystem; +use App\Entity\Partition; use App\Entity\Trace; use App\Model\ClientStatus; +use App\Model\ImageStatus; use App\Model\TraceStatus; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -13,21 +17,33 @@ 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; +use Symfony\Contracts\HttpClient\HttpClientInterface; #[AsController] class ClientsController extends AbstractController { public function __construct( - protected readonly EntityManagerInterface $entityManager + protected readonly EntityManagerInterface $entityManager, + public readonly CreateAuxFilesAction $createAuxFilesAction ) { } + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ClientExceptionInterface + */ #[Route('/opengnsys/rest/clients/status/webhook', methods: ['POST'])] public function index(Request $request): JsonResponse { $data = $request->toArray(); - $requiredFields = ['nfn', 'idi', 'dsk', 'par', 'cpt', 'ipr', 'inv_sft', 'ids', 'res', 'der', 'job_id']; + $requiredFields = ['nfn', 'idi', 'dsk', 'par', 'ids', 'res', 'der', 'job_id']; foreach ($requiredFields as $field) { if (!isset($data[$field])) { @@ -42,8 +58,9 @@ class ClientsController extends AbstractController if ($data['res'] === 1) { $trace->setStatus(TraceStatus::SUCCESS); $trace->setFinishedAt(new \DateTime()); - + $image->setStatus(ImageStatus::PENDING); $image->setCreated(true); + $this->createAuxFilesAction->__invoke($image); } else { $trace->setStatus(TraceStatus::FAILED); @@ -59,7 +76,25 @@ class ClientsController extends AbstractController $this->entityManager->persist($trace); $this->entityManager->flush(); + } + if ($data['nfn'] === 'RESPUESTA_RestaurarImagen') { + $trace = $this->entityManager->getRepository(Trace::class)->findOneBy(['jobId' => $data['job_id']]); + $image = $this->entityManager->getRepository(Image::class)->findOneBy(['uuid' => $data['idi']]); + + if ($data['res'] === 1) { + $trace->setStatus(TraceStatus::SUCCESS); + $trace->setFinishedAt(new \DateTime()); + $image->setStatus(ImageStatus::PENDING); + } else { + $trace->setStatus(TraceStatus::FAILED); + $trace->setFinishedAt(new \DateTime()); + $trace->setOutput($data['der']); + } + + $this->entityManager->persist($image); + $this->entityManager->persist($trace); + $this->entityManager->flush(); } return new JsonResponse([], Response::HTTP_OK); diff --git a/src/Controller/OgAgent/Webhook/OgAgentController.php b/src/Controller/OgAgent/Webhook/OgAgentController.php index 082ca13..27f129b 100644 --- a/src/Controller/OgAgent/Webhook/OgAgentController.php +++ b/src/Controller/OgAgent/Webhook/OgAgentController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace App\Controller\OgAgent\Webhook; use App\Entity\Client; +use App\Model\ClientStatus; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; @@ -35,6 +36,21 @@ class OgAgentController extends AbstractController } $client = $this->entityManager->getRepository(Client::class)->findOneBy(['mac' => $data['mac']]); + if (!$client) { + return new JsonResponse(['message' => 'Client not found'], Response::HTTP_NOT_FOUND); + } + + switch ($data['ostype']) { + case 'Linux': + $client->setStatus(ClientStatus::LINUX); + break; + + default: + return new JsonResponse(['message' => 'Invalid status'], Response::HTTP_BAD_REQUEST); + } + + $this->entityManager->persist($client); + $this->entityManager->flush(); return new JsonResponse([], Response::HTTP_OK); } diff --git a/src/Dto/Input/ClientInput.php b/src/Dto/Input/ClientInput.php index c7e22e6..3f07594 100644 --- a/src/Dto/Input/ClientInput.php +++ b/src/Dto/Input/ClientInput.php @@ -151,7 +151,7 @@ final class ClientInput } if ($client->getRepository()) { - $this->repository = $client->getRepository(); + $this->repository = new ImageRepositoryOutput($client->getRepository()); } } diff --git a/src/Dto/Input/ImageInput.php b/src/Dto/Input/ImageInput.php index 4f2ccae..1d29283 100644 --- a/src/Dto/Input/ImageInput.php +++ b/src/Dto/Input/ImageInput.php @@ -11,6 +11,7 @@ use App\Dto\Output\SoftwareProfileOutput; use App\Entity\Image; use App\Entity\OrganizationalUnit; use App\Entity\Partition; +use App\Model\ImageStatus; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; @@ -100,6 +101,7 @@ final class ImageInput $image->setDescription($this->description); $image->setComments($this->comments); $image->setType($this->type); + $image->setStatus(ImageStatus::PENDING); if ($this->softwareProfile) { $image->setSoftwareProfile($this->softwareProfile->getEntity()); diff --git a/src/Dto/Output/ImageOutput.php b/src/Dto/Output/ImageOutput.php index 1f6dbfa..235e88b 100644 --- a/src/Dto/Output/ImageOutput.php +++ b/src/Dto/Output/ImageOutput.php @@ -48,6 +48,12 @@ final class ImageOutput extends AbstractOutput #[Groups(['image:read'])] public ?array $partitionInfo = null; + #[Groups(['image:read'])] + public ?string $imageFullsum = ''; + + #[Groups(['image:read'])] + public ?string $status = null; + #[Groups(['image:read'])] public \DateTime $createdAt; @@ -66,6 +72,8 @@ final class ImageOutput extends AbstractOutput $this->revision = $image->getRevision(); $this->info = $image->getInfo(); $this->size = $image->getSize(); + $this->imageFullsum = $image->getImageFullsum(); + $this->status = $image->getStatus(); $this->softwareProfile = $image->getSoftwareProfile() ? new SoftwareProfileOutput($image->getSoftwareProfile()) : null; $this->imageRepository = $image->getRepository() ? new ImageRepositoryOutput($image->getRepository()) : null; $this->partitionInfo = json_decode($image->getPartitionInfo(), true); diff --git a/src/Dto/Output/TraceOutput.php b/src/Dto/Output/TraceOutput.php index 465da71..e3a9b2d 100644 --- a/src/Dto/Output/TraceOutput.php +++ b/src/Dto/Output/TraceOutput.php @@ -28,6 +28,9 @@ final class TraceOutput extends AbstractOutput #[Groups(['trace:read'])] public ?string $output = null; + #[Groups(['trace:read'])] + public ?array $input = null; + #[Groups(['trace:read'])] public ?\DateTimeInterface $finishedAt = null; @@ -47,6 +50,7 @@ final class TraceOutput extends AbstractOutput $this->jobId = $trace->getJobId(); $this->executedAt = $trace->getExecutedAt(); $this->output = $trace->getOutput(); + $this->input = $trace->getInput(); $this->finishedAt = $trace->getFinishedAt(); $this->createdAt = $trace->getCreatedAt(); $this->createdBy = $trace->getCreatedBy(); diff --git a/src/Entity/Client.php b/src/Entity/Client.php index 0095ac7..8f3ee49 100644 --- a/src/Entity/Client.php +++ b/src/Entity/Client.php @@ -15,6 +15,8 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; #[UniqueEntity(fields: ['mac'], message: 'This MAC address is already in use.')] class Client extends AbstractEntity { + + // TODO nueva variable bool isTeacher use NameableTrait; #[ORM\Column(length: 255, nullable: true)] diff --git a/src/Entity/Image.php b/src/Entity/Image.php index 9f86cac..dc2c8ba 100644 --- a/src/Entity/Image.php +++ b/src/Entity/Image.php @@ -6,8 +6,11 @@ use App\Repository\ImageRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; #[ORM\Entity(repositoryClass: ImageRepository::class)] +#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_NAME', fields: ['name'])] +#[UniqueEntity(fields: ['name'], message: 'validators.image.name.unique')] class Image extends AbstractEntity { use NameableTrait; @@ -56,10 +59,16 @@ class Image extends AbstractEntity #[ORM\Column(nullable: true)] private ?bool $created = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $imageFullsum = null; + + #[ORM\Column(length: 255)] + private ?string $status = null; + + public function __construct() { parent::__construct(); - $this->partitions = new ArrayCollection(); } public function getDescription(): ?string @@ -229,4 +238,28 @@ class Image extends AbstractEntity return $this; } + + public function getImageFullsum(): ?string + { + return $this->imageFullsum; + } + + public function setImageFullsum(?string $imageFullsum): static + { + $this->imageFullsum = $imageFullsum; + + return $this; + } + + public function getStatus(): ?string + { + return $this->status; + } + + public function setStatus(string $status): static + { + $this->status = $status; + + return $this; + } } diff --git a/src/Entity/NetworkSettings.php b/src/Entity/NetworkSettings.php index 7e5a9ac..8ba83ee 100644 --- a/src/Entity/NetworkSettings.php +++ b/src/Entity/NetworkSettings.php @@ -72,6 +72,12 @@ class NetworkSettings extends AbstractEntity #[ORM\ManyToOne] private ?OgLive $ogLive = null; + #[ORM\Column(length: 255, nullable: true)] + private ?string $ogLog = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $ogShare = null; + public function __construct() { parent::__construct(); @@ -321,4 +327,28 @@ class NetworkSettings extends AbstractEntity return $this; } + + public function getOgLog(): ?string + { + return $this->ogLog; + } + + public function setOgLog(?string $ogLog): static + { + $this->ogLog = $ogLog; + + return $this; + } + + public function getOgShare(): ?string + { + return $this->ogShare; + } + + public function setOgShare(?string $ogShare): static + { + $this->ogShare = $ogShare; + + return $this; + } } diff --git a/src/Entity/Trace.php b/src/Entity/Trace.php index 275c9ef..ae416bf 100644 --- a/src/Entity/Trace.php +++ b/src/Entity/Trace.php @@ -32,6 +32,9 @@ class Trace extends AbstractEntity #[ORM\Column(length: 255, nullable: true)] private ?string $jobId = null; + #[ORM\Column(type: "json", nullable: true)] + private ?array $input = null; + public function getClient(): ?Client { return $this->client; @@ -115,4 +118,16 @@ class Trace extends AbstractEntity return $this; } + + public function getInput(): ?array + { + return $this->input; + } + + public function setInput(?array $input): static + { + $this->input = $input; + + return $this; + } } diff --git a/src/Factory/ImageFactory.php b/src/Factory/ImageFactory.php index 692c49d..80fbdcf 100644 --- a/src/Factory/ImageFactory.php +++ b/src/Factory/ImageFactory.php @@ -35,6 +35,7 @@ final class ImageFactory extends ModelFactory return [ 'createdAt' => self::faker()->dateTime(), 'name' => self::faker()->text(255), + 'status' => self::faker()->randomElement(['IN_PROGRESS', 'FINISHED', 'ERROR']), 'softwareProfile' => SoftwareProfileFactory::new(), 'repository' => ImageRepositoryFactory::new(), 'updatedAt' => self::faker()->dateTime(), diff --git a/src/OpenApi/OpenApiFactory.php b/src/OpenApi/OpenApiFactory.php index 2f4935e..71ce331 100644 --- a/src/OpenApi/OpenApiFactory.php +++ b/src/OpenApi/OpenApiFactory.php @@ -21,7 +21,8 @@ final readonly class OpenApiFactory implements OpenApiFactoryInterface $this->addRefreshToken($openApi); $this->addOgAgentEndpoints($openApi); $this->addUDsEndpoints($openApi); - $this->addStatusEndpoint($openApi); + $this->addOgBootStatusEndpoint($openApi); + $this->addOgRepositoryStatusEndpoint($openApi); $this->addInstallOgLiveWebhookEndpoint($openApi); return $openApi; @@ -689,7 +690,7 @@ final readonly class OpenApiFactory implements OpenApiFactoryInterface )); } - private function addStatusEndpoint(OpenApi $openApi): void + private function addOgBootStatusEndpoint(OpenApi $openApi): void { $openApi ->getPaths() @@ -738,6 +739,55 @@ final readonly class OpenApiFactory implements OpenApiFactoryInterface )); } + private function addOgRepositoryStatusEndpoint(OpenApi $openApi): void + { + $openApi + ->getPaths() + ->addPath('/og-repository/status', (new Model\PathItem())->withGet( + (new Model\Operation('getStatus')) + ->withTags(['OgRepository']) + ->withResponses([ + Response::HTTP_OK => [ + 'description' => 'Service status', + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'status' => [ + 'type' => 'string', + 'example' => 'ok', + ], + 'uptime' => [ + 'type' => 'integer', + 'example' => 12345, + ], + ], + ], + ], + ], + ], + Response::HTTP_SERVICE_UNAVAILABLE => [ + 'description' => 'Service unavailable', + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'error' => [ + 'type' => 'string', + 'example' => 'Service is down', + ], + ], + ], + ], + ], + ], + ]) + ->withSummary('Get service status') + )); + } + private function addUDsEndpoints(OpenApi $openApi): void { $openApi->getPaths()->addPath('/opengnsys/rest//ous', (new Model\PathItem())->withGet(