- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
277 lines
10 KiB
PHP
277 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Queue\Services;
|
|
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Queue\Contracts\JobChainManagerInterface;
|
|
use App\Framework\Queue\Contracts\JobDependencyManagerInterface;
|
|
use App\Framework\Queue\Contracts\QueueInterface;
|
|
use App\Framework\Queue\ValueObjects\Job;
|
|
|
|
final readonly class DependencyResolutionEngine
|
|
{
|
|
public function __construct(
|
|
private JobDependencyManagerInterface $dependencyManager,
|
|
private JobChainManagerInterface $chainManager,
|
|
private QueueInterface $queue,
|
|
private Logger $logger
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Process job completion and resolve any dependent jobs that can now be executed
|
|
*/
|
|
public function resolveJobCompletion(string $jobId, bool $successful = true): array
|
|
{
|
|
$this->logger->info('Starting dependency resolution for completed job', [
|
|
'job_id' => $jobId,
|
|
'successful' => $successful,
|
|
]);
|
|
|
|
$resolvedJobs = [];
|
|
|
|
// 1. Resolve individual job dependencies
|
|
$dependentJobs = $this->dependencyManager->resolveJobCompletion($jobId, $successful);
|
|
|
|
foreach ($dependentJobs as $dependentJobId) {
|
|
$resolvedJobs[] = [
|
|
'job_id' => $dependentJobId,
|
|
'type' => 'dependency_resolved',
|
|
'trigger_job' => $jobId,
|
|
];
|
|
}
|
|
|
|
// 2. Handle job chain progression
|
|
$this->chainManager->handleJobCompletion($jobId, $successful);
|
|
|
|
// Get next jobs in any chains this job belongs to
|
|
$chains = $this->chainManager->getChainsForJob($jobId);
|
|
|
|
foreach ($chains as $chainEntry) {
|
|
if ($chainEntry->isRunning()) {
|
|
$nextJobId = $this->chainManager->getNextJobInChain($chainEntry->chainId, $jobId);
|
|
|
|
if ($nextJobId !== null) {
|
|
// Check if the next job has any other unsatisfied dependencies
|
|
if ($this->dependencyManager->canJobBeExecuted($nextJobId)) {
|
|
$resolvedJobs[] = [
|
|
'job_id' => $nextJobId,
|
|
'type' => 'chain_progression',
|
|
'chain_id' => $chainEntry->chainId,
|
|
'trigger_job' => $jobId,
|
|
];
|
|
} else {
|
|
$this->logger->info('Next job in chain has unsatisfied dependencies', [
|
|
'chain_id' => $chainEntry->chainId,
|
|
'next_job_id' => $nextJobId,
|
|
'unsatisfied_deps' => count($this->dependencyManager->getUnsatisfiedDependencies($nextJobId)),
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->logger->info('Dependency resolution completed', [
|
|
'trigger_job_id' => $jobId,
|
|
'resolved_jobs_count' => count($resolvedJobs),
|
|
'resolved_jobs' => array_column($resolvedJobs, 'job_id'),
|
|
]);
|
|
|
|
return $resolvedJobs;
|
|
}
|
|
|
|
/**
|
|
* Get all jobs that are ready to be executed (no unsatisfied dependencies)
|
|
*/
|
|
public function getExecutableJobs(): array
|
|
{
|
|
$readyJobs = $this->dependencyManager->getReadyJobs();
|
|
|
|
$this->logger->debug('Found jobs ready for execution', [
|
|
'ready_jobs_count' => count($readyJobs),
|
|
'ready_jobs' => $readyJobs,
|
|
]);
|
|
|
|
return $readyJobs;
|
|
}
|
|
|
|
/**
|
|
* Validate a job dependency graph for circular dependencies
|
|
*/
|
|
public function validateDependencyGraph(array $jobIds): array
|
|
{
|
|
$validationResults = [];
|
|
|
|
foreach ($jobIds as $jobId) {
|
|
$hasCircularDeps = $this->dependencyManager->hasCircularDependencies($jobId);
|
|
|
|
if ($hasCircularDeps) {
|
|
$dependencyChain = $this->dependencyManager->getDependencyChain($jobId);
|
|
|
|
$validationResults[] = [
|
|
'job_id' => $jobId,
|
|
'valid' => false,
|
|
'error' => 'circular_dependency',
|
|
'dependency_chain' => $dependencyChain,
|
|
];
|
|
|
|
$this->logger->warning('Circular dependency detected', [
|
|
'job_id' => $jobId,
|
|
'dependency_chain' => $dependencyChain,
|
|
]);
|
|
} else {
|
|
$validationResults[] = [
|
|
'job_id' => $jobId,
|
|
'valid' => true,
|
|
];
|
|
}
|
|
}
|
|
|
|
return $validationResults;
|
|
}
|
|
|
|
/**
|
|
* Get dependency analysis for a job
|
|
*/
|
|
public function analyzeDependencies(string $jobId): array
|
|
{
|
|
$dependencies = $this->dependencyManager->getDependencies($jobId);
|
|
$dependents = $this->dependencyManager->getDependents($jobId);
|
|
$unsatisfiedDeps = $this->dependencyManager->getUnsatisfiedDependencies($jobId);
|
|
$dependencyChain = $this->dependencyManager->getDependencyChain($jobId);
|
|
$canExecute = $this->dependencyManager->canJobBeExecuted($jobId);
|
|
|
|
return [
|
|
'job_id' => $jobId,
|
|
'can_execute' => $canExecute,
|
|
'direct_dependencies' => array_map(fn ($dep) => [
|
|
'depends_on_job_id' => $dep->dependsOnJobId,
|
|
'dependency_type' => $dep->dependencyType,
|
|
'is_satisfied' => $dep->isSatisfied,
|
|
'condition' => $dep->conditionExpression,
|
|
], $dependencies),
|
|
'dependent_jobs' => array_map(fn ($dep) => [
|
|
'dependent_job_id' => $dep->dependentJobId,
|
|
'dependency_type' => $dep->dependencyType,
|
|
'is_satisfied' => $dep->isSatisfied,
|
|
], $dependents),
|
|
'unsatisfied_dependencies' => array_map(fn ($dep) => [
|
|
'depends_on_job_id' => $dep->dependsOnJobId,
|
|
'dependency_type' => $dep->dependencyType,
|
|
'condition' => $dep->conditionExpression,
|
|
], $unsatisfiedDeps),
|
|
'full_dependency_chain' => $dependencyChain,
|
|
'statistics' => [
|
|
'total_dependencies' => count($dependencies),
|
|
'satisfied_dependencies' => count($dependencies) - count($unsatisfiedDeps),
|
|
'unsatisfied_dependencies' => count($unsatisfiedDeps),
|
|
'total_dependents' => count($dependents),
|
|
'chain_depth' => count($dependencyChain),
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get analysis for job chains
|
|
*/
|
|
public function analyzeChains(string $jobId): array
|
|
{
|
|
$chains = $this->chainManager->getChainsForJob($jobId);
|
|
$chainAnalysis = [];
|
|
|
|
foreach ($chains as $chainEntry) {
|
|
$progress = $this->chainManager->getChainProgress($chainEntry->chainId);
|
|
$nextJob = $this->chainManager->getNextJobInChain($chainEntry->chainId, $jobId);
|
|
|
|
$chainAnalysis[] = [
|
|
'chain_id' => $chainEntry->chainId,
|
|
'name' => $chainEntry->name,
|
|
'status' => $chainEntry->status,
|
|
'execution_mode' => $chainEntry->executionMode,
|
|
'stop_on_failure' => $chainEntry->stopOnFailure,
|
|
'progress' => $progress,
|
|
'next_job_after_current' => $nextJob,
|
|
'job_position' => $this->getJobPositionInChain($jobId, $chainEntry),
|
|
'total_jobs' => count($chainEntry->getJobIdsArray()),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'job_id' => $jobId,
|
|
'chains' => $chainAnalysis,
|
|
'total_chains' => count($chains),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Comprehensive dependency health check
|
|
*/
|
|
public function performHealthCheck(): array
|
|
{
|
|
$this->logger->info('Starting dependency system health check');
|
|
|
|
$activeChains = $this->chainManager->getActiveChains();
|
|
$pendingChains = $this->chainManager->getPendingChains();
|
|
$readyJobs = $this->dependencyManager->getReadyJobs();
|
|
|
|
// Check for potential issues
|
|
$issues = [];
|
|
|
|
// Check for stalled chains (running for too long)
|
|
foreach ($activeChains as $chain) {
|
|
if ($chain->startedAt) {
|
|
$startTime = strtotime($chain->startedAt);
|
|
$hoursRunning = (time() - $startTime) / 3600;
|
|
|
|
if ($hoursRunning > 24) { // Configurable threshold
|
|
$issues[] = [
|
|
'type' => 'stalled_chain',
|
|
'chain_id' => $chain->chainId,
|
|
'hours_running' => round($hoursRunning, 2),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for jobs with many unsatisfied dependencies (potential deadlocks)
|
|
foreach ($readyJobs as $jobId) {
|
|
$analysis = $this->analyzeDependencies($jobId);
|
|
if ($analysis['statistics']['unsatisfied_dependencies'] > 10) { // Configurable threshold
|
|
$issues[] = [
|
|
'type' => 'many_unsatisfied_dependencies',
|
|
'job_id' => $jobId,
|
|
'unsatisfied_count' => $analysis['statistics']['unsatisfied_dependencies'],
|
|
];
|
|
}
|
|
}
|
|
|
|
$healthReport = [
|
|
'status' => count($issues) === 0 ? 'healthy' : 'issues_detected',
|
|
'timestamp' => date('Y-m-d H:i:s'),
|
|
'statistics' => [
|
|
'active_chains' => count($activeChains),
|
|
'pending_chains' => count($pendingChains),
|
|
'ready_jobs' => count($readyJobs),
|
|
'detected_issues' => count($issues),
|
|
],
|
|
'issues' => $issues,
|
|
];
|
|
|
|
$this->logger->info('Dependency system health check completed', [
|
|
'status' => $healthReport['status'],
|
|
'issues_count' => count($issues),
|
|
]);
|
|
|
|
return $healthReport;
|
|
}
|
|
|
|
private function getJobPositionInChain(string $jobId, object $chainEntry): ?int
|
|
{
|
|
$jobIds = $chainEntry->getJobIdsArray();
|
|
|
|
return array_search($jobId, $jobIds, true) ?: null;
|
|
}
|
|
}
|