Refactor: Move sentinel update checks to ServerManagerJob and add tests for hourly dispatch
This commit is contained in:
parent
2302a70a44
commit
4002044877
4 changed files with 338 additions and 12 deletions
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Jobs\CheckForUpdatesJob;
|
||||
use App\Jobs\CheckHelperImageJob;
|
||||
use App\Jobs\CheckTraefikVersionJob;
|
||||
|
|
@ -100,17 +99,7 @@ private function pullImages(): void
|
|||
} else {
|
||||
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
||||
}
|
||||
foreach ($servers as $server) {
|
||||
try {
|
||||
if ($server->isSentinelEnabled()) {
|
||||
$this->scheduleInstance->job(function () use ($server) {
|
||||
CheckAndStartSentinelJob::dispatch($server);
|
||||
})->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error pulling images: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
// Sentinel update checks are now handled by ServerManagerJob
|
||||
$this->scheduleInstance->job(new CheckHelperImageJob)
|
||||
->cron($this->updateCheckFrequency)
|
||||
->timezone($this->instanceTimezone)
|
||||
|
|
|
|||
|
|
@ -146,6 +146,15 @@ private function processServerTasks(Server $server): void
|
|||
ServerPatchCheckJob::dispatch($server);
|
||||
}
|
||||
|
||||
// Check for sentinel updates hourly (independent of user-configurable update_check_frequency)
|
||||
if ($server->isSentinelEnabled()) {
|
||||
$shouldCheckSentinel = $this->shouldRunNow('0 * * * *', $serverTimezone);
|
||||
|
||||
if ($shouldCheckSentinel) {
|
||||
CheckAndStartSentinelJob::dispatch($server);
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch Sentinel restart if due (daily for Sentinel-enabled servers)
|
||||
$isSentinelEnabled = $server->isSentinelEnabled();
|
||||
$shouldRestartSentinel = $isSentinelEnabled && $this->shouldRunNow('0 0 * * *', $serverTimezone);
|
||||
|
|
|
|||
187
tests/Feature/SentinelUpdateCheckIndependenceTest.php
Normal file
187
tests/Feature/SentinelUpdateCheckIndependenceTest.php
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Jobs\ServerManagerJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Queue::fake();
|
||||
|
||||
// Create user (which automatically creates a team)
|
||||
$user = User::factory()->create();
|
||||
$this->team = $user->teams()->first();
|
||||
|
||||
// Create server with sentinel enabled
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
|
||||
// Enable sentinel on the server
|
||||
$this->server->settings->update([
|
||||
'is_sentinel_enabled' => true,
|
||||
'server_timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
$this->server->refresh();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow(); // Reset frozen time
|
||||
});
|
||||
|
||||
it('dispatches sentinel check hourly regardless of instance update_check_frequency setting', function () {
|
||||
// Set instance update_check_frequency to yearly (most infrequent option)
|
||||
$instanceSettings = InstanceSettings::first();
|
||||
$instanceSettings->update([
|
||||
'update_check_frequency' => '0 0 1 1 *', // Yearly - January 1st at midnight
|
||||
'instance_timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
// Set time to top of any hour (sentinel should check every hour)
|
||||
Carbon::setTestNow('2025-06-15 14:00:00'); // Random hour, not January 1st
|
||||
|
||||
// Run ServerManagerJob
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Assert that CheckAndStartSentinelJob was dispatched despite yearly update check frequency
|
||||
Queue::assertPushed(CheckAndStartSentinelJob::class, function ($job) {
|
||||
return $job->server->id === $this->server->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not dispatch sentinel check when not at top of hour', function () {
|
||||
// Set instance update_check_frequency to hourly (most frequent)
|
||||
$instanceSettings = InstanceSettings::first();
|
||||
$instanceSettings->update([
|
||||
'update_check_frequency' => '0 * * * *', // Hourly
|
||||
'instance_timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
// Set time to middle of the hour (sentinel check cron won't match)
|
||||
Carbon::setTestNow('2025-06-15 14:30:00'); // 30 minutes past the hour
|
||||
|
||||
// Run ServerManagerJob
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Assert that CheckAndStartSentinelJob was NOT dispatched (not top of hour)
|
||||
Queue::assertNotPushed(CheckAndStartSentinelJob::class);
|
||||
});
|
||||
|
||||
it('dispatches sentinel check at every hour mark throughout the day', function () {
|
||||
$instanceSettings = InstanceSettings::first();
|
||||
$instanceSettings->update([
|
||||
'update_check_frequency' => '0 0 1 1 *', // Yearly
|
||||
'instance_timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
// Test multiple hours throughout a day
|
||||
$hoursToTest = [0, 6, 12, 18, 23]; // Various hours of the day
|
||||
|
||||
foreach ($hoursToTest as $hour) {
|
||||
Queue::fake(); // Reset queue for each test
|
||||
|
||||
Carbon::setTestNow("2025-06-15 {$hour}:00:00");
|
||||
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
Queue::assertPushed(CheckAndStartSentinelJob::class, function ($job) {
|
||||
return $job->server->id === $this->server->id;
|
||||
}, "Failed to dispatch sentinel check at hour {$hour}");
|
||||
}
|
||||
});
|
||||
|
||||
it('respects server timezone when checking sentinel updates', function () {
|
||||
// Update server timezone to America/New_York
|
||||
$this->server->settings->update([
|
||||
'server_timezone' => 'America/New_York',
|
||||
]);
|
||||
|
||||
$instanceSettings = InstanceSettings::first();
|
||||
$instanceSettings->update([
|
||||
'instance_timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
// Set time to 17:00 UTC which is 12:00 PM EST (top of hour in server's timezone)
|
||||
Carbon::setTestNow('2025-01-15 17:00:00');
|
||||
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Should dispatch because it's top of hour in server's timezone (America/New_York)
|
||||
Queue::assertPushed(CheckAndStartSentinelJob::class, function ($job) {
|
||||
return $job->server->id === $this->server->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not dispatch sentinel check for servers without sentinel enabled', function () {
|
||||
// Disable sentinel
|
||||
$this->server->settings->update([
|
||||
'is_sentinel_enabled' => false,
|
||||
]);
|
||||
|
||||
$instanceSettings = InstanceSettings::first();
|
||||
$instanceSettings->update([
|
||||
'update_check_frequency' => '0 * * * *',
|
||||
'instance_timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
Carbon::setTestNow('2025-06-15 14:00:00');
|
||||
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Should NOT dispatch because sentinel is disabled
|
||||
Queue::assertNotPushed(CheckAndStartSentinelJob::class);
|
||||
});
|
||||
|
||||
it('handles multiple servers with different sentinel configurations', function () {
|
||||
// Create a second server with sentinel disabled
|
||||
$server2 = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
$server2->settings->update([
|
||||
'is_sentinel_enabled' => false,
|
||||
'server_timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
// Create a third server with sentinel enabled
|
||||
$server3 = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
]);
|
||||
$server3->settings->update([
|
||||
'is_sentinel_enabled' => true,
|
||||
'server_timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
$instanceSettings = InstanceSettings::first();
|
||||
$instanceSettings->update([
|
||||
'instance_timezone' => 'UTC',
|
||||
]);
|
||||
|
||||
Carbon::setTestNow('2025-06-15 14:00:00');
|
||||
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Should dispatch for server1 (sentinel enabled) and server3 (sentinel enabled)
|
||||
Queue::assertPushed(CheckAndStartSentinelJob::class, 2);
|
||||
|
||||
// Verify it was dispatched for the correct servers
|
||||
Queue::assertPushed(CheckAndStartSentinelJob::class, function ($job) {
|
||||
return $job->server->id === $this->server->id;
|
||||
});
|
||||
|
||||
Queue::assertPushed(CheckAndStartSentinelJob::class, function ($job) use ($server3) {
|
||||
return $job->server->id === $server3->id;
|
||||
});
|
||||
});
|
||||
141
tests/Unit/ServerManagerJobSentinelCheckTest.php
Normal file
141
tests/Unit/ServerManagerJobSentinelCheckTest.php
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Jobs\ServerManagerJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Mockery;
|
||||
|
||||
beforeEach(function () {
|
||||
Queue::fake();
|
||||
Carbon::setTestNow('2025-01-15 12:00:00'); // Set to top of the hour for cron matching
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Mockery::close();
|
||||
Carbon::setTestNow(); // Reset frozen time
|
||||
});
|
||||
|
||||
it('dispatches CheckAndStartSentinelJob hourly for sentinel-enabled servers', function () {
|
||||
// Mock InstanceSettings
|
||||
$settings = Mockery::mock(InstanceSettings::class);
|
||||
$settings->instance_timezone = 'UTC';
|
||||
$this->app->instance(InstanceSettings::class, $settings);
|
||||
|
||||
// Create a mock server with sentinel enabled
|
||||
$server = Mockery::mock(Server::class)->makePartial();
|
||||
$server->shouldReceive('isSentinelEnabled')->andReturn(true);
|
||||
$server->id = 1;
|
||||
$server->name = 'test-server';
|
||||
$server->ip = '192.168.1.100';
|
||||
$server->sentinel_updated_at = Carbon::now();
|
||||
$server->shouldReceive('getAttribute')->with('settings')->andReturn((object) ['server_timezone' => 'UTC']);
|
||||
$server->shouldReceive('waitBeforeDoingSshCheck')->andReturn(120);
|
||||
|
||||
// Mock the Server query
|
||||
Server::shouldReceive('where')->with('ip', '!=', '1.2.3.4')->andReturnSelf();
|
||||
Server::shouldReceive('get')->andReturn(collect([$server]));
|
||||
|
||||
// Execute the job
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Assert CheckAndStartSentinelJob was dispatched for the sentinel-enabled server
|
||||
Queue::assertPushed(CheckAndStartSentinelJob::class, function ($job) use ($server) {
|
||||
return $job->server->id === $server->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not dispatch CheckAndStartSentinelJob for servers without sentinel enabled', function () {
|
||||
// Mock InstanceSettings
|
||||
$settings = Mockery::mock(InstanceSettings::class);
|
||||
$settings->instance_timezone = 'UTC';
|
||||
$this->app->instance(InstanceSettings::class, $settings);
|
||||
|
||||
// Create a mock server with sentinel disabled
|
||||
$server = Mockery::mock(Server::class)->makePartial();
|
||||
$server->shouldReceive('isSentinelEnabled')->andReturn(false);
|
||||
$server->id = 2;
|
||||
$server->name = 'test-server-no-sentinel';
|
||||
$server->ip = '192.168.1.101';
|
||||
$server->sentinel_updated_at = Carbon::now();
|
||||
$server->shouldReceive('getAttribute')->with('settings')->andReturn((object) ['server_timezone' => 'UTC']);
|
||||
$server->shouldReceive('waitBeforeDoingSshCheck')->andReturn(120);
|
||||
|
||||
// Mock the Server query
|
||||
Server::shouldReceive('where')->with('ip', '!=', '1.2.3.4')->andReturnSelf();
|
||||
Server::shouldReceive('get')->andReturn(collect([$server]));
|
||||
|
||||
// Execute the job
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Assert CheckAndStartSentinelJob was NOT dispatched
|
||||
Queue::assertNotPushed(CheckAndStartSentinelJob::class);
|
||||
});
|
||||
|
||||
it('respects server timezone when scheduling sentinel checks', function () {
|
||||
// Mock InstanceSettings
|
||||
$settings = Mockery::mock(InstanceSettings::class);
|
||||
$settings->instance_timezone = 'UTC';
|
||||
$this->app->instance(InstanceSettings::class, $settings);
|
||||
|
||||
// Set test time to top of hour in America/New_York (which is 17:00 UTC)
|
||||
Carbon::setTestNow('2025-01-15 17:00:00'); // 12:00 PM EST (top of hour in EST)
|
||||
|
||||
// Create a mock server with sentinel enabled and America/New_York timezone
|
||||
$server = Mockery::mock(Server::class)->makePartial();
|
||||
$server->shouldReceive('isSentinelEnabled')->andReturn(true);
|
||||
$server->id = 3;
|
||||
$server->name = 'test-server-est';
|
||||
$server->ip = '192.168.1.102';
|
||||
$server->sentinel_updated_at = Carbon::now();
|
||||
$server->shouldReceive('getAttribute')->with('settings')->andReturn((object) ['server_timezone' => 'America/New_York']);
|
||||
$server->shouldReceive('waitBeforeDoingSshCheck')->andReturn(120);
|
||||
|
||||
// Mock the Server query
|
||||
Server::shouldReceive('where')->with('ip', '!=', '1.2.3.4')->andReturnSelf();
|
||||
Server::shouldReceive('get')->andReturn(collect([$server]));
|
||||
|
||||
// Execute the job
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Assert CheckAndStartSentinelJob was dispatched (should run at top of hour in server's timezone)
|
||||
Queue::assertPushed(CheckAndStartSentinelJob::class, function ($job) use ($server) {
|
||||
return $job->server->id === $server->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not dispatch sentinel check when not at top of hour', function () {
|
||||
// Mock InstanceSettings
|
||||
$settings = Mockery::mock(InstanceSettings::class);
|
||||
$settings->instance_timezone = 'UTC';
|
||||
$this->app->instance(InstanceSettings::class, $settings);
|
||||
|
||||
// Set test time to middle of the hour (not top of hour)
|
||||
Carbon::setTestNow('2025-01-15 12:30:00');
|
||||
|
||||
// Create a mock server with sentinel enabled
|
||||
$server = Mockery::mock(Server::class)->makePartial();
|
||||
$server->shouldReceive('isSentinelEnabled')->andReturn(true);
|
||||
$server->id = 4;
|
||||
$server->name = 'test-server-mid-hour';
|
||||
$server->ip = '192.168.1.103';
|
||||
$server->sentinel_updated_at = Carbon::now();
|
||||
$server->shouldReceive('getAttribute')->with('settings')->andReturn((object) ['server_timezone' => 'UTC']);
|
||||
$server->shouldReceive('waitBeforeDoingSshCheck')->andReturn(120);
|
||||
|
||||
// Mock the Server query
|
||||
Server::shouldReceive('where')->with('ip', '!=', '1.2.3.4')->andReturnSelf();
|
||||
Server::shouldReceive('get')->andReturn(collect([$server]));
|
||||
|
||||
// Execute the job
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Assert CheckAndStartSentinelJob was NOT dispatched (not top of hour)
|
||||
Queue::assertNotPushed(CheckAndStartSentinelJob::class);
|
||||
});
|
||||
Loading…
Reference in a new issue