mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-14 16:52:45 -04:00
High disk usage notifications were sent every ~10 minutes on Coolify Cloud because the cache rate limiter did not hold across Sentinel-triggered checks. - Add a notification_throttles table and NotificationThrottle helper that atomically records when a notification was sent for a resource. - Add the ThrottledNotification contract; Team::notify() skips a throttled notification that was already sent within its interval. - High disk usage: configurable per-server interval (server_disk_usage_notification_interval_hours, default 24h) in Server > Advanced and the servers API. - Container restarted (coolify-proxy, database TCP proxy): at most one notification per hour per server/database during a restart loop. - Move the unreachable and missing-backup markers into the new table and migrate existing data (servers.unreachable_notification_sent and scheduled_database_backups.missing_backup_notification_sent_at). - Regenerate OpenAPI files.
83 lines
3.4 KiB
PHP
83 lines
3.4 KiB
PHP
<?php
|
|
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\NotificationThrottle;
|
|
use App\Models\Server;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use App\Notifications\Server\HighDiskUsage;
|
|
use App\Notifications\Server\Unreachable;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
Server::flushIdentityMap();
|
|
$this->team = Team::factory()->create();
|
|
$this->server = Server::factory()->create(['team_id' => $this->team->id]);
|
|
});
|
|
|
|
afterEach(function () {
|
|
Carbon::setTestNow();
|
|
});
|
|
|
|
it('claims a notification only once until it is released', function () {
|
|
expect(NotificationThrottle::claim($this->server, Unreachable::class))->toBeTrue()
|
|
->and(NotificationThrottle::claim($this->server, Unreachable::class))->toBeFalse()
|
|
->and($this->server->unreachable_notification_sent)->toBeTrue()
|
|
->and(NotificationThrottle::release($this->server, Unreachable::class))->toBeTrue()
|
|
->and(NotificationThrottle::release($this->server, Unreachable::class))->toBeFalse()
|
|
->and($this->server->unreachable_notification_sent)->toBeFalse()
|
|
->and(NotificationThrottle::claim($this->server, Unreachable::class))->toBeTrue();
|
|
});
|
|
|
|
it('claims a notification again only when it was sent before the given time', function () {
|
|
Carbon::setTestNow(Carbon::parse('2026-09-29 12:00:00', 'UTC'));
|
|
|
|
expect(NotificationThrottle::claim($this->server, HighDiskUsage::class, now()->subHours(6)))->toBeTrue();
|
|
|
|
Carbon::setTestNow(now()->addHours(5));
|
|
expect(NotificationThrottle::claim($this->server, HighDiskUsage::class, now()->subHours(6)))->toBeFalse();
|
|
|
|
Carbon::setTestNow(now()->addHours(2));
|
|
expect(NotificationThrottle::claim($this->server, HighDiskUsage::class, now()->subHours(6)))->toBeTrue()
|
|
->and(NotificationThrottle::query()->count())->toBe(1);
|
|
});
|
|
|
|
it('keeps throttles separate for each notification and resource', function () {
|
|
$otherServer = Server::factory()->create(['team_id' => $this->team->id]);
|
|
|
|
NotificationThrottle::record($this->server, Unreachable::class);
|
|
|
|
expect(NotificationThrottle::claim($this->server, HighDiskUsage::class))->toBeTrue()
|
|
->and(NotificationThrottle::claim($otherServer, Unreachable::class))->toBeTrue()
|
|
->and(NotificationThrottle::claim($this->server, Unreachable::class))->toBeFalse();
|
|
});
|
|
|
|
it('removes the throttles of a server when the server is force deleted', function () {
|
|
NotificationThrottle::record($this->server, Unreachable::class);
|
|
|
|
$this->server->forceDelete();
|
|
|
|
expect(NotificationThrottle::query()->count())->toBe(0);
|
|
});
|
|
|
|
it('keeps unreachable_notification_sent in the server API response', function () {
|
|
config()->set('app.maintenance.driver', 'file');
|
|
config()->set('cache.default', 'array');
|
|
InstanceSettings::forceCreate(['id' => 0, 'is_api_enabled' => true]);
|
|
$user = User::factory()->create();
|
|
$this->team->members()->attach($user->id, ['role' => 'owner']);
|
|
session(['currentTeam' => $this->team]);
|
|
$token = $user->createToken('read-token', ['read']);
|
|
$token->accessToken->forceFill(['team_id' => $this->team->id])->save();
|
|
|
|
NotificationThrottle::record($this->server, Unreachable::class);
|
|
|
|
$this->withHeaders(['Authorization' => 'Bearer '.$token->plainTextToken])
|
|
->getJson('/api/v1/servers/'.$this->server->uuid)
|
|
->assertOk()
|
|
->assertJsonPath('unreachable_notification_sent', true);
|
|
});
|