diff --git a/config/api_platform/Client.yaml b/config/api_platform/Client.yaml index 4fca0a6..641ba9a 100644 --- a/config/api_platform/Client.yaml +++ b/config/api_platform/Client.yaml @@ -13,6 +13,7 @@ resources: filters: - 'api_platform.filter.client.order' - 'api_platform.filter.client.search' + - 'api_platform.filter.client.exist' ApiPlatform\Metadata\Get: provider: App\State\Provider\ClientProvider diff --git a/config/api_platform/OgLive.yaml b/config/api_platform/OgLive.yaml index e1b3327..a40fa2a 100644 --- a/config/api_platform/OgLive.yaml +++ b/config/api_platform/OgLive.yaml @@ -24,7 +24,7 @@ resources: ApiPlatform\Metadata\Post: ~ ApiPlatform\Metadata\Delete: ~ - sync: + oglives_sync: class: ApiPlatform\Metadata\Post method: POST input: false diff --git a/config/api_platform/Subnet.yaml b/config/api_platform/Subnet.yaml index 6f384de..72ef541 100644 --- a/config/api_platform/Subnet.yaml +++ b/config/api_platform/Subnet.yaml @@ -23,6 +23,13 @@ resources: ApiPlatform\Metadata\Post: ~ ApiPlatform\Metadata\Delete: ~ + subnet_sync: + class: ApiPlatform\Metadata\Post + method: POST + input: false + uriTemplate: /subnets/sync + controller: App\Controller\OgDhcp\Subnet\SyncAction + get_collection_subnets: shortName: Subnet Server description: Get collection of Subnet @@ -101,7 +108,8 @@ resources: class: ApiPlatform\Metadata\Delete method: DELETE input: false - uriTemplate: /og-dhcp/server/{uuid}/delete-host + read: false + uriTemplate: /og-dhcp/server/{uuid}/delete-host/{clientUuid} controller: App\Controller\OgDhcp\Subnet\DeleteHostAction diff --git a/config/services/api_platform.yaml b/config/services/api_platform.yaml index cd8bc48..19567b8 100644 --- a/config/services/api_platform.yaml +++ b/config/services/api_platform.yaml @@ -23,6 +23,11 @@ services: arguments: [ { 'id': 'exact', 'name': 'partial', 'serialNumber': 'exact', organizationalUnit.id: 'exact', mac: 'exact', ip: 'exact' } ] tags: [ 'api_platform.filter' ] + api_platform.filter.client.exist: + parent: 'api_platform.doctrine.orm.exists_filter' + arguments: [{'subnet': ~ }] + tags: [ 'api_platform.filter' ] + api_platform.filter.command.order: parent: 'api_platform.doctrine.orm.order_filter' arguments: @@ -144,6 +149,18 @@ services: arguments: [ { 'id': 'exact', 'name': 'partial', } ] tags: [ 'api_platform.filter' ] + api_platform.filter.subnet.order: + parent: 'api_platform.doctrine.orm.order_filter' + arguments: + $properties: { 'id': ~, 'name': ~ } + $orderParameterName: 'order' + tags: [ 'api_platform.filter' ] + + api_platform.filter.subnet.search: + parent: 'api_platform.doctrine.orm.search_filter' + arguments: [ { 'id': 'exact', 'name': 'partial', ip: 'exact', nextServer: 'exact', netmask: 'exact', bootFileName: 'partial'} ] + tags: [ 'api_platform.filter' ] + api_platform.filter.trace.search: parent: 'api_platform.doctrine.orm.search_filter' arguments: [ { 'id': 'exact', 'command.id': 'exact', 'client.id': 'exact' } ] diff --git a/migrations/Version20241014053130.php b/migrations/Version20241014053130.php new file mode 100644 index 0000000..5abd23e --- /dev/null +++ b/migrations/Version20241014053130.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE subnet ADD server_id INT DEFAULT NULL, ADD synchronized TINYINT(1) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE subnet DROP server_id, DROP synchronized'); + } +} diff --git a/migrations/Version20241014082029.php b/migrations/Version20241014082029.php new file mode 100644 index 0000000..7777645 --- /dev/null +++ b/migrations/Version20241014082029.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE client DROP FOREIGN KEY FK_C7440455C9CF9478'); + $this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455C9CF9478 FOREIGN KEY (subnet_id) REFERENCES subnet (id) ON DELETE CASCADE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE client DROP FOREIGN KEY FK_C7440455C9CF9478'); + $this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455C9CF9478 FOREIGN KEY (subnet_id) REFERENCES subnet (id)'); + } +} diff --git a/migrations/Version20241014102105.php b/migrations/Version20241014102105.php new file mode 100644 index 0000000..6015cff --- /dev/null +++ b/migrations/Version20241014102105.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE client DROP FOREIGN KEY FK_C7440455C9CF9478'); + $this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455C9CF9478 FOREIGN KEY (subnet_id) REFERENCES subnet (id) ON DELETE SET NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE client DROP FOREIGN KEY FK_C7440455C9CF9478'); + $this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455C9CF9478 FOREIGN KEY (subnet_id) REFERENCES subnet (id) ON DELETE CASCADE'); + } +} diff --git a/src/Controller/OgBoot/OgBootController.php b/src/Controller/OgBoot/OgBootController.php index f9e26f3..eaa71be 100644 --- a/src/Controller/OgBoot/OgBootController.php +++ b/src/Controller/OgBoot/OgBootController.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Controller\OgBoot; -use App\Service\OgBoot\ConfigService; use App\Service\OgBoot\StatusService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; @@ -23,7 +22,6 @@ class OgBootController extends AbstractController { public function __construct( private readonly StatusService $ogbootStatusService, - private readonly ConfigService $ogbootConfigService, ) { } @@ -41,19 +39,4 @@ class OgBootController extends AbstractController return new JsonResponse( data: $data, status: Response::HTTP_OK); } - - - /** - * @throws TransportExceptionInterface - * @throws ServerExceptionInterface - * @throws RedirectionExceptionInterface - * @throws ClientExceptionInterface - */ - #[Route('/config', name: 'ogboot_config', methods: ['GET'])] - public function config(): JsonResponse - { - $data = $this->ogbootConfigService->__invoke(); - - return new JsonResponse( data: $data, status: Response::HTTP_OK); - } } diff --git a/src/Controller/OgDhcp/AbstractOgDhcpController.php b/src/Controller/OgDhcp/AbstractOgDhcpController.php index cfd81b6..2e4e6f5 100644 --- a/src/Controller/OgDhcp/AbstractOgDhcpController.php +++ b/src/Controller/OgDhcp/AbstractOgDhcpController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Controller\OgDhcp; +use App\Service\Utils\GetIpAddressAndNetmaskFromCIDRService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; @@ -21,7 +22,8 @@ abstract class AbstractOgDhcpController extends AbstractController { public function __construct( protected readonly string $ogDhcpApiUrl, - protected readonly EntityManagerInterface $entityManager + protected readonly EntityManagerInterface $entityManager, + protected readonly GetIpAddressAndNetmaskFromCIDRService $getIpAddressAndNetmaskFromCIDRService ) { } diff --git a/src/Controller/OgDhcp/OgDhcpController.php b/src/Controller/OgDhcp/OgDhcpController.php new file mode 100644 index 0000000..f67f01f --- /dev/null +++ b/src/Controller/OgDhcp/OgDhcpController.php @@ -0,0 +1,41 @@ +ogdhcpStatusService->__invoke(); + + return new JsonResponse( data: $data, status: Response::HTTP_OK); + } +} diff --git a/src/Controller/OgDhcp/Subnet/DeleteAction.php b/src/Controller/OgDhcp/Subnet/DeleteAction.php index 8f687c5..a00555a 100644 --- a/src/Controller/OgDhcp/Subnet/DeleteAction.php +++ b/src/Controller/OgDhcp/Subnet/DeleteAction.php @@ -29,7 +29,7 @@ class DeleteAction extends AbstractOgDhcpController throw new ValidatorException('Data Id is required'); } - $content = $this->createRequest($httpClient, 'DELETE', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getId()); + $content = $this->createRequest($httpClient, 'DELETE', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getServerId()); $this->entityManager->remove($data); $this->entityManager->flush(); diff --git a/src/Controller/OgDhcp/Subnet/DeleteHostAction.php b/src/Controller/OgDhcp/Subnet/DeleteHostAction.php index 4544f45..cf32b80 100644 --- a/src/Controller/OgDhcp/Subnet/DeleteHostAction.php +++ b/src/Controller/OgDhcp/Subnet/DeleteHostAction.php @@ -3,6 +3,7 @@ namespace App\Controller\OgDhcp\Subnet; use App\Controller\OgDhcp\AbstractOgDhcpController; +use App\Entity\Client; use App\Entity\Subnet; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -23,20 +24,27 @@ class DeleteHostAction extends AbstractOgDhcpController * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface */ - public function __invoke(Subnet $data, HttpClientInterface $httpClient): JsonResponse + public function __invoke(Subnet $data, string $clientUuid, HttpClientInterface $httpClient): JsonResponse { + $client = $this->entityManager->getRepository(Client::class)->findOneBy(['uuid' => $clientUuid]); + + if (!$client || $client->getSubnet() !== $data) { + throw new ValidatorException('Client not found'); + } + if (!$data->getId()) { throw new ValidatorException('Data URL is required'); } $params = [ 'json' => [ - 'host' => '', + 'macAddress' => strtolower($client->getMac()), ] ]; - $content = $this->createRequest($httpClient, 'POST', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getId().'/hosts', $params); + $content = $this->createRequest($httpClient, 'DELETE', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getServerId().'/hosts', $params); + $data->removeClient($client); $this->entityManager->persist($data); $this->entityManager->flush(); diff --git a/src/Controller/OgDhcp/Subnet/GetAction.php b/src/Controller/OgDhcp/Subnet/GetAction.php index b17698c..77f1bce 100644 --- a/src/Controller/OgDhcp/Subnet/GetAction.php +++ b/src/Controller/OgDhcp/Subnet/GetAction.php @@ -29,7 +29,7 @@ class GetAction extends AbstractOgDhcpController throw new ValidatorException('Checksum is required'); } - $content = $this->createRequest($httpClient, 'GET', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getId()); + $content = $this->createRequest($httpClient, 'GET', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getServerId()); return new JsonResponse(data: $content, status: Response::HTTP_OK); } diff --git a/src/Controller/OgDhcp/Subnet/GetHostsAction.php b/src/Controller/OgDhcp/Subnet/GetHostsAction.php index b465466..52ca307 100644 --- a/src/Controller/OgDhcp/Subnet/GetHostsAction.php +++ b/src/Controller/OgDhcp/Subnet/GetHostsAction.php @@ -29,7 +29,7 @@ class GetHostsAction extends AbstractOgDhcpController throw new ValidatorException('Checksum is required'); } - $content = $this->createRequest($httpClient, 'GET', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getId().'/hosts'); + $content = $this->createRequest($httpClient, 'GET', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getServerId().'/hosts'); return new JsonResponse(data: $content, status: Response::HTTP_OK); } diff --git a/src/Controller/OgDhcp/Subnet/PostAction.php b/src/Controller/OgDhcp/Subnet/PostAction.php index e9e32e4..a4824f1 100644 --- a/src/Controller/OgDhcp/Subnet/PostAction.php +++ b/src/Controller/OgDhcp/Subnet/PostAction.php @@ -37,6 +37,8 @@ class PostAction extends AbstractOgDhcpController $content = $this->createRequest($httpClient, 'POST', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets' , $params); + $data->setServerId($content['message']['id']); + $data->setSynchronized(true); $this->entityManager->persist($data); $this->entityManager->flush(); diff --git a/src/Controller/OgDhcp/Subnet/PostHostAction.php b/src/Controller/OgDhcp/Subnet/PostHostAction.php index 1b900e8..4330f8d 100644 --- a/src/Controller/OgDhcp/Subnet/PostHostAction.php +++ b/src/Controller/OgDhcp/Subnet/PostHostAction.php @@ -44,7 +44,7 @@ class PostHostAction extends AbstractOgDhcpController 'json' => $data ]; - $content = $this->createRequest($httpClient, 'POST', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$subnet->getId().'/hosts', $params); + $content = $this->createRequest($httpClient, 'POST', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$subnet->getServerId().'/hosts', $params); $subnet->addClient($clientEntity); $this->entityManager->persist($subnet); diff --git a/src/Controller/OgDhcp/Subnet/PutAction.php b/src/Controller/OgDhcp/Subnet/PutAction.php index f57b282..9e3004c 100644 --- a/src/Controller/OgDhcp/Subnet/PutAction.php +++ b/src/Controller/OgDhcp/Subnet/PutAction.php @@ -38,7 +38,7 @@ class PutAction extends AbstractOgDhcpController ] ]; - $content = $this->createRequest($httpClient, 'PUT', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getId(), $params); + $content = $this->createRequest($httpClient, 'PUT', $this->ogDhcpApiUrl.'/ogdhcp/v1/subnets/'.$data->getServerId(), $params); $this->entityManager->persist($data); $this->entityManager->flush(); diff --git a/src/Controller/OgDhcp/Subnet/SyncAction.php b/src/Controller/OgDhcp/Subnet/SyncAction.php new file mode 100644 index 0000000..29c86dc --- /dev/null +++ b/src/Controller/OgDhcp/Subnet/SyncAction.php @@ -0,0 +1,90 @@ +createRequest($httpClient, 'GET', $this->ogDhcpApiUrl . '/ogdhcp/v1/subnets'); + + $arraySync = []; + + foreach ($content['message'] as $subnet) { + $subnetEntity = $this->entityManager->getRepository(Subnet::class)->findOneBy(['serverId' => $subnet['id']]); + if ($subnetEntity) { + $this->extracted($subnetEntity, $subnet); + $this->entityManager->persist($subnetEntity); + } else { + $subnetEntity = new Subnet(); + $this->extracted($subnetEntity, $subnet); + } + + $arraySync[] = $subnet['id']; + + $subnetEntity->setServerId($subnet['id']); + $subnetEntity->setSynchronized(true); + $this->entityManager->persist($subnetEntity); + } + + $this->setSynchronized($arraySync); + + $this->entityManager->flush(); + + return new JsonResponse(data: $content, status: Response::HTTP_OK); + } + + /** + * @param Subnet|null $subnetEntity + * @param mixed $subnet + * @return void + */ + private function extracted(Subnet|null $subnetEntity, mixed $subnet): void + { + $getParsedData = $this->getIpAddressAndNetmaskFromCIDRService->__invoke($subnet['subnet']); + + $subnetEntity->setName($subnet['boot-file-name']); + $subnetEntity->setBootFileName($subnet['boot-file-name']); + $subnetEntity->setIpAddress($getParsedData['ip']); + $subnetEntity->setNetmask($getParsedData['mask']); + $subnetEntity->setNextServer($subnet['next-server']); + + foreach ($subnet['reservations'] as $host) { + $hostEntity = $this->entityManager->getRepository(Client::class)->findOneBy(['mac' => $host['hw-address']]); + if ($hostEntity){ + $subnetEntity->addClient($hostEntity); + } + } + } + + public function setSynchronized(array $array): void + { + $this->entityManager->createQueryBuilder() + ->update(Subnet::class, 's') + ->set('s.synchronized', 'false') + ->where('s.serverId NOT IN (:array)') + ->setParameter('array', array_values($array)) + ->getQuery() + ->getResult(); + } +} \ No newline at end of file diff --git a/src/Dto/Output/ClientOutput.php b/src/Dto/Output/ClientOutput.php index 2bed44b..ca46c88 100644 --- a/src/Dto/Output/ClientOutput.php +++ b/src/Dto/Output/ClientOutput.php @@ -52,6 +52,9 @@ final class ClientOutput extends AbstractOutput #[ApiProperty(readableLink: true )] public ?OgLiveOutput $ogLive = null; + #[Groups(['client:read'])] + public ?string $subnet = null; + #[Groups(['client:read'])] public ?array $position = ['x' => 0, 'y' => 0]; @@ -89,6 +92,7 @@ final class ClientOutput extends AbstractOutput $this->menu = $client->getMenu() ? new MenuOutput($client->getMenu()) : null; $this->position = $client->getPosition(); $this->hardwareProfile = $client->getHardwareProfile() ? new HardwareProfileOutput($client->getHardwareProfile()) : null; + $this->subnet = $client->getSubnet() ? $client->getSubnet()->getIpAddress(). '-' . $client->getSubnet()->getNetmask() : null; $this->ogLive = $client->getOgLive() ? new OgLiveOutput($client->getOgLive()) : null; $this->status = $client->getStatus(); $this->createdAt = $client->getCreatedAt(); diff --git a/src/Dto/Output/SubnetOutput.php b/src/Dto/Output/SubnetOutput.php index f542562..e112d82 100644 --- a/src/Dto/Output/SubnetOutput.php +++ b/src/Dto/Output/SubnetOutput.php @@ -30,6 +30,12 @@ final class SubnetOutput extends AbstractOutput #[Groups(['subnet:read'])] public array $clients; + #[Groups(['subnet:read'])] + public ?bool $synchronized = false; + + #[Groups(['subnet:read'])] + public ?int $serverId; + #[Groups(['subnet:read'])] public \DateTime $createdAt; @@ -45,7 +51,8 @@ final class SubnetOutput extends AbstractOutput $this->ipAddress = $subnet->getIpAddress(); $this->nextServer = $subnet->getNextServer(); $this->bootFileName = $subnet->getBootFileName(); - + $this->synchronized = $subnet->isSynchronized(); + $this->serverId = $subnet->getServerId(); $this->clients = $subnet->getClients()->map( fn(Client $client) => new ClientOutput($client) diff --git a/src/Entity/Client.php b/src/Entity/Client.php index 4c4a2ce..28b6e72 100644 --- a/src/Entity/Client.php +++ b/src/Entity/Client.php @@ -64,6 +64,7 @@ class Client extends AbstractEntity private ?OgRepository $repository = null; #[ORM\ManyToOne(inversedBy: 'clients')] + #[ORM\JoinColumn( onDelete: 'SET NULL')] private ?Subnet $subnet = null; #[ORM\ManyToOne(inversedBy: 'clients')] private ?OgLive $ogLive = null; diff --git a/src/Entity/Subnet.php b/src/Entity/Subnet.php index 719de65..fe9dd2d 100644 --- a/src/Entity/Subnet.php +++ b/src/Entity/Subnet.php @@ -11,6 +11,7 @@ use Doctrine\ORM\Mapping as ORM; class Subnet extends AbstractEntity { use NameableTrait; + use SynchronizedTrait; #[ORM\Column(length: 255)] private ?string $netmask = null; @@ -36,6 +37,9 @@ class Subnet extends AbstractEntity #[ORM\OneToMany(mappedBy: 'subnet', targetEntity: Client::class)] private Collection $clients; + #[ORM\Column(nullable: true)] + private ?int $serverId = null; + public function __construct() { parent::__construct(); @@ -164,7 +168,6 @@ class Subnet extends AbstractEntity public function removeClient(Client $client): static { if ($this->clients->removeElement($client)) { - // set the owning side to null (unless already changed) if ($client->getSubnet() === $this) { $client->setSubnet(null); } @@ -172,4 +175,16 @@ class Subnet extends AbstractEntity return $this; } + + public function getServerId(): ?int + { + return $this->serverId; + } + + public function setServerId(?int $serverId): static + { + $this->serverId = $serverId; + + return $this; + } } diff --git a/src/Service/OgBoot/ConfigService.php b/src/Service/OgDhcp/StatusService.php similarity index 72% rename from src/Service/OgBoot/ConfigService.php rename to src/Service/OgDhcp/StatusService.php index 8b247b9..6e434bf 100644 --- a/src/Service/OgBoot/ConfigService.php +++ b/src/Service/OgDhcp/StatusService.php @@ -1,6 +1,6 @@ false, // Ignorar la verificación del certificado SSL - 'verify_host' => false, // Ignorar la verificación del nombre del host + 'verify_peer' => false, + 'verify_host' => false, ]); try { - $response = $httpClient->request('GET', $this->ogBootApiUrl.'/ogboot/v1/config', [ + $response = $httpClient->request('GET', $this->ogDhcpApiUrl.'/ogdhcp/v1/status', [ 'headers' => [ 'accept' => 'application/json', ], diff --git a/src/Service/Utils/GetIpAddressAndNetmaskFromCIDRService.php b/src/Service/Utils/GetIpAddressAndNetmaskFromCIDRService.php new file mode 100644 index 0000000..67e1c37 --- /dev/null +++ b/src/Service/Utils/GetIpAddressAndNetmaskFromCIDRService.php @@ -0,0 +1,18 @@ + $ip, + 'mask' => $mask + ]; + } +} \ No newline at end of file