feat(Production): Complete production deployment infrastructure

- 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.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -2,23 +2,19 @@
declare(strict_types=1);
use App\Framework\Queue\Contracts\QueueInterface;
use App\Framework\Queue\Contracts\JobDependencyManagerInterface;
use App\Framework\Database\EntityManagerInterface;
use App\Framework\DI\Container;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Contracts\JobChainManagerInterface;
use App\Framework\Queue\Services\JobMetricsManager;
use App\Framework\Queue\Contracts\JobDependencyManagerInterface;
use App\Framework\Queue\Contracts\QueueInterface;
use App\Framework\Queue\Services\DependencyResolutionEngine;
use App\Framework\Queue\Services\JobChainExecutionCoordinator;
use App\Framework\Queue\ValueObjects\JobDependency;
use App\Framework\Queue\ValueObjects\JobChain;
use App\Framework\Queue\ValueObjects\JobMetrics;
use App\Framework\Queue\Services\JobMetricsManager;
use App\Framework\Queue\ValueObjects\ChainExecutionMode;
use App\Framework\Queue\Entities\JobProgressEntry;
use App\Framework\Queue\Entities\JobProgressStep;
use App\Framework\Database\EntityManagerInterface;
use App\Framework\Logging\Logger;
use App\Framework\Core\Application;
use App\Framework\DI\Container;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Queue\ValueObjects\JobChain;
use App\Framework\Queue\ValueObjects\JobDependency;
use App\Framework\Queue\ValueObjects\JobMetrics;
beforeEach(function () {
// Set up test container
@@ -37,11 +33,12 @@ beforeEach(function () {
function createTestJob(string $id, string $data): object
{
return new class($id, $data) {
return new class ($id, $data) {
public function __construct(
public readonly string $id,
public readonly string $data
) {}
) {
}
};
}
@@ -112,7 +109,7 @@ test('job chain execution with sequential mode', function () {
$jobs = [
createTestJob('chain-job-1', 'Chain Job 1'),
createTestJob('chain-job-2', 'Chain Job 2'),
createTestJob('chain-job-3', 'Chain Job 3')
createTestJob('chain-job-3', 'Chain Job 3'),
];
// 2. Create job chain
@@ -137,7 +134,7 @@ test('job chain failure handling', function () {
$jobs = [
createTestJob('fail-job-1', 'Job 1'),
createTestJob('fail-job-2', 'Job 2 (will fail)'),
createTestJob('fail-job-3', 'Job 3')
createTestJob('fail-job-3', 'Job 3'),
];
// 2. Create job chain with stop on failure
@@ -181,7 +178,7 @@ test('circular dependency detection', function () {
$this->dependencyManager->addDependency($depB);
// 3. Adding the third dependency should throw an exception or be handled
expect(fn() => $this->dependencyManager->addDependency($depC))
expect(fn () => $this->dependencyManager->addDependency($depC))
->toThrow(\InvalidArgumentException::class);
});
@@ -265,7 +262,7 @@ test('queue metrics calculation', function () {
completedAt: null,
failedAt: date('Y-m-d H:i:s'),
metadata: []
)
),
];
// 2. Record all metrics
@@ -307,7 +304,7 @@ test('dead letter queue functionality', function () {
// 3. Verify dead letter detection
$failedJobs = $this->metricsManager->getFailedJobs('default', '1 hour');
$deadLetterJob = array_filter($failedJobs, fn($job) => $job->jobId === 'dead-letter-job')[0] ?? null;
$deadLetterJob = array_filter($failedJobs, fn ($job) => $job->jobId === 'dead-letter-job')[0] ?? null;
expect($deadLetterJob)->not()->toBeNull()
->and($deadLetterJob->attempts)->toBe(3)
@@ -323,7 +320,7 @@ test('system health monitoring', function () {
new JobMetrics('health-2', 'health-queue', 'completed', 1, 3, 75.0, 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
new JobMetrics('health-3', 'health-queue', 'completed', 1, 3, 100.0, 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
// One failed job
new JobMetrics('health-4', 'health-queue', 'failed', 2, 3, 25.0, 1024, 'Health test failure', date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, date('Y-m-d H:i:s'), [])
new JobMetrics('health-4', 'health-queue', 'failed', 2, 3, 25.0, 1024, 'Health test failure', date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, date('Y-m-d H:i:s'), []),
];
// 2. Record all metrics
@@ -345,7 +342,7 @@ test('performance and throughput metrics', function () {
$performanceMetrics = [
new JobMetrics('perf-1', 'perf-queue', 'completed', 1, 3, 50.0, 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
new JobMetrics('perf-2', 'perf-queue', 'completed', 1, 3, 150.0, 2 * 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
new JobMetrics('perf-3', 'perf-queue', 'completed', 1, 3, 300.0, 4 * 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, [])
new JobMetrics('perf-3', 'perf-queue', 'completed', 1, 3, 300.0, 4 * 1024 * 1024, null, date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), null, []),
];
// 2. Record performance metrics
@@ -369,4 +366,4 @@ test('performance and throughput metrics', function () {
expect($throughputStats)->toHaveKey('total_completed')
->and($throughputStats['total_completed'])->toBe(3)
->and($throughputStats)->toHaveKey('average_throughput_per_hour');
});
});

View File

@@ -2,21 +2,18 @@
declare(strict_types=1);
use App\Framework\Queue\Services\WorkerRegistry;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Entities\Worker;
use App\Framework\Queue\Services\DatabaseDistributedLock;
use App\Framework\Queue\Services\JobDistributionService;
use App\Framework\Queue\Services\WorkerHealthCheckService;
use App\Framework\Queue\Services\FailoverRecoveryService;
use App\Framework\Queue\Entities\Worker;
use App\Framework\Queue\ValueObjects\WorkerId;
use App\Framework\Queue\Services\WorkerRegistry;
use App\Framework\Queue\ValueObjects\JobId;
use App\Framework\Queue\ValueObjects\LockKey;
use App\Framework\Queue\ValueObjects\QueueName;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Logging\Logger;
/**
* Real-world scenario tests for the Distributed Processing System
@@ -64,7 +61,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::emailQueue()],
maxJobs: 20,
capabilities: ['email', 'newsletter', 'notifications']
)
),
];
$imageWorkers = [
@@ -74,7 +71,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('image-processing')],
maxJobs: 5, // Resource intensive
capabilities: ['image-resize', 'thumbnail', 'watermark']
)
),
];
$generalWorkers = [
@@ -83,7 +80,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
processId: 1001,
queues: [
QueueName::defaultQueue(),
QueueName::fromString('reports')
QueueName::fromString('reports'),
],
maxJobs: 15,
capabilities: ['pdf-generation', 'reporting', 'exports']
@@ -93,11 +90,11 @@ describe('Distributed Processing Real-World Scenarios', function () {
processId: 1002,
queues: [
QueueName::defaultQueue(),
QueueName::fromString('reports')
QueueName::fromString('reports'),
],
maxJobs: 15,
capabilities: ['pdf-generation', 'reporting', 'exports']
)
),
];
$allWorkers = array_merge($emailWorkers, $imageWorkers, $generalWorkers);
@@ -123,7 +120,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
// General processing jobs
['id' => JobId::generate(), 'queue' => QueueName::defaultQueue(), 'type' => 'invoice-generation'],
['id' => JobId::generate(), 'queue' => QueueName::fromString('reports'), 'type' => 'sales-report']
['id' => JobId::generate(), 'queue' => QueueName::fromString('reports'), 'type' => 'sales-report'],
];
// Mock job distribution
@@ -222,7 +219,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('video-processing')],
maxJobs: 2,
capabilities: ['video-encode', 'gpu-acceleration', 'h264', 'h265']
)
),
];
// CPU workers for audio processing
@@ -233,7 +230,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('audio-processing')],
maxJobs: 8,
capabilities: ['audio-encode', 'mp3', 'aac', 'flac']
)
),
];
// Thumbnail generation workers
@@ -251,7 +248,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('thumbnail-generation')],
maxJobs: 10,
capabilities: ['image-resize', 'ffmpeg', 'thumbnail']
)
),
];
$allWorkers = array_merge($videoWorkers, $audioWorkers, $thumbnailWorkers);
@@ -311,8 +308,8 @@ describe('Distributed Processing Real-World Scenarios', function () {
'required_capabilities' => ['gpu-acceleration', 'h264'],
'resource_requirements' => [
'gpu_memory' => '4GB',
'encoding_quality' => 'high'
]
'encoding_quality' => 'high',
],
];
// Mock worker scoring (would normally be done by JobDistributionService)
@@ -341,7 +338,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('transactions')],
maxJobs: 50,
capabilities: ['payment-processing', 'fraud-detection', 'pci-compliant']
)
),
];
// Simulate concurrent transaction processing
@@ -423,7 +420,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('cache-warming')],
maxJobs: 25,
capabilities: ['cdn-management', 'us-east-region', 'edge-caching']
)
),
];
$europeWorkers = [
@@ -433,7 +430,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('cache-warming')],
maxJobs: 20,
capabilities: ['cdn-management', 'eu-west-region', 'edge-caching']
)
),
];
$asiaWorkers = [
@@ -443,18 +440,24 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('cache-warming')],
maxJobs: 15,
capabilities: ['cdn-management', 'asia-pacific-region', 'edge-caching']
)
),
];
$allCdnWorkers = array_merge($usEastWorkers, $europeWorkers, $asiaWorkers);
// Verify regional distribution
$usEastCount = count(array_filter($allCdnWorkers,
fn($w) => $w->hasCapability('us-east-region')));
$europeCount = count(array_filter($allCdnWorkers,
fn($w) => $w->hasCapability('eu-west-region')));
$asiaCount = count(array_filter($allCdnWorkers,
fn($w) => $w->hasCapability('asia-pacific-region')));
$usEastCount = count(array_filter(
$allCdnWorkers,
fn ($w) => $w->hasCapability('us-east-region')
));
$europeCount = count(array_filter(
$allCdnWorkers,
fn ($w) => $w->hasCapability('eu-west-region')
));
$asiaCount = count(array_filter(
$allCdnWorkers,
fn ($w) => $w->hasCapability('asia-pacific-region')
));
expect($usEastCount)->toBe(2);
expect($europeCount)->toBe(1);
@@ -492,7 +495,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('content-delivery')],
maxJobs: 50,
capabilities: ['backup-region', 'medium-capacity']
)
),
];
// Simulate primary region failure
@@ -505,7 +508,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
}
// Total backup capacity should handle reduced load
$totalBackupCapacity = array_sum(array_map(fn($w) => $w->maxJobs, $backupWorkers));
$totalBackupCapacity = array_sum(array_map(fn ($w) => $w->maxJobs, $backupWorkers));
expect($totalBackupCapacity)->toBe(100); // Same as primary capacity
});
});
@@ -535,7 +538,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('data-preprocessing')],
maxJobs: 10,
capabilities: ['data-cleaning', 'feature-engineering', 'pandas', 'numpy']
)
),
];
$inferenceWorkers = [
@@ -545,7 +548,7 @@ describe('Distributed Processing Real-World Scenarios', function () {
queues: [QueueName::fromString('ml-inference')],
maxJobs: 50, // High throughput for inference
capabilities: ['model-serving', 'tensorflow-lite', 'onnx']
)
),
];
// Simulate GPU worker under heavy load
@@ -580,4 +583,4 @@ describe('Distributed Processing Real-World Scenarios', function () {
expect($inferenceWorkerIdle->getLoadPercentage()->getValue())->toBe(20.0); // Light load
});
});
});
});

View File

@@ -2,21 +2,21 @@
declare(strict_types=1);
use App\Framework\Queue\Services\WorkerRegistry;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Entities\Worker;
use App\Framework\Queue\Services\DatabaseDistributedLock;
use App\Framework\Queue\Services\FailoverRecoveryService;
use App\Framework\Queue\Services\JobDistributionService;
use App\Framework\Queue\Services\WorkerHealthCheckService;
use App\Framework\Queue\Services\FailoverRecoveryService;
use App\Framework\Queue\Entities\Worker;
use App\Framework\Queue\ValueObjects\WorkerId;
use App\Framework\Queue\Services\WorkerRegistry;
use App\Framework\Queue\ValueObjects\JobId;
use App\Framework\Queue\ValueObjects\LockKey;
use App\Framework\Queue\ValueObjects\QueueName;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Logging\Logger;
use App\Framework\Queue\ValueObjects\WorkerId;
/**
* Comprehensive integration tests for the Distributed Processing System
@@ -56,7 +56,7 @@ describe('Distributed Processing System', function () {
processId: 1001,
queues: [
QueueName::emailQueue(),
QueueName::defaultQueue()
QueueName::defaultQueue(),
],
maxJobs: 10,
capabilities: ['email', 'pdf-generation']
@@ -67,7 +67,7 @@ describe('Distributed Processing System', function () {
processId: 1002,
queues: [
QueueName::defaultQueue(),
QueueName::fromString('high-priority')
QueueName::fromString('high-priority'),
],
maxJobs: 5,
capabilities: ['image-processing', 'pdf-generation']
@@ -77,7 +77,7 @@ describe('Distributed Processing System', function () {
hostname: 'app-server-3',
processId: 1003,
queues: [
QueueName::emailQueue()
QueueName::emailQueue(),
],
maxJobs: 15,
capabilities: ['email', 'notifications']
@@ -330,7 +330,7 @@ describe('Distributed Processing System', function () {
it('calculates worker scores based on load and capabilities', function () {
$jobData = [
'required_capabilities' => ['email', 'pdf-generation']
'required_capabilities' => ['email', 'pdf-generation'],
];
$bestWorker = $this->jobDistribution->findBestWorkerForJob(
@@ -558,7 +558,7 @@ describe('Distributed Processing System', function () {
JobId::generate(),
JobId::generate(),
JobId::generate(),
JobId::generate()
JobId::generate(),
];
// Mock successful distribution for all concurrent jobs
@@ -597,7 +597,7 @@ describe('Distributed Processing System', function () {
$workers = [
WorkerId::generate(),
WorkerId::generate(),
WorkerId::generate()
WorkerId::generate(),
];
// Simulate lock contention - only first worker succeeds
@@ -643,7 +643,7 @@ describe('Distributed Processing System', function () {
$this->logger->shouldReceive('error')->andReturn(null);
// System should handle gracefully and throw appropriate exception
expect(fn() => $this->workerRegistry->findActiveWorkers())
expect(fn () => $this->workerRegistry->findActiveWorkers())
->toThrow(\PDOException::class);
});
@@ -659,7 +659,7 @@ describe('Distributed Processing System', function () {
'total_capacity' => 30,
'current_load' => 10,
'avg_cpu_usage' => 45.5,
'avg_memory_usage' => 819200000 // ~800MB in bytes
'avg_memory_usage' => 819200000, // ~800MB in bytes
]);
$queueStmt = mock(\PDOStatement::class);
@@ -671,7 +671,8 @@ describe('Distributed Processing System', function () {
);
$this->connection->shouldReceive('prepare')->andReturn(
$statsStmt, $queueStmt
$statsStmt,
$queueStmt
);
$this->logger->shouldReceive('error')->never();
@@ -744,21 +745,21 @@ describe('Distributed Processing System', function () {
describe('Edge Cases and Error Scenarios', function () {
it('handles worker registration with invalid data gracefully', function () {
expect(fn() => Worker::register(
expect(fn () => Worker::register(
hostname: '', // Invalid empty hostname
processId: 1001,
queues: [QueueName::defaultQueue()],
maxJobs: 10
))->toThrow(\InvalidArgumentException::class);
expect(fn() => Worker::register(
expect(fn () => Worker::register(
hostname: 'valid-host',
processId: 1001,
queues: [], // Invalid empty queues
maxJobs: 10
))->toThrow(\InvalidArgumentException::class);
expect(fn() => Worker::register(
expect(fn () => Worker::register(
hostname: 'valid-host',
processId: 1001,
queues: [QueueName::defaultQueue()],
@@ -767,13 +768,13 @@ describe('Distributed Processing System', function () {
});
it('handles lock key validation properly', function () {
expect(fn() => LockKey::fromString(''))
expect(fn () => LockKey::fromString(''))
->toThrow(\InvalidArgumentException::class);
expect(fn() => LockKey::fromString(str_repeat('a', 256))) // Too long
expect(fn () => LockKey::fromString(str_repeat('a', 256))) // Too long
->toThrow(\InvalidArgumentException::class);
expect(fn() => LockKey::fromString('invalid@key!')) // Invalid characters
expect(fn () => LockKey::fromString('invalid@key!')) // Invalid characters
->toThrow(\InvalidArgumentException::class);
});
@@ -817,4 +818,4 @@ describe('Distributed Processing System', function () {
expect($result)->toBeFalse();
});
});
});
});