refs #2585. CommandTask schedule. Update several scripts with new logic
testing/ogcore-api/pipeline/head There was a failure building this commit Details

develop
Manuel Aranda Rosales 2025-08-04 09:49:07 +02:00
parent c4870223e9
commit f627f0a86d
11 changed files with 612 additions and 103 deletions

View File

@ -4,7 +4,21 @@ declare(strict_types=1);
namespace App\Command;
use App\Controller\OgAgent\CreateImageAction;
use App\Controller\OgAgent\DeployImageAction;
use App\Controller\OgAgent\PartitionAssistantAction;
use App\Controller\OgAgent\RunScriptAction;
use App\Dto\Input\CommandExecuteInput;
use App\Dto\Input\DeployImageInput;
use App\Dto\Input\PartitionInput;
use App\Dto\Input\PartitionPostInput;
use App\Dto\Output\ClientOutput;
use App\Entity\Client;
use App\Entity\Image;
use App\Entity\ImageImageRepository;
use App\Entity\Partition;
use App\Entity\Trace;
use App\Model\CommandTypes;
use App\Model\TraceStatus;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
@ -17,7 +31,11 @@ use Symfony\Component\Console\Style\SymfonyStyle;
class ExecutePendingTracesCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $entityManager
private readonly EntityManagerInterface $entityManager,
private readonly CreateImageAction $createImageAction,
private readonly DeployImageAction $deployImageAction,
private readonly PartitionAssistantAction $partitionAssistantAction,
private readonly RunScriptAction $runScriptAction
) {
parent::__construct();
}
@ -28,22 +46,271 @@ class ExecutePendingTracesCommand extends Command
$startTime = microtime(true);
$traces = $this->entityManager->getRepository(Trace::class)
->findBy(['status' => TraceStatus::PENDING]);
->createQueryBuilder('t')
->select('t')
->where('t.status = :status')
->setParameter('status', TraceStatus::PENDING)
->andWhere('t.id IN (
SELECT MIN(t2.id)
FROM App\Entity\Trace t2
WHERE t2.status = :status
GROUP BY t2.client
)')
->orderBy('t.id', 'ASC')
->getQuery()
->getResult();
$count = count($traces);
$io->info("Found $count pending traces");
$io->info("Found $count pending trace(s) - processing the latest one");
$processedCount = 0;
$errorCount = 0;
foreach ($traces as $trace) {
$trace->setStatus(TraceStatus::IN_PROGRESS);
$this->entityManager->persist($trace);
try {
$io->info("Processing trace {$trace->getUuid()} with command: {$trace->getCommand()}");
$result = $this->executeTrace($trace);
if ($result) {
$processedCount++;
$io->success("Successfully processed trace {$trace->getUuid()}");
} else {
$errorCount++;
$io->error("Failed to process trace {$trace->getUuid()}");
}
} catch (\Exception $e) {
$errorCount++;
$io->error("Error processing trace {$trace->getUuid()}: " . $e->getMessage());
$trace->setStatus(TraceStatus::FAILED);
$trace->setOutput($e->getMessage());
$trace->setFinishedAt(new \DateTime());
$this->entityManager->persist($trace);
}
}
$this->entityManager->flush();
$executionTime = microtime(true) - $startTime;
$io->success("Updated $count traces to IN_PROGRESS status");
$io->success("Processed $processedCount traces successfully, $errorCount failed");
$io->note("Execution time: " . round($executionTime, 3) . "s");
return Command::SUCCESS;
}
private function executeTrace(Trace $trace): bool
{
$command = $trace->getCommand();
$input = $trace->getInput() ?? [];
$client = $trace->getClient();
if (!$client) {
throw new \Exception("No client associated with trace");
}
$trace->setExecutedAt(new \DateTime());
$this->entityManager->persist($trace);
$this->entityManager->flush();
try {
switch ($command) {
case CommandTypes::CREATE_IMAGE:
return $this->executeCreateImage($trace, $input);
case CommandTypes::DEPLOY_IMAGE:
return $this->executeDeployImage($trace, $input);
case CommandTypes::PARTITION_AND_FORMAT:
return $this->executePartitionAssistant($trace, $input);
case CommandTypes::RUN_SCRIPT:
return $this->executeRunScript($trace, $input);
default:
throw new \Exception("Unsupported command type: $command");
}
} catch (\Exception $e) {
$trace->setStatus(TraceStatus::FAILED);
$trace->setOutput($e->getMessage());
$trace->setFinishedAt(new \DateTime());
$this->entityManager->persist($trace);
$this->entityManager->flush();
throw $e;
}
}
private function executeCreateImage(Trace $trace, array $input): bool
{
$client = $trace->getClient();
if (!isset($input['image'])) {
throw new \Exception("Image UUID not found in trace input");
}
$image = $this->entityManager->getRepository(Image::class)
->findOneBy(['uuid' => $input['image']]);
if (!$image) {
throw new \Exception("Image not found with UUID: {$input['image']}");
}
$partition = null;
if (isset($input['diskNumber']) && isset($input['partitionNumber'])) {
$partition = $this->entityManager->getRepository(Partition::class)
->findOneBy([
'client' => $client,
'diskNumber' => $input['diskNumber'],
'partitionNumber' => $input['partitionNumber']
]);
if (!$partition) {
throw new \Exception("Partition not found for client {$client->getUuid()} with disk {$input['diskNumber']} and partition {$input['partitionNumber']}");
}
}
try {
$response = $this->createImageAction->__invoke(
queue: false,
image: $image,
partition: $partition,
client: $client,
gitRepositoryName: $input['gitRepositoryName'] ?? null,
existingTrace: $trace
);
if ($response->getStatusCode() === 200) {
$trace->setStatus(TraceStatus::SUCCESS);
$trace->setFinishedAt(new \DateTime());
$this->entityManager->persist($trace);
$this->entityManager->flush();
return true;
}
return false;
} catch (\Exception $e) {
return false;
}
}
private function executeDeployImage(Trace $trace, array $input): bool
{
$client = $trace->getClient();
if (!isset($input['imageImageRepository'])) {
throw new \Exception("ImageImageRepository UUID not found in trace input");
}
$imageImageRepository = $this->entityManager->getRepository(ImageImageRepository::class)
->findOneBy(['uuid' => $input['imageImageRepository']]);
if (!$imageImageRepository) {
throw new \Exception("ImageImageRepository not found with UUID: {$input['imageImageRepository']}");
}
$deployInput = new DeployImageInput();
$deployInput->method = $input['method'] ?? 'unicast';
$deployInput->type = $input['type'] ?? 'monolithic';
$deployInput->diskNumber = $input['diskNumber'] ?? 1;
$deployInput->partitionNumber = $input['partitionNumber'] ?? 1;
$deployInput->mcastMode = $input['mcastMode'] ?? 'duplex';
$deployInput->mcastSpeed = $input['mcastSpeed'] ?? 100;
$deployInput->mcastPort = $input['mcastPort'] ?? 8000;
$deployInput->mcastIp = $input['mcastIp'] ?? '224.0.0.1';
$deployInput->maxClients = $input['maxClients'] ?? 10;
$deployInput->maxTime = $input['maxTime'] ?? 3600;
$deployInput->p2pMode = $input['p2pMode'] ?? 'seed';
$deployInput->p2pTime = $input['p2pTime'] ?? 300;
$jobId = $this->deployImageAction->__invoke(
imageImageRepository: $imageImageRepository,
input: $deployInput,
client: $client
);
$trace->setJobId($jobId);
$this->entityManager->persist($trace);
$this->entityManager->flush();
return true;
}
private function executePartitionAssistant(Trace $trace, array $input): bool
{
$client = $trace->getClient();
$partitionInput = new PartitionPostInput();
$partitionInput->clients = [new ClientOutput($client)];
$partitions = [];
$diskNumber = 1;
foreach ($input as $item) {
if (isset($item['dis'])) {
$diskNumber = (int)$item['dis'];
} elseif (isset($item['par'])) {
$partitionInputObj = new PartitionInput();
$partitionInputObj->diskNumber = $diskNumber;
$partitionInputObj->partitionNumber = (int)$item['par'];
$partitionInputObj->partitionCode = $item['cpt'] ?? 'LINUX';
$partitionInputObj->size = (float)($item['tam'] / 1024);
$partitionInputObj->filesystem = $item['sfi'] ?? 'EXT4';
$partitionInputObj->format = ($item['ope'] ?? '0') === '1';
$partitions[] = $partitionInputObj;
}
}
$partitionInput->partitions = $partitions;
$partitionInput->queue = false;
try {
$response = $this->partitionAssistantAction->__invoke($partitionInput, $trace);
if ($response->getStatusCode() === 200) {
$trace->setStatus(TraceStatus::SUCCESS);
$trace->setFinishedAt(new \DateTime());
$this->entityManager->persist($trace);
$this->entityManager->flush();
return true;
}
return false;
} catch (\Exception $e) {
return false;
}
}
private function executeRunScript(Trace $trace, array $input): bool
{
$client = $trace->getClient();
if (!isset($input['script'])) {
throw new \Exception("Script not found in trace input");
}
$commandExecuteInput = new CommandExecuteInput();
$commandExecuteInput->clients = [new ClientOutput($client)];
$commandExecuteInput->script = $input['script'];
$commandExecuteInput->queue = false;
try {
$response = $this->runScriptAction->__invoke($commandExecuteInput, $trace);
if ($response->getStatusCode() === 200) {
$trace->setStatus(TraceStatus::SUCCESS);
$trace->setFinishedAt(new \DateTime());
$this->entityManager->persist($trace);
$this->entityManager->flush();
return true;
}
return false;
} catch (\Exception $e) {
return false;
}
}
}

View File

@ -4,13 +4,23 @@ declare(strict_types=1);
namespace App\Command;
use App\Controller\OgAgent\CreateImageAction;
use App\Controller\OgAgent\DeployImageAction;
use App\Controller\OgAgent\PartitionAssistantAction;
use App\Controller\OgAgent\RunScriptAction;
use App\Dto\Input\CommandExecuteInput;
use App\Dto\Input\DeployImageInput;
use App\Dto\Input\PartitionInput;
use App\Dto\Input\PartitionPostInput;
use App\Dto\Output\ClientOutput;
use App\Entity\CommandTask;
use App\Entity\Image;
use App\Entity\ImageImageRepository;
use App\Entity\Partition;
use App\Entity\Trace;
use App\Model\ClientStatus;
use App\Model\CommandTypes;
use App\Model\TraceStatus;
use App\Repository\CommandTaskRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
@ -27,6 +37,8 @@ class RunScheduledCommandTasksCommand extends Command
private readonly EntityManagerInterface $entityManager,
private readonly RunScriptAction $runScriptAction,
private readonly DeployImageAction $deployImageAction,
private readonly CreateImageAction $createImageAction,
private readonly PartitionAssistantAction $partitionAssistantAction,
)
{
parent::__construct();
@ -38,47 +50,39 @@ class RunScheduledCommandTasksCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$now = new \DateTimeImmutable('now +2 hours');
$now->setTimezone(new \DateTimeZone('Europe/Madrid'));
$nowMinute = $now->format('Y-m-d H:i');
$now = new \DateTimeImmutable('now');
$tasks = $this->commandTaskRepository->findAll();
foreach ($tasks as $task) {
/** @var CommandTask $task */
$nextExecution = $task->getNextExecution();
if (!$nextExecution) {
continue;
}
$taskMinute = $nextExecution->format('Y-m-d H:i');
$difference = $now->getTimestamp() - $nextExecution->getTimestamp();
if ($taskMinute === $nowMinute) {
$output->writeln("Now: " . $now->format('Y-m-d H:i:s T'));
$output->writeln("NextExecution: " . $nextExecution->format('Y-m-d H:i:s T'));
$output->writeln("Diferencia: $difference segundos para la tarea {$task->getName()}");
if (abs($difference) < 30) {
$output->writeln("Ejecutando tarea: " . $task->getName());
$scripts = $task->getCommandTaskScripts()->toArray();
usort($scripts, fn($a, $b) => $a->getExecutionOrder() <=> $b->getExecutionOrder());
foreach ($scripts as $script) {
$output->writeln(" - Ejecutando script de tipo {$script->getType()} con orden {$script->getExecutionOrder()}");
$output->writeln(" - Creando traza para script de tipo {$script->getType()} con orden {$script->getExecutionOrder()}");
if ($script->getType() === 'run-script') {
$input = new CommandExecuteInput();
foreach ($task->getOrganizationalUnit()?->getClients() as $client) {
if ($client->getStatus() !== ClientStatus::OG_LIVE) {
continue;
}
$input->clients[] = new ClientOutput($client);
}
$input->script = $script->getContent();
$this->runScriptAction->__invoke($input);
}
$this->createTraceForScript($script, $task, $output);
}
$task->setLastExecution(new \DateTime());
$task->setLastExecution(new \DateTimeImmutable('now'));
$task->setNextExecution($task->calculateNextExecutionDate());
$this->entityManager->persist($task);
}
}
@ -86,4 +90,134 @@ class RunScheduledCommandTasksCommand extends Command
$this->entityManager->flush();
return Command::SUCCESS;
}
private function createTraceForScript($script, CommandTask $task, OutputInterface $output): void
{
$scriptParameters = $script->getParameters();
if ($task->getScope() !== 'clients') {
$clients = $task->getOrganizationalUnit()?->getClients();
} else {
$clients = $task->getClients();
}
foreach ($clients as $client) {
$trace = new Trace();
$trace->setClient($client);
$trace->setStatus(TraceStatus::PENDING);
$trace->setExecutedAt(new \DateTime());
switch ($script->getType()) {
case 'run-script':
$trace->setCommand(CommandTypes::RUN_SCRIPT);
$trace->setInput([
'script' => $script->getContent()
]);
break;
case 'deploy-image':
$trace->setCommand(CommandTypes::DEPLOY_IMAGE);
$trace->setInput([
'imageImageRepository' => $scriptParameters['imageImageRepositoryUuid'],
'method' => $scriptParameters['method'] ?? 'unicast',
'type' => $scriptParameters['type'] ?? 'monolithic',
'diskNumber' => $scriptParameters['diskNumber'] ?? 1,
'partitionNumber' => $scriptParameters['partitionNumber'] ?? 1,
'mcastMode' => $scriptParameters['mcastMode'] ?? 'duplex',
'mcastSpeed' => $scriptParameters['mcastSpeed'] ?? 100,
'mcastPort' => $scriptParameters['mcastPort'] ?? 8000,
'mcastIp' => $scriptParameters['mcastIp'] ?? '224.0.0.1',
'maxClients' => $scriptParameters['maxClients'] ?? 10,
'maxTime' => $scriptParameters['maxTime'] ?? 3600,
'p2pMode' => $scriptParameters['p2pMode'] ?? 'seed',
'p2pTime' => $scriptParameters['p2pTime'] ?? 300
]);
break;
case 'create-image':
$trace->setCommand(CommandTypes::CREATE_IMAGE);
$trace->setInput([
'image' => $scriptParameters['imageUuid'],
'diskNumber' => $scriptParameters['diskNumber'] ?? null,
'partitionNumber' => $scriptParameters['partitionNumber'] ?? null,
'gitRepositoryName' => $scriptParameters['gitRepositoryName'] ?? null
]);
break;
case 'partition-assistant':
$trace->setCommand(CommandTypes::PARTITION_AND_FORMAT);
$trace->setInput($scriptParameters);
break;
default:
$output->writeln(" - Tipo de script no soportado: {$script->getType()}");
continue 2; // Salta al siguiente cliente
}
$this->entityManager->persist($trace);
$output->writeln(" - Traza creada para cliente {$client->getUuid()} con comando {$trace->getCommand()}");
}
}
private function executePartitionAssistant($script, CommandTask $task, OutputInterface $output): void
{
$scriptParameters = $script->getParameters();
$output->writeln(" - Debug: Parameters = " . ($scriptParameters ? json_encode($scriptParameters) : 'null'));
if (!$scriptParameters) {
$output->writeln(" - Error: Parámetros del script vacíos o nulos");
return;
}
if (!is_array($scriptParameters)) {
$output->writeln(" - Error: Los parámetros deben ser un array");
return;
}
foreach ($task->getOrganizationalUnit()?->getClients() as $client) {
$partitionInput = new PartitionPostInput();
$partitionInput->clients = [new ClientOutput($client)];
$partitions = [];
foreach ($scriptParameters as $partitionData) {
if (isset($partitionData['removed']) && $partitionData['removed']) {
continue;
}
if (!isset($partitionData['size']) || $partitionData['size'] <= 0) {
continue;
}
$partitionInputObj = new PartitionInput();
$partitionInputObj->diskNumber = $partitionData['diskNumber'] ?? 1;
$partitionInputObj->partitionNumber = $partitionData['partitionNumber'] ?? 1;
$partitionInputObj->partitionCode = $partitionData['partitionCode'] ?? 'LINUX';
$partitionInputObj->size = (float)($partitionData['size'] / 1024);
$partitionInputObj->filesystem = $partitionData['filesystem'] ?? 'EXT4';
$partitionInputObj->format = $partitionData['format'] ?? false;
$partitionInputObj->memoryUsage = $partitionData['memoryUsage'] ?? 0;
$partitions[] = $partitionInputObj;
}
if (empty($partitions)) {
$output->writeln(" - Warning: No hay particiones válidas para procesar");
continue;
}
$partitionInput->partitions = $partitions;
$partitionInput->queue = false;
try {
$response = $this->partitionAssistantAction->__invoke($partitionInput, null);
$output->writeln(" - Partition assistant iniciado para cliente {$client->getUuid()}");
} catch (\Exception $e) {
$output->writeln(" - Error en partition assistant para cliente {$client->getUuid()}: " . $e->getMessage());
}
}
}
}

View File

@ -42,7 +42,7 @@ class CreateImageAction extends AbstractOgAgentController
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function __invoke(bool $queue, Image $image, ?Partition $partition = null, ?Client $client = null, ?string $gitRepositoryName = null): JsonResponse
public function __invoke(bool $queue, Image $image, ?Partition $partition = null, ?Client $client = null, ?string $gitRepositoryName = null, ?Trace $existingTrace = null): JsonResponse
{
$client = $client ?? $image->getClient();
@ -77,11 +77,11 @@ class CreateImageAction extends AbstractOgAgentController
$this->entityManager->persist($imageImageRepository);
return $this->createMonolithicImage($imageImageRepository, $partitionInfo, $image, $repository, $client, $queue);
return $this->createMonolithicImage($imageImageRepository, $partitionInfo, $image, $repository, $client, $queue, $existingTrace);
} else {
$repository = $image->getClient()->getRepository();
return $this->createGitImage($image, $partitionInfo, $repository, $queue, $gitRepositoryName);
return $this->createGitImage($image, $partitionInfo, $repository, $queue, $gitRepositoryName, $existingTrace);
}
}
@ -97,7 +97,8 @@ class CreateImageAction extends AbstractOgAgentController
Image $image,
ImageRepository $repository,
?Client $client = null,
bool $queue = false
bool $queue = false,
?Trace $existingTrace = null
): JsonResponse
{
if (!isset($partitionInfo['numDisk'], $partitionInfo['numPartition'], $partitionInfo['partitionCode'], $partitionInfo['filesystem'])) {
@ -157,6 +158,8 @@ class CreateImageAction extends AbstractOgAgentController
'image' => $image->getUuid(),
'partitionCode' => $partitionInfo['partitionCode'],
'partitionType' => $partitionInfo['filesystem'],
'partitionNumber' => $partitionInfo['numPartition'],
'diskNumber' => $partitionInfo['numDisk'],
'repository' => $repository->getIp(),
'name' => $image->getName().'_v'.$imageImageRepository->getVersion(),
];
@ -188,17 +191,27 @@ class CreateImageAction extends AbstractOgAgentController
'image' => $image->getUuid(),
'partitionCode' => $partitionInfo['partitionCode'],
'partitionType' => $partitionInfo['filesystem'],
'partitionNumber' => $partitionInfo['numPartition'],
'diskNumber' => $partitionInfo['numDisk'],
'repository' => $repository->getIp(),
'name' => $image->getName().'_v'.$imageImageRepository->getVersion(),
];
$this->createService->__invoke(
$image->getClient(),
CommandTypes::CREATE_IMAGE,
TraceStatus::IN_PROGRESS,
$jobId,
$inputData
);
if ($existingTrace) {
$existingTrace->setStatus(TraceStatus::IN_PROGRESS);
$existingTrace->setJobId($jobId);
$existingTrace->setInput($inputData);
$this->entityManager->persist($existingTrace);
$this->entityManager->flush();
} else {
$this->createService->__invoke(
$image->getClient(),
CommandTypes::CREATE_IMAGE,
TraceStatus::IN_PROGRESS,
$jobId,
$inputData
);
}
return new JsonResponse(data: $image, status: Response::HTTP_OK);
} catch (Exception $e) {
@ -226,7 +239,8 @@ class CreateImageAction extends AbstractOgAgentController
array $partitionInfo,
ImageRepository $repository,
bool $queue = false,
?string $gitRepositoryName = null
?string $gitRepositoryName = null,
?Trace $existingTrace = null
): JsonResponse
{
if (!isset($partitionInfo['numDisk'], $partitionInfo['numPartition'], $partitionInfo['partitionCode'], $partitionInfo['filesystem'])) {
@ -283,6 +297,8 @@ class CreateImageAction extends AbstractOgAgentController
'image' => $image->getUuid(),
'partitionCode' => $partitionInfo['partitionCode'],
'partitionType' => $partitionInfo['filesystem'],
'partitionNumber' => $partitionInfo['numPartition'],
'diskNumber' => $partitionInfo['numDisk'],
'repository' => $repository->getIp(),
'name' => $image->getName(),
];
@ -313,17 +329,27 @@ class CreateImageAction extends AbstractOgAgentController
'image' => $image->getUuid(),
'partitionCode' => $partitionInfo['partitionCode'],
'partitionType' => $partitionInfo['filesystem'],
'partitionNumber' => $partitionInfo['numPartition'],
'diskNumber' => $partitionInfo['numDisk'],
'repository' => $repository->getIp(),
'name' => $image->getName(),
];
$this->createService->__invoke(
$image->getClient(),
CommandTypes::CREATE_IMAGE_GIT,
TraceStatus::IN_PROGRESS,
$jobId,
$inputData
);
if ($existingTrace) {
$existingTrace->setStatus(TraceStatus::IN_PROGRESS);
$existingTrace->setJobId($jobId);
$existingTrace->setInput($inputData);
$this->entityManager->persist($existingTrace);
$this->entityManager->flush();
} else {
$this->createService->__invoke(
$image->getClient(),
CommandTypes::CREATE_IMAGE_GIT,
TraceStatus::IN_PROGRESS,
$jobId,
$inputData
);
}
return new JsonResponse(data: $image, status: Response::HTTP_OK);
} catch (Exception $e) {

View File

@ -37,7 +37,7 @@ class PartitionAssistantAction extends AbstractOgAgentController
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function __invoke(PartitionPostInput $input): JsonResponse
public function __invoke(PartitionPostInput $input, ?Trace $existingTrace = null): JsonResponse
{
$partitions = $input->partitions;
@ -125,7 +125,15 @@ class PartitionAssistantAction extends AbstractOgAgentController
$this->entityManager->persist($client);
$this->entityManager->flush();
$this->createService->__invoke($client, CommandTypes::PARTITION_AND_FORMAT, TraceStatus::IN_PROGRESS, $jobId, $data);
if ($existingTrace) {
$existingTrace->setStatus(TraceStatus::IN_PROGRESS);
$existingTrace->setJobId($jobId);
$existingTrace->setInput($data);
$this->entityManager->persist($existingTrace);
$this->entityManager->flush();
} else {
$this->createService->__invoke($client, CommandTypes::PARTITION_AND_FORMAT, TraceStatus::IN_PROGRESS, $jobId, $data);
}
}
}

View File

@ -5,6 +5,7 @@ namespace App\Controller\OgAgent;
use App\Dto\Input\CommandExecuteInput;
use App\Dto\Input\MultipleClientsInput;
use App\Entity\Client;
use App\Entity\Trace;
use App\Model\ClientStatus;
use App\Model\CommandTypes;
use App\Model\TraceStatus;
@ -30,8 +31,10 @@ class RunScriptAction extends AbstractOgAgentController
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
*/
public function __invoke(CommandExecuteInput $input): JsonResponse
public function __invoke(CommandExecuteInput $input, ?Trace $existingTrace = null): JsonResponse
{
$clientJobs = [];
/** @var Client $clientEntity */
foreach ($input->clients as $clientEntity) {
/** @var Client $client */
@ -41,49 +44,111 @@ class RunScriptAction extends AbstractOgAgentController
throw new BadRequestHttpException('IP is required');
}
$data = [
'nfn' => 'EjecutarScript',
'scp' => base64_encode($input->script),
'ids' => '0'
];
$data = $this->buildRequestData($client, $input->script);
$response = $this->executeScript($client, $data);
$response = $this->createRequest(
method: 'POST',
url: 'https://'.$client->getIp().':8000/opengnsys/EjecutarScript',
params: [
'json' => $data,
],
token: $client->getToken(),
);
if (isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR) {
$this->logger->error('Error running script', ['client' => $client->getId(), 'error' => $response['error']]);
if ($input->queue) {
$inputData = [
'script' => $input->script,
];
$this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::PENDING, null, $inputData);
continue;
}
if ($this->isErrorResponse($response)) {
$this->handleError($client, $input, $response);
continue;
}
$this->logger->info('Powering off client', ['client' => $client->getId()]);
$jobId = $response['job_id'];
$clientJobs[(string) $client->get] = $jobId;
$this->entityManager->persist($client);
$this->entityManager->flush();
$inputData = [
'script' => $input->script,
];
$this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::IN_PROGRESS, $jobId, $inputData);
$this->handleSuccess($client, $input, $response, $existingTrace);
}
return new JsonResponse(data: [], status: Response::HTTP_OK);
return new JsonResponse(data: $clientJobs, status: Response::HTTP_OK);
}
private function buildRequestData(Client $client, string $script): array
{
$scriptBase64 = base64_encode($script);
if ($this->isLinuxOrWindows($client)) {
return [
'script' => $scriptBase64,
'client' => 'false'
];
}
if ($this->isLinuxOrWindowsSession($client)) {
return [
'script' => $scriptBase64,
'client' => 'true'
];
}
return [
'nfn' => 'EjecutarScript',
'scp' => $scriptBase64,
'ids' => '0'
];
}
private function isLinuxOrWindows(Client $client): bool
{
return $client->getStatus() === ClientStatus::LINUX || $client->getStatus() === ClientStatus::WINDOWS;
}
private function isLinuxOrWindowsSession(Client $client): bool
{
return $client->getStatus() === ClientStatus::LINUX_SESSION || $client->getStatus() === ClientStatus::WINDOWS_SESSION;
}
private function executeScript(Client $client, array $data): array
{
return $this->createRequest(
method: 'POST',
url: 'https://'.$client->getIp().':8000/opengnsys/EjecutarScript',
params: [
'json' => $data,
],
token: $client->getToken(),
);
}
private function isErrorResponse(array $response): bool
{
return isset($response['error']) && $response['code'] === Response::HTTP_INTERNAL_SERVER_ERROR;
}
private function handleError(Client $client, CommandExecuteInput $input, array $response): void
{
$this->logger->error('Error running script', [
'client' => $client->getId(),
'error' => $response['error']
]);
if ($input->queue) {
$inputData = ['script' => $input->script];
$this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::PENDING, null, $inputData);
}
}
private function handleSuccess(Client $client, CommandExecuteInput $input, array $response, ?Trace $existingTrace): void
{
$this->logger->info('Running script on client', ['client' => $client->getId()]);
$jobId = $response['job_id'];
$inputData = ['script' => $input->script];
$this->entityManager->persist($client);
$this->entityManager->flush();
if ($existingTrace) {
$this->updateExistingTrace($existingTrace, $jobId, $inputData);
} else {
$this->createService->__invoke($client, CommandTypes::RUN_SCRIPT, TraceStatus::IN_PROGRESS, $jobId, $inputData);
}
}
private function updateExistingTrace(Trace $trace, string $jobId, array $inputData): void
{
$trace->setStatus(TraceStatus::IN_PROGRESS);
$trace->setJobId($jobId);
$trace->setInput($inputData);
$this->entityManager->persist($trace);
$this->entityManager->flush();
}
}

View File

@ -57,6 +57,8 @@ class AgentSessionController extends AbstractController
return new JsonResponse(['message' => 'Invalid status'], Response::HTTP_BAD_REQUEST);
}
$client->setToken($data['secret'] ?? null);
$this->entityManager->persist($client);
$this->entityManager->flush();

View File

@ -39,7 +39,7 @@ class StatusController extends AbstractController
{
const string CREATE_IMAGE = 'RESPUESTA_CrearImagen';
const string CREATE_IMAGE_GIT = 'RESPUESTA_CrearImagenGit';
const string UPDATE_IMAGE_GIT = 'RESPUESTA_ModificarImagenGit';
const string UPDATE_IMAGE_GIT = 'RESPUESTA_ActualizarImagenGit';
const string RESTORE_IMAGE = 'RESPUESTA_RestaurarImagen';
const string RESTORE_IMAGE_GIT = 'RESPUESTA_RestaurarImagenGit';
const string CONFIGURE_IMAGE = 'RESPUESTA_Configurar';

View File

@ -69,8 +69,9 @@ final class CommandTaskInput
$this->clients[] = new ClientOutput($client);
}
}
$this->organizationalUnit = new OrganizationalUnitOutput($commandTask->getOrganizationalUnit());
if ($commandTask->getOrganizationalUnit()) {
$this->organizationalUnit = new OrganizationalUnitOutput($commandTask->getOrganizationalUnit());
}
$this->notes = $commandTask->getNotes();
$this->scope = $commandTask->getScope();
$this->content = $commandTask->getParameters();
@ -87,12 +88,15 @@ final class CommandTaskInput
$commandTask->setName($this->name);
$clientsToAdd = [];
foreach ($this->clients as $client) {
$clientsToAdd[] = $client->getEntity();
}
}
$commandTask->setOrganizationalUnit($this->organizationalUnit->getEntity());
$commandTask->setClients( $clientsToAdd ?? [] );
if ($this->organizationalUnit) {
$commandTask->setOrganizationalUnit($this->organizationalUnit->getEntity());
}
$commandTask->setClients( $clientsToAdd );
$commandTask->setNotes($this->notes);
$commandTask->setParameters($this->content);
$commandTask->setScope($this->scope);

View File

@ -16,53 +16,53 @@ use Symfony\Component\Validator\Constraints as Assert;
final class PartitionInput
{
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
public ?UuidInterface $uuid = null;
#[Groups(['partition:write'])]
public ?bool $removed = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
public ?bool $format = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The disk number of the partition', example: 1)]
public ?int $diskNumber = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The number of the partition', example: 1)]
public ?int $partitionNumber = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The code of the partition', example: "code")]
public ?string $partitionCode = null;
#[Assert\NotNull()]
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The size of the partition', example: 100)]
public ?float $size = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The cache content of the partition', example: "cache content")]
public ?string $cacheContent = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The type of the partition', example: "LINUX")]
public ?string $type = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The filesystem of the partition', example: "EXT4")]
public ?string $filesystem = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The operative system name of the partition', example: "Ubuntu")]
public ?OperativeSystemOutput $operativeSystem = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The memory usage of the partition', example: 100)]
public ?int $memoryUsage = null;
#[Groups(['partition:write'])]
#[Groups(['partition:write', 'client:write'])]
#[ApiProperty(description: 'The image of the partition')]
public ?ImageOutput $image = null;

View File

@ -50,7 +50,9 @@ final class CommandTaskOutput extends AbstractOutput
fn(Client $client) => new ClientOutput($client)
)->toArray();
$this->organizationalUnit = new OrganizationalUnitOutput($commandTask->getOrganizationalUnit());
if ($commandTask->getOrganizationalUnit()) {
$this->organizationalUnit = new OrganizationalUnitOutput($commandTask->getOrganizationalUnit());
}
$this->notes = $commandTask->getNotes();
$this->scope = $commandTask->getScope();
$this->lastExecution = $commandTask->getLastExecution();

View File

@ -73,6 +73,7 @@ class Client extends AbstractEntity
#[ORM\ManyToOne(inversedBy: 'clients')]
#[ORM\JoinColumn( onDelete: 'SET NULL')]
private ?Subnet $subnet = null;
#[ORM\ManyToOne(inversedBy: 'clients')]
#[ORM\JoinColumn( onDelete: 'SET NULL')]
private ?OgLive $ogLive = null;