2025-11-11 17:14:39 +05:00
|
|
|
#!/usr/local/CyberCP/bin/python
|
|
|
|
|
"""
|
|
|
|
|
CyberPanel Resource Limits Manager
|
|
|
|
|
Handles resource limits using OpenLiteSpeed native cgroups v2 integration
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import logging as log
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
# Django imports
|
|
|
|
|
import sys
|
|
|
|
|
sys.path.append('/usr/local/CyberCP')
|
|
|
|
|
import django
|
|
|
|
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings")
|
|
|
|
|
django.setup()
|
|
|
|
|
|
|
|
|
|
from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ResourceLimitsManager:
|
|
|
|
|
"""
|
|
|
|
|
Manages resource limits for websites using OpenLiteSpeed native cgroups v2 API
|
|
|
|
|
This uses the lscgctl command to set per-user limits, which OLS enforces automatically
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Path to OLS cgroups control tool
|
|
|
|
|
LSCGCTL_PATH = "/usr/local/lsws/lsns/bin/lscgctl"
|
|
|
|
|
LSSETUP_PATH = "/usr/local/lsws/lsns/bin/lssetup"
|
|
|
|
|
OLS_CONF_PATH = "/usr/local/lsws/conf/httpd_config.conf"
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
"""Initialize the resource limits manager"""
|
|
|
|
|
self._initialized = False
|
|
|
|
|
|
2025-11-11 17:27:11 +05:00
|
|
|
def _check_rhel8_cgroups_v2(self):
|
|
|
|
|
"""
|
|
|
|
|
Check if RHEL 8 family needs manual cgroups v2 enablement
|
|
|
|
|
|
|
|
|
|
RHEL 8, AlmaLinux 8, Rocky Linux 8, and CloudLinux 8 have cgroups v2
|
|
|
|
|
backported to kernel 4.18 but it's disabled by default.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True if cgroups v2 is available or not RHEL 8, False if needs enablement
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Check if this is a RHEL 8 family system
|
|
|
|
|
redhat_release_paths = ['/etc/redhat-release', '/etc/system-release']
|
|
|
|
|
is_rhel8 = False
|
|
|
|
|
os_name = "Unknown"
|
|
|
|
|
|
|
|
|
|
for release_file in redhat_release_paths:
|
|
|
|
|
if os.path.exists(release_file):
|
|
|
|
|
try:
|
|
|
|
|
with open(release_file, 'r') as f:
|
|
|
|
|
release_content = f.read().lower()
|
|
|
|
|
os_name = release_content.strip()
|
|
|
|
|
|
|
|
|
|
# Check for RHEL 8 family (RHEL, AlmaLinux, Rocky, CloudLinux, CentOS 8)
|
|
|
|
|
if ('release 8' in release_content or
|
|
|
|
|
'release 8.' in release_content):
|
|
|
|
|
if any(distro in release_content for distro in
|
|
|
|
|
['red hat', 'almalinux', 'rocky', 'cloudlinux', 'centos']):
|
|
|
|
|
is_rhel8 = True
|
|
|
|
|
break
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
if not is_rhel8:
|
|
|
|
|
# Not RHEL 8 family, no special handling needed
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
# This is RHEL 8 family - check if cgroups v2 is actually enabled
|
|
|
|
|
logging.writeToFile(f"Detected RHEL 8 family system: {os_name}")
|
|
|
|
|
|
|
|
|
|
# Check if cgroups v2 is mounted (indicates it's enabled)
|
|
|
|
|
try:
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
['mount'],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=5
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if 'cgroup2' in result.stdout:
|
|
|
|
|
logging.writeToFile("cgroups v2 is enabled on RHEL 8 family system")
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
# cgroups v2 is not enabled - provide instructions
|
|
|
|
|
logging.writeToFile("=" * 80)
|
|
|
|
|
logging.writeToFile("RHEL 8 FAMILY: cgroups v2 MANUAL ENABLEMENT REQUIRED")
|
|
|
|
|
logging.writeToFile("=" * 80)
|
|
|
|
|
logging.writeToFile(f"System: {os_name}")
|
|
|
|
|
logging.writeToFile(f"Kernel: {os.uname().release}")
|
|
|
|
|
logging.writeToFile("")
|
|
|
|
|
logging.writeToFile("RHEL 8, AlmaLinux 8, Rocky Linux 8, and CloudLinux 8 have cgroups v2")
|
|
|
|
|
logging.writeToFile("backported but disabled by default. To enable, run these commands:")
|
|
|
|
|
logging.writeToFile("")
|
|
|
|
|
logging.writeToFile("1. Enable cgroups v2 in boot parameters:")
|
|
|
|
|
logging.writeToFile(" grubby --update-kernel=ALL --args='systemd.unified_cgroup_hierarchy=1'")
|
|
|
|
|
logging.writeToFile("")
|
|
|
|
|
logging.writeToFile("2. Reboot the system:")
|
|
|
|
|
logging.writeToFile(" reboot")
|
|
|
|
|
logging.writeToFile("")
|
|
|
|
|
logging.writeToFile("3. After reboot, verify cgroups v2 is enabled:")
|
|
|
|
|
logging.writeToFile(" mount | grep cgroup2")
|
|
|
|
|
logging.writeToFile("")
|
|
|
|
|
logging.writeToFile("4. Then create websites with resource limits")
|
|
|
|
|
logging.writeToFile("=" * 80)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Error checking cgroups v2 mount status: {str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Error checking RHEL 8 family status: {str(e)}")
|
|
|
|
|
# If we can't detect, assume it's OK and let the normal checks proceed
|
|
|
|
|
return True
|
|
|
|
|
|
2025-11-11 17:14:39 +05:00
|
|
|
def _ensure_cgroups_enabled(self):
|
|
|
|
|
"""
|
|
|
|
|
Ensure OpenLiteSpeed cgroups are enabled
|
|
|
|
|
This performs automatic setup if needed
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True if cgroups are enabled, False otherwise
|
|
|
|
|
"""
|
|
|
|
|
if self._initialized:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
try:
|
2025-11-11 17:27:11 +05:00
|
|
|
# Special check for RHEL 8 family systems
|
|
|
|
|
if not self._check_rhel8_cgroups_v2():
|
|
|
|
|
return False
|
|
|
|
|
|
2025-11-11 17:14:39 +05:00
|
|
|
# Check kernel support first
|
|
|
|
|
if not os.path.exists('/sys/fs/cgroup/cgroup.controllers'):
|
|
|
|
|
logging.writeToFile("cgroups v2 not available on this system (requires kernel 5.2+)")
|
2025-11-11 17:27:11 +05:00
|
|
|
logging.writeToFile("For RHEL 8 family, see instructions above to enable cgroups v2")
|
2025-11-11 17:14:39 +05:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Check if lscgctl exists
|
|
|
|
|
if not os.path.exists(self.LSCGCTL_PATH):
|
|
|
|
|
logging.writeToFile("lscgctl not found, attempting to run lssetup...")
|
|
|
|
|
|
|
|
|
|
# Try to run lssetup
|
|
|
|
|
if os.path.exists(self.LSSETUP_PATH):
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
[self.LSSETUP_PATH],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=30
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
|
|
logging.writeToFile("lssetup completed successfully")
|
|
|
|
|
else:
|
|
|
|
|
logging.writeToFile(f"lssetup failed: {result.stderr}")
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
logging.writeToFile(f"lssetup not found at {self.LSSETUP_PATH}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Check if cgroups are enabled in OLS config
|
|
|
|
|
if not self._check_ols_cgroups_enabled():
|
|
|
|
|
logging.writeToFile("Enabling cgroups in OpenLiteSpeed configuration...")
|
|
|
|
|
if not self._enable_ols_cgroups():
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
self._initialized = True
|
|
|
|
|
logging.writeToFile("OpenLiteSpeed cgroups support ready")
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Error ensuring cgroups enabled: {str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _check_ols_cgroups_enabled(self):
|
|
|
|
|
"""
|
|
|
|
|
Check if cgroups are enabled in OpenLiteSpeed config
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True if enabled, False otherwise
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
if not os.path.exists(self.OLS_CONF_PATH):
|
|
|
|
|
logging.writeToFile(f"OLS config not found at {self.OLS_CONF_PATH}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
with open(self.OLS_CONF_PATH, 'r') as f:
|
|
|
|
|
config = f.read()
|
|
|
|
|
|
|
|
|
|
# Look for CGIRLimit section and check cgroups value
|
|
|
|
|
# Pattern: cgroups followed by whitespace and value
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
# Find CGIRLimit section
|
|
|
|
|
cgirlimit_match = re.search(r'CGIRLimit\s*\{([^}]+)\}', config, re.DOTALL)
|
|
|
|
|
if not cgirlimit_match:
|
|
|
|
|
logging.writeToFile("CGIRLimit section not found in OLS config")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
cgirlimit_section = cgirlimit_match.group(1)
|
|
|
|
|
|
|
|
|
|
# Check for cgroups setting
|
|
|
|
|
cgroups_match = re.search(r'cgroups\s+(\d+)', cgirlimit_section)
|
|
|
|
|
if cgroups_match:
|
|
|
|
|
value = int(cgroups_match.group(1))
|
|
|
|
|
# 1 = On, 0 = Off, 2 = Disabled
|
|
|
|
|
if value == 1:
|
|
|
|
|
logging.writeToFile("cgroups already enabled in OLS config")
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
logging.writeToFile(f"cgroups is set to {value} (need 1 for enabled)")
|
|
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
logging.writeToFile("cgroups setting not found in CGIRLimit section")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Error checking OLS cgroups config: {str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _enable_ols_cgroups(self):
|
|
|
|
|
"""
|
|
|
|
|
Enable cgroups in OpenLiteSpeed configuration
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True if successful, False otherwise
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
if not os.path.exists(self.OLS_CONF_PATH):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Read the config file
|
|
|
|
|
with open(self.OLS_CONF_PATH, 'r') as f:
|
|
|
|
|
config = f.read()
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
# Find CGIRLimit section
|
|
|
|
|
cgirlimit_match = re.search(r'(CGIRLimit\s*\{[^}]+\})', config, re.DOTALL)
|
|
|
|
|
if not cgirlimit_match:
|
|
|
|
|
logging.writeToFile("CGIRLimit section not found, cannot enable cgroups")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
old_section = cgirlimit_match.group(1)
|
|
|
|
|
|
|
|
|
|
# Check if cgroups line exists
|
|
|
|
|
if re.search(r'cgroups\s+\d+', old_section):
|
|
|
|
|
# Replace existing cgroups value with 1
|
|
|
|
|
new_section = re.sub(r'cgroups\s+\d+', 'cgroups 1', old_section)
|
|
|
|
|
else:
|
|
|
|
|
# Add cgroups line before the closing brace
|
|
|
|
|
new_section = old_section.replace('}', ' cgroups 1\n}')
|
|
|
|
|
|
|
|
|
|
# Replace in config
|
|
|
|
|
new_config = config.replace(old_section, new_section)
|
|
|
|
|
|
|
|
|
|
# Backup original config
|
|
|
|
|
backup_path = self.OLS_CONF_PATH + '.backup'
|
|
|
|
|
with open(backup_path, 'w') as f:
|
|
|
|
|
f.write(config)
|
|
|
|
|
|
|
|
|
|
# Write new config
|
|
|
|
|
with open(self.OLS_CONF_PATH, 'w') as f:
|
|
|
|
|
f.write(new_config)
|
|
|
|
|
|
|
|
|
|
logging.writeToFile("Enabled cgroups in OLS config, restarting OpenLiteSpeed...")
|
|
|
|
|
|
|
|
|
|
# Graceful restart of OLS
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
['/usr/local/lsws/bin/lswsctrl', 'restart'],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=30
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
|
|
logging.writeToFile("OpenLiteSpeed restarted successfully")
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
logging.writeToFile(f"Failed to restart OpenLiteSpeed: {result.stderr}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Error enabling OLS cgroups: {str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def set_user_limits(self, username, package):
|
|
|
|
|
"""
|
|
|
|
|
Set resource limits for a Linux user using OpenLiteSpeed lscgctl
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
username (str): Linux username (e.g., website owner)
|
|
|
|
|
package (Package): Package model instance with resource limits
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True if successful, False otherwise
|
|
|
|
|
"""
|
|
|
|
|
# Skip if limits not enforced
|
|
|
|
|
if not package.enforceDiskLimits:
|
|
|
|
|
logging.writeToFile(f"Resource limits not enforced for {username} (enforceDiskLimits=0)")
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
# Ensure cgroups are enabled (auto-setup if needed)
|
|
|
|
|
if not self._ensure_cgroups_enabled():
|
|
|
|
|
logging.writeToFile(f"cgroups not available, skipping resource limits for {username}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Convert package limits to lscgctl format
|
|
|
|
|
# CPU: convert cores to percentage (1 core = 100%, 2 cores = 200%, etc.)
|
|
|
|
|
cpu_percent = package.cpuCores * 100
|
|
|
|
|
|
|
|
|
|
# Memory: convert MB to format with M suffix
|
|
|
|
|
memory_limit = f"{package.memoryLimitMB}M"
|
|
|
|
|
|
|
|
|
|
# Tasks: use procHardLimit as max tasks
|
|
|
|
|
max_tasks = package.procHardLimit
|
|
|
|
|
|
|
|
|
|
# Build lscgctl command
|
|
|
|
|
# Format: lscgctl set username --cpu 100 --mem 1024M --tasks 500
|
|
|
|
|
cmd = [
|
|
|
|
|
self.LSCGCTL_PATH,
|
|
|
|
|
'set',
|
|
|
|
|
username,
|
|
|
|
|
'--cpu', str(cpu_percent),
|
|
|
|
|
'--mem', memory_limit,
|
|
|
|
|
'--tasks', str(max_tasks)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Note: I/O limits may require additional configuration
|
|
|
|
|
# Check if lscgctl supports --io parameter
|
|
|
|
|
|
|
|
|
|
logging.writeToFile(f"Setting limits for user {username}: CPU={cpu_percent}%, MEM={memory_limit}, TASKS={max_tasks}")
|
|
|
|
|
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
cmd,
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=10
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
|
|
logging.writeToFile(f"Successfully set resource limits for {username}")
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
error_msg = result.stderr if result.stderr else result.stdout
|
|
|
|
|
logging.writeToFile(f"Failed to set limits for {username}: {error_msg}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
|
logging.writeToFile(f"Timeout setting resource limits for {username}")
|
|
|
|
|
return False
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Error setting resource limits for {username}: {str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def remove_user_limits(self, username):
|
|
|
|
|
"""
|
|
|
|
|
Remove resource limits for a Linux user
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
username (str): Linux username
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True if successful, False otherwise
|
|
|
|
|
"""
|
|
|
|
|
if not os.path.exists(self.LSCGCTL_PATH):
|
|
|
|
|
logging.writeToFile(f"lscgctl not available, skipping limit removal for {username}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Use lscgctl to remove limits
|
|
|
|
|
# Format: lscgctl remove username
|
|
|
|
|
cmd = [self.LSCGCTL_PATH, 'remove', username]
|
|
|
|
|
|
|
|
|
|
logging.writeToFile(f"Removing resource limits for user {username}")
|
|
|
|
|
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
cmd,
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=10
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
|
|
logging.writeToFile(f"Successfully removed resource limits for {username}")
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
error_msg = result.stderr if result.stderr else result.stdout
|
|
|
|
|
# It's not critical if removal fails (user may not have had limits)
|
|
|
|
|
logging.writeToFile(f"Note: Could not remove limits for {username}: {error_msg}")
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
|
logging.writeToFile(f"Timeout removing resource limits for {username}")
|
|
|
|
|
return False
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Error removing resource limits for {username}: {str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def get_user_limits(self, username):
|
|
|
|
|
"""
|
|
|
|
|
Get current resource limits for a Linux user
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
username (str): Linux username
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
dict: Current limits or None
|
|
|
|
|
"""
|
|
|
|
|
if not os.path.exists(self.LSCGCTL_PATH):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Use lscgctl to get limits
|
|
|
|
|
# Format: lscgctl get username
|
|
|
|
|
cmd = [self.LSCGCTL_PATH, 'get', username]
|
|
|
|
|
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
cmd,
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=10
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
|
|
# Parse the output (format may vary)
|
|
|
|
|
return {'output': result.stdout.strip()}
|
|
|
|
|
else:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Error getting resource limits for {username}: {str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def set_inode_limit(self, domain, username, inode_limit):
|
|
|
|
|
"""
|
|
|
|
|
Set inode (file count) limit for a website using filesystem quotas
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
domain (str): Website domain name
|
|
|
|
|
username (str): System username for the website
|
|
|
|
|
inode_limit (int): Maximum number of files/directories
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: True if successful, False otherwise
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
# Check if quota tools are available
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
['which', 'setquota'],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
timeout=5
|
|
|
|
|
)
|
|
|
|
|
if result.returncode != 0:
|
|
|
|
|
logging.writeToFile("setquota command not found, skipping inode limit")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Set inode quota using setquota
|
|
|
|
|
# Format: setquota -u username 0 0 soft_inode hard_inode /
|
|
|
|
|
result = subprocess.run(
|
|
|
|
|
['setquota', '-u', username, '0', '0',
|
|
|
|
|
str(inode_limit), str(inode_limit), '/'],
|
|
|
|
|
check=True,
|
|
|
|
|
capture_output=True,
|
|
|
|
|
timeout=10
|
|
|
|
|
)
|
|
|
|
|
logging.writeToFile(f"Set inode limit for {domain} ({username}): {inode_limit}")
|
|
|
|
|
return True
|
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
|
logging.writeToFile(f"Timeout setting inode limit for {domain}")
|
|
|
|
|
return False
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
|
logging.writeToFile(f"Failed to set inode limit: {e.stderr.decode() if e.stderr else str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Failed to set inode limit: {str(e)}")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_cgroup_support(self):
|
|
|
|
|
"""
|
|
|
|
|
Check if OpenLiteSpeed cgroups v2 support is available
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
dict: Support status for various features
|
|
|
|
|
"""
|
|
|
|
|
support = {
|
|
|
|
|
'cgroups_v2': False,
|
|
|
|
|
'lscgctl_available': False,
|
|
|
|
|
'memory_controller': False,
|
|
|
|
|
'cpu_controller': False,
|
|
|
|
|
'io_controller': False,
|
2025-11-11 17:27:11 +05:00
|
|
|
'quota_tools': False,
|
|
|
|
|
'rhel8_family': False,
|
|
|
|
|
'rhel8_needs_enablement': False,
|
|
|
|
|
'os_name': 'Unknown'
|
2025-11-11 17:14:39 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try:
|
2025-11-11 17:27:11 +05:00
|
|
|
# Check for RHEL 8 family
|
|
|
|
|
redhat_release_paths = ['/etc/redhat-release', '/etc/system-release']
|
|
|
|
|
for release_file in redhat_release_paths:
|
|
|
|
|
if os.path.exists(release_file):
|
|
|
|
|
try:
|
|
|
|
|
with open(release_file, 'r') as f:
|
|
|
|
|
release_content = f.read()
|
|
|
|
|
support['os_name'] = release_content.strip()
|
|
|
|
|
|
|
|
|
|
# Check for RHEL 8 family
|
|
|
|
|
if ('release 8' in release_content.lower() or
|
|
|
|
|
'release 8.' in release_content.lower()):
|
|
|
|
|
if any(distro in release_content.lower() for distro in
|
|
|
|
|
['red hat', 'almalinux', 'rocky', 'cloudlinux', 'centos']):
|
|
|
|
|
support['rhel8_family'] = True
|
|
|
|
|
|
|
|
|
|
# Check if cgroups v2 is actually mounted
|
|
|
|
|
result = subprocess.run(['mount'], capture_output=True,
|
|
|
|
|
text=True, timeout=5)
|
|
|
|
|
if 'cgroup2' not in result.stdout:
|
|
|
|
|
support['rhel8_needs_enablement'] = True
|
|
|
|
|
break
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
2025-11-11 17:14:39 +05:00
|
|
|
# Check cgroups v2
|
|
|
|
|
if os.path.exists('/sys/fs/cgroup/cgroup.controllers'):
|
|
|
|
|
support['cgroups_v2'] = True
|
|
|
|
|
|
|
|
|
|
# Check controllers
|
|
|
|
|
with open('/sys/fs/cgroup/cgroup.controllers', 'r') as f:
|
|
|
|
|
controllers = f.read().strip().split()
|
|
|
|
|
support['memory_controller'] = 'memory' in controllers
|
|
|
|
|
support['cpu_controller'] = 'cpu' in controllers
|
|
|
|
|
support['io_controller'] = 'io' in controllers
|
|
|
|
|
|
|
|
|
|
# Check lscgctl tool
|
|
|
|
|
support['lscgctl_available'] = os.path.exists(self.LSCGCTL_PATH)
|
|
|
|
|
|
|
|
|
|
# Check quota tools
|
|
|
|
|
result = subprocess.run(['which', 'setquota'], capture_output=True, timeout=5)
|
|
|
|
|
support['quota_tools'] = result.returncode == 0
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.writeToFile(f"Error checking cgroup support: {str(e)}")
|
|
|
|
|
|
|
|
|
|
return support
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Singleton instance
|
|
|
|
|
resource_manager = ResourceLimitsManager()
|