feat: implement bandwidth enforcement mechanism in Proxmox server component to throttle servers exceeding usage limits, enhancing resource management
This commit is contained in:
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace Paymenter\Extensions\Servers\Proxmox\Console\Commands;
|
namespace Paymenter\Extensions\Servers\Proxmox\Console\Commands;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Paymenter\Extensions\Servers\Proxmox\Models\Server;
|
use Paymenter\Extensions\Servers\Proxmox\Models\Server;
|
||||||
use Paymenter\Extensions\Servers\Proxmox\Models\ServerStatistics;
|
use Paymenter\Extensions\Servers\Proxmox\Models\ServerStatistics;
|
||||||
use Paymenter\Extensions\Servers\Proxmox\Proxmox;
|
use Paymenter\Extensions\Servers\Proxmox\Proxmox;
|
||||||
@@ -166,6 +168,145 @@ class PollServerStatistics extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->info('Statistics polling completed. Success: ' . $successCount . ', Errors: ' . $errorCount);
|
$this->info('Statistics polling completed. Success: ' . $successCount . ', Errors: ' . $errorCount);
|
||||||
|
|
||||||
|
// Enforce bandwidth throttling for servers exceeding their limits
|
||||||
|
$this->info('Checking bandwidth limits...');
|
||||||
|
$this->enforceBandwidthLimits();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function enforceBandwidthLimits()
|
||||||
|
{
|
||||||
|
$threshold = $this->calculateBandwidthThreshold();
|
||||||
|
$this->info('Bandwidth threshold: ' . $threshold . ' GiB');
|
||||||
|
|
||||||
|
$throttledCount = 0;
|
||||||
|
$servers = Server::all();
|
||||||
|
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
try {
|
||||||
|
$monthlyTraffic = $this->calculateServerMonthlyTraffic($server);
|
||||||
|
|
||||||
|
if ($monthlyTraffic > $threshold) {
|
||||||
|
$this->warn('Server ID ' . $server->id . ' (' . $server->hostname . ') exceeded bandwidth: ' .
|
||||||
|
round($monthlyTraffic, 2) . ' GiB / ' . $threshold . ' GiB');
|
||||||
|
$this->throttleServerBandwidth($server);
|
||||||
|
$throttledCount++;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->error('Error checking bandwidth for server ID ' . $server->id . ': ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Bandwidth enforcement completed. Throttled: ' . $throttledCount . ' servers');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function calculateBandwidthThreshold()
|
||||||
|
{
|
||||||
|
return Cache::remember('bandwidth_threshold_95_' . Carbon::now()->format('Y-m'), 15, function () {
|
||||||
|
$startOfMonth = Carbon::now()->startOfMonth();
|
||||||
|
$allServers = Server::all();
|
||||||
|
|
||||||
|
if ($allServers->isEmpty()) {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalTraffic = 0;
|
||||||
|
$serverCount = 0;
|
||||||
|
|
||||||
|
foreach ($allServers as $server) {
|
||||||
|
$firstStat = ServerStatistics::where('server_id', $server->id)
|
||||||
|
->where('DateTime', '>=', $startOfMonth)
|
||||||
|
->orderBy('DateTime', 'asc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$latestStat = ServerStatistics::where('server_id', $server->id)
|
||||||
|
->where('DateTime', '>=', $startOfMonth)
|
||||||
|
->orderBy('DateTime', 'desc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($firstStat && $latestStat) {
|
||||||
|
$netinDiff = max(0, $latestStat->netin - $firstStat->netin);
|
||||||
|
$netoutDiff = max(0, $latestStat->netout - $firstStat->netout);
|
||||||
|
$totalBytes = $netinDiff + $netoutDiff;
|
||||||
|
$totalTraffic += $totalBytes / (1024 * 1024 * 1024);
|
||||||
|
$serverCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($serverCount === 0) {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
$averageBandwidth = $totalTraffic / $serverCount;
|
||||||
|
$threshold95Percent = $averageBandwidth * 0.95;
|
||||||
|
|
||||||
|
return round(max($threshold95Percent, 10), 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function calculateServerMonthlyTraffic(Server $server)
|
||||||
|
{
|
||||||
|
$startOfMonth = Carbon::now()->startOfMonth();
|
||||||
|
|
||||||
|
$firstStat = ServerStatistics::where('server_id', $server->id)
|
||||||
|
->where('DateTime', '>=', $startOfMonth)
|
||||||
|
->orderBy('DateTime', 'asc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
$latestStat = ServerStatistics::where('server_id', $server->id)
|
||||||
|
->where('DateTime', '>=', $startOfMonth)
|
||||||
|
->orderBy('DateTime', 'desc')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$firstStat || !$latestStat) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$netinDiff = max(0, $latestStat->netin - $firstStat->netin);
|
||||||
|
$netoutDiff = max(0, $latestStat->netout - $firstStat->netout);
|
||||||
|
$totalBytes = $netinDiff + $netoutDiff;
|
||||||
|
|
||||||
|
return round($totalBytes / (1024 * 1024 * 1024), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function throttleServerBandwidth(Server $server)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$proxmox = new Proxmox;
|
||||||
|
$config = $proxmox->request('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/config')->json()['data'];
|
||||||
|
|
||||||
|
$updates = [];
|
||||||
|
|
||||||
|
foreach ($config as $key => $value) {
|
||||||
|
if (preg_match('/^net\d+$/', $key)) {
|
||||||
|
$parts = explode(',', $value);
|
||||||
|
$newParts = [];
|
||||||
|
$hasRate = false;
|
||||||
|
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if (strpos($part, 'rate=') === 0) {
|
||||||
|
$newParts[] = 'rate=1';
|
||||||
|
$hasRate = true;
|
||||||
|
} else {
|
||||||
|
$newParts[] = $part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$hasRate) {
|
||||||
|
$newParts[] = 'rate=1';
|
||||||
|
}
|
||||||
|
|
||||||
|
$updates[$key] = implode(',', $newParts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($updates)) {
|
||||||
|
$proxmox->request('/nodes/' . $server->node->name . '/qemu/' . $server->vm_id . '/config', 'put', $updates);
|
||||||
|
$this->info('Throttled network interfaces for server ID ' . $server->id . ' to 1 MiB/s');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->error('Failed to throttle bandwidth for server ID ' . $server->id . ': ' . $e->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ class Server extends Component
|
|||||||
public function mount(ServerModel $server): void
|
public function mount(ServerModel $server): void
|
||||||
{
|
{
|
||||||
parent::mount($server);
|
parent::mount($server);
|
||||||
|
|
||||||
|
// Check and enforce bandwidth throttling if exceeded
|
||||||
|
$this->checkAndEnforceBandwidthLimit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMonthlyTrafficProperty()
|
public function getMonthlyTrafficProperty()
|
||||||
@@ -97,6 +100,68 @@ class Server extends Component
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function checkAndEnforceBandwidthLimit()
|
||||||
|
{
|
||||||
|
$monthlyTraffic = $this->monthlyTraffic;
|
||||||
|
$threshold = $this->bandwidthThreshold95;
|
||||||
|
|
||||||
|
// Check if bandwidth is exceeded
|
||||||
|
if ($monthlyTraffic > $threshold) {
|
||||||
|
// Throttle the server to 1 MiB/s
|
||||||
|
$this->throttleServerBandwidth();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function throttleServerBandwidth()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$proxmox = new \Paymenter\Extensions\Servers\Proxmox\Proxmox;
|
||||||
|
|
||||||
|
// Get current VM config
|
||||||
|
$config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'];
|
||||||
|
|
||||||
|
$updates = [];
|
||||||
|
|
||||||
|
// Update all network interfaces to rate limit of 1 MiB/s
|
||||||
|
foreach ($config as $key => $value) {
|
||||||
|
if (preg_match('/^net\d+$/', $key)) {
|
||||||
|
// Parse existing network config
|
||||||
|
$parts = explode(',', $value);
|
||||||
|
$newParts = [];
|
||||||
|
$hasRate = false;
|
||||||
|
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if (strpos($part, 'rate=') === 0) {
|
||||||
|
// Replace existing rate
|
||||||
|
$newParts[] = 'rate=1';
|
||||||
|
$hasRate = true;
|
||||||
|
} else {
|
||||||
|
$newParts[] = $part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add rate if not present
|
||||||
|
if (!$hasRate) {
|
||||||
|
$newParts[] = 'rate=1';
|
||||||
|
}
|
||||||
|
|
||||||
|
$updates[$key] = implode(',', $newParts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the updates if any network interfaces were found
|
||||||
|
if (!empty($updates)) {
|
||||||
|
$proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', 'put', $updates);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Log the error but don't fail the page load
|
||||||
|
\Log::error('Failed to throttle bandwidth for server ' . $this->server->id, [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'server_id' => $this->server->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('proxmox::server');
|
return view('proxmox::server');
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ EOF
|
|||||||
"$git_cache"
|
"$git_cache"
|
||||||
;;
|
;;
|
||||||
5)
|
5)
|
||||||
LOG_FILE="${SCRIPT_DIR}/storage/logs/laravel.log"
|
LOG_DATE=$(date +%Y-%m-%d)
|
||||||
|
LOG_FILE="${SCRIPT_DIR}/storage/logs/laravel-${LOG_DATE}.log"
|
||||||
if [ -f "$LOG_FILE" ]; then
|
if [ -f "$LOG_FILE" ]; then
|
||||||
> "$LOG_FILE"
|
> "$LOG_FILE"
|
||||||
echo "Log file cleared successfully: $LOG_FILE"
|
echo "Log file cleared successfully: $LOG_FILE"
|
||||||
|
|||||||
Reference in New Issue
Block a user