refs #1087. CreateImage. Output and webhooks controllers
testing/ogcore-api/pipeline/head There was a failure building this commit Details

pull/13/head
Manuel Aranda Rosales 2024-11-15 09:39:35 +01:00
parent aee03d6a85
commit 03e36f6218
18 changed files with 358 additions and 10 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace App\Controller\OgAgent;
use App\Entity\Client;
use App\Entity\Command;
use App\Entity\Image;
use App\Entity\Trace;
use App\Model\ClientStatus;
use App\Model\TraceStatus;
use App\Service\Trace\CreateService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
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;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class DeployImageAction extends AbstractController
{
public function __construct(
protected readonly EntityManagerInterface $entityManager,
protected readonly HttpClientInterface $httpClient,
protected readonly CreateService $createService,
)
{
}
public function __invoke(Image $image, Command $command, Client $client)
{
if (!$image->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;
}
}

View File

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

View File

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

View File

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

View File

@ -151,7 +151,7 @@ final class ClientInput
}
if ($client->getRepository()) {
$this->repository = $client->getRepository();
$this->repository = new ImageRepositoryOutput($client->getRepository());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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