Spaces:
Sleeping
Sleeping
| from solverforge_legacy.solver import SolverStatus | |
| from solverforge_legacy.solver.score import HardSoftScore | |
| from solverforge_legacy.solver.domain import ( | |
| planning_entity, | |
| planning_solution, | |
| PlanningId, | |
| PlanningScore, | |
| PlanningVariable, | |
| PlanningEntityCollectionProperty, | |
| ProblemFactCollectionProperty, | |
| ValueRangeProvider, | |
| ) | |
| from typing import Annotated, Optional, List, Union | |
| from dataclasses import dataclass, field | |
| from .json_serialization import ( | |
| JsonDomainBase, | |
| IdSerializer, | |
| IdListSerializer, | |
| VMListValidator, | |
| ServerValidator, | |
| ) | |
| from pydantic import Field | |
| class Server: | |
| """ | |
| A physical server that can host virtual machines. | |
| Servers have capacity limits for CPU cores, memory (GB), and storage (GB). | |
| This is a problem fact - it doesn't change during solving. | |
| """ | |
| id: Annotated[str, PlanningId] | |
| name: str | |
| cpu_cores: int | |
| memory_gb: int | |
| storage_gb: int | |
| rack: Optional[str] = None | |
| def __str__(self): | |
| return self.name | |
| def __repr__(self): | |
| return f"Server({self.id}, {self.name})" | |
| def __hash__(self): | |
| return hash(self.id) | |
| def __eq__(self, other): | |
| if not isinstance(other, Server): | |
| return False | |
| return self.id == other.id | |
| class VM: | |
| """ | |
| A virtual machine that needs to be placed on a server. | |
| VMs have resource requirements (CPU, memory, storage) and optional | |
| affinity/anti-affinity constraints for placement. | |
| The server field is the planning variable that the solver optimizes. | |
| """ | |
| id: Annotated[str, PlanningId] | |
| name: str | |
| cpu_cores: int | |
| memory_gb: int | |
| storage_gb: int | |
| priority: int = 1 | |
| affinity_group: Optional[str] = None | |
| anti_affinity_group: Optional[str] = None | |
| server: Annotated[Optional[Server], PlanningVariable] = None | |
| def __str__(self): | |
| return self.name | |
| def __repr__(self): | |
| return f"VM({self.id}, {self.name})" | |
| class VMPlacementPlan: | |
| """ | |
| The planning solution containing all servers and VMs. | |
| The solver will assign VMs to servers while respecting capacity constraints, | |
| affinity/anti-affinity rules, and optimizing for consolidation and balance. | |
| """ | |
| name: str | |
| servers: Annotated[list[Server], ProblemFactCollectionProperty, ValueRangeProvider] | |
| vms: Annotated[list[VM], PlanningEntityCollectionProperty] | |
| score: Annotated[Optional[HardSoftScore], PlanningScore] = None | |
| solver_status: SolverStatus = SolverStatus.NOT_SOLVING | |
| def get_vms_on_server(self, server: Server) -> list: | |
| """Get all VMs assigned to a specific server.""" | |
| return [vm for vm in self.vms if vm.server == server] | |
| def get_server_used_cpu(self, server: Server) -> int: | |
| """Get total CPU cores used on a server.""" | |
| return sum(vm.cpu_cores for vm in self.vms if vm.server == server) | |
| def get_server_used_memory(self, server: Server) -> int: | |
| """Get total memory (GB) used on a server.""" | |
| return sum(vm.memory_gb for vm in self.vms if vm.server == server) | |
| def get_server_used_storage(self, server: Server) -> int: | |
| """Get total storage (GB) used on a server.""" | |
| return sum(vm.storage_gb for vm in self.vms if vm.server == server) | |
| def total_servers(self) -> int: | |
| return len(self.servers) | |
| def active_servers(self) -> int: | |
| active_server_ids = set(vm.server.id for vm in self.vms if vm.server is not None) | |
| return len(active_server_ids) | |
| def unassigned_vms(self) -> int: | |
| return sum(1 for vm in self.vms if vm.server is None) | |
| def total_cpu_utilization(self) -> float: | |
| total_capacity = sum(s.cpu_cores for s in self.servers) | |
| total_used = sum(vm.cpu_cores for vm in self.vms if vm.server is not None) | |
| if total_capacity == 0: | |
| return 0.0 | |
| return total_used / total_capacity | |
| def total_memory_utilization(self) -> float: | |
| total_capacity = sum(s.memory_gb for s in self.servers) | |
| total_used = sum(vm.memory_gb for vm in self.vms if vm.server is not None) | |
| if total_capacity == 0: | |
| return 0.0 | |
| return total_used / total_capacity | |
| def total_storage_utilization(self) -> float: | |
| total_capacity = sum(s.storage_gb for s in self.servers) | |
| total_used = sum(vm.storage_gb for vm in self.vms if vm.server is not None) | |
| if total_capacity == 0: | |
| return 0.0 | |
| return total_used / total_capacity | |
| def __str__(self): | |
| return f"VMPlacementPlan(name={self.name}, servers={len(self.servers)}, vms={len(self.vms)})" | |
| # Pydantic REST models for API (used for deserialization and context) | |
| class VMModel(JsonDomainBase): | |
| id: str | |
| name: str | |
| cpu_cores: int = Field(..., alias="cpuCores") | |
| memory_gb: int = Field(..., alias="memoryGb") | |
| storage_gb: int = Field(..., alias="storageGb") | |
| priority: int = 1 | |
| affinity_group: Optional[str] = Field(None, alias="affinityGroup") | |
| anti_affinity_group: Optional[str] = Field(None, alias="antiAffinityGroup") | |
| server: Annotated[ | |
| Union[str, "ServerModel", None], | |
| IdSerializer, | |
| ServerValidator, | |
| ] = None | |
| class ServerModel(JsonDomainBase): | |
| id: str | |
| name: str | |
| cpu_cores: int = Field(..., alias="cpuCores") | |
| memory_gb: int = Field(..., alias="memoryGb") | |
| storage_gb: int = Field(..., alias="storageGb") | |
| rack: Optional[str] = None | |
| vms: Annotated[ | |
| List[Union[str, VMModel]], | |
| IdListSerializer, | |
| VMListValidator, | |
| ] = Field(default_factory=list) | |
| used_cpu: int = Field(0, alias="usedCpu") | |
| used_memory: int = Field(0, alias="usedMemory") | |
| used_storage: int = Field(0, alias="usedStorage") | |
| cpu_utilization: float = Field(0.0, alias="cpuUtilization") | |
| memory_utilization: float = Field(0.0, alias="memoryUtilization") | |
| storage_utilization: float = Field(0.0, alias="storageUtilization") | |
| class VMPlacementPlanModel(JsonDomainBase): | |
| name: str | |
| servers: List[ServerModel] | |
| vms: List[VMModel] | |
| score: Optional[str] = None | |
| solver_status: Optional[str] = Field(None, alias="solverStatus") | |
| total_servers: int = Field(0, alias="totalServers") | |
| active_servers: int = Field(0, alias="activeServers") | |
| unassigned_vms: int = Field(0, alias="unassignedVms") | |
| total_cpu_utilization: float = Field(0.0, alias="totalCpuUtilization") | |
| total_memory_utilization: float = Field(0.0, alias="totalMemoryUtilization") | |
| total_storage_utilization: float = Field(0.0, alias="totalStorageUtilization") | |