fix(server): recheck unreachable servers before failing deployments

Deployments failed with "Server is not functional" when the server was
still flagged unreachable after a single failed scheduled check. Add
Server::isFunctionalAfterRecheck(), which runs a live SSH and Docker
connection check when the server is marked unreachable. It skips the
check for disabled servers and servers with placeholder IPs, then
re-evaluates isFunctional(). ApplicationDeploymentJob now uses it.

ServerManagerJob no longer skips the SSH connection check for servers
with a live Sentinel when the server is flagged unreachable or unusable.
The Sentinel heartbeat does not restore these flags, so without the
check they would never recover.

Add feature tests for the recheck behavior and unit tests covering the
Sentinel skip condition.
This commit is contained in:
Andras Bacsai
2026-10-01 09:30:21 +02:00
parent 69c809f4a2
commit b5393e24ec
5 changed files with 136 additions and 3 deletions
@@ -35,6 +35,8 @@ function createSentinelCheckServer(Team $team, Carbon $sentinelUpdatedAt, Server
'server_timezone' => 'UTC',
'server_role' => $role,
'is_build_server' => $role === ServerRole::BUILD,
'is_reachable' => true,
'is_usable' => true,
]);
return $server->refresh();
@@ -76,6 +78,21 @@ it('skips ServerConnectionCheckJob when sentinel is live', function () {
Queue::assertNotPushed(ServerConnectionCheckJob::class);
});
it('dispatches ServerConnectionCheckJob when sentinel is live but the server is marked unusable', function (string $flag) {
// A Sentinel heartbeat does not restore these flags, so only the SSH check can recover them
$server = createSentinelCheckServer($this->team, Carbon::now());
$server->settings->update([$flag => false]);
expect($server->isSentinelLive())->toBeTrue();
$job = new ServerManagerJob;
$job->handle();
Queue::assertPushed(ServerConnectionCheckJob::class, function ($job) use ($server) {
return $job->server->id === $server->id;
});
})->with(['is_reachable', 'is_usable']);
it('dispatches ServerConnectionCheckJob when sentinel is not live', function () {
$server = createSentinelCheckServer($this->team, Carbon::now()->subMinutes(10));