Create standalone pod_executor package
Created a standalone code execution package independent of MCP-Forge: Structure: - pod_executor/security/ - Resource limits, audit protocols, validation - pod_executor/containers/ - Podman client and container management - pod_executor/simple/ - Stateless code executor - pod_executor/jupyter/ - Stateful Jupyter backend with sessions Key changes: - Removed ForgeConfig dependency - all parameters explicit - Audit logger now a protocol with NullAuditLogger/SimpleFileAuditLogger - Validator now a protocol with NoOpValidator/BasicValidator - All imports updated to pod_executor namespace - Audit calls use simple strings instead of enums Benefits: - Standalone package usable without MCP-Forge - Clear separation between execution engine and MCP protocol - Easier testing and development - Reusable in other projects
This commit is contained in:
parent
8b6b237be9
commit
db75b822f4
14 changed files with 2842 additions and 0 deletions
150
src/pod_executor/security/resource_limits.py
Normal file
150
src/pod_executor/security/resource_limits.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
"""
|
||||
Resource limit parser and validator.
|
||||
|
||||
Parses and validates resource limit strings (memory, CPU, storage).
|
||||
All values must be positive and within reasonable limits.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
def parse_memory_string(memory: str) -> int:
|
||||
"""
|
||||
Parse memory string to bytes.
|
||||
|
||||
Supports: k, m, g suffixes (case-insensitive)
|
||||
Examples: "512m" → 536870912, "2g" → 2147483648
|
||||
|
||||
Args:
|
||||
memory: Memory string with suffix (e.g., "512m", "2g", "1024k")
|
||||
|
||||
Returns:
|
||||
Memory in bytes
|
||||
|
||||
Raises:
|
||||
ValueError: If format is invalid or value is <= 0
|
||||
"""
|
||||
memory = memory.strip()
|
||||
|
||||
# Pattern: optional sign, number (int or float), suffix (k/m/g)
|
||||
pattern = r'^(-?\d+(?:\.\d+)?)\s*([kmgKMG])$'
|
||||
match = re.match(pattern, memory)
|
||||
|
||||
if not match:
|
||||
raise ValueError(
|
||||
f"Invalid memory format: '{memory}'. "
|
||||
f"Expected format: <number><k|m|g> (e.g., '512m', '2g')"
|
||||
)
|
||||
|
||||
value_str, suffix = match.groups()
|
||||
value = float(value_str)
|
||||
|
||||
if value <= 0:
|
||||
raise ValueError(
|
||||
f"Memory value must be positive, got: {value}"
|
||||
)
|
||||
|
||||
# Convert to bytes
|
||||
suffix_lower = suffix.lower()
|
||||
multipliers = {
|
||||
'k': 1024,
|
||||
'm': 1024 * 1024,
|
||||
'g': 1024 * 1024 * 1024,
|
||||
}
|
||||
|
||||
bytes_value = int(value * multipliers[suffix_lower])
|
||||
|
||||
return bytes_value
|
||||
|
||||
|
||||
def parse_cpu_quota(cpu_quota: int) -> int:
|
||||
"""
|
||||
Validate CPU quota value.
|
||||
|
||||
CPU quota is in microseconds per 100ms period.
|
||||
100000 = 100% of one CPU core
|
||||
|
||||
Args:
|
||||
cpu_quota: CPU quota in microseconds (e.g., 50000 for 50% of one core)
|
||||
|
||||
Returns:
|
||||
Validated CPU quota value
|
||||
|
||||
Raises:
|
||||
ValueError: If quota <= 0 or > 1000000 (10 cores max)
|
||||
"""
|
||||
if cpu_quota <= 0:
|
||||
raise ValueError(
|
||||
f"CPU quota must be positive, got: {cpu_quota}"
|
||||
)
|
||||
|
||||
# Maximum of 10 cores (1000000 microseconds)
|
||||
if cpu_quota > 1000000:
|
||||
raise ValueError(
|
||||
f"CPU quota exceeds maximum of 1000000 (10 cores), got: {cpu_quota}"
|
||||
)
|
||||
|
||||
return cpu_quota
|
||||
|
||||
|
||||
def parse_storage_string(storage: str) -> int:
|
||||
"""
|
||||
Parse storage string to bytes (same as memory).
|
||||
|
||||
Args:
|
||||
storage: Storage string with suffix (e.g., "1g", "512m")
|
||||
|
||||
Returns:
|
||||
Storage in bytes
|
||||
|
||||
Raises:
|
||||
ValueError: If format is invalid or value is <= 0
|
||||
"""
|
||||
return parse_memory_string(storage)
|
||||
|
||||
|
||||
class ResourceLimits:
|
||||
"""
|
||||
Resource limits with validation.
|
||||
|
||||
Encapsulates memory, storage, CPU, and timeout limits with validation.
|
||||
Provides conversion to Podman container parameters.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
memory: str,
|
||||
storage: str,
|
||||
cpu_quota: int,
|
||||
timeout: int = 300
|
||||
):
|
||||
"""
|
||||
Initialize resource limits with validation.
|
||||
|
||||
Args:
|
||||
memory: Memory limit string (e.g., "512m", "2g")
|
||||
storage: Storage limit string (e.g., "1g", "10g")
|
||||
cpu_quota: CPU quota in microseconds per 100ms period
|
||||
timeout: Execution timeout in seconds (default: 300)
|
||||
|
||||
Raises:
|
||||
ValueError: If any limit is invalid
|
||||
"""
|
||||
self.memory_bytes = parse_memory_string(memory)
|
||||
self.storage_bytes = parse_storage_string(storage)
|
||||
self.cpu_quota = parse_cpu_quota(cpu_quota)
|
||||
self.timeout = timeout
|
||||
|
||||
def to_podman_params(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Convert to Podman container create parameters.
|
||||
|
||||
Returns:
|
||||
Dictionary of parameters suitable for Podman container creation
|
||||
"""
|
||||
return {
|
||||
"mem_limit": str(self.memory_bytes), # Podman expects string
|
||||
"cpu_quota": self.cpu_quota
|
||||
# Note: storage_bytes tracked internally but not passed to Podman (not supported)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue