Spaces:
Sleeping
Sleeping
File size: 6,731 Bytes
e2dcb4d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
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
@dataclass
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
@planning_entity
@dataclass
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})"
@planning_solution
@dataclass
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)
@property
def total_servers(self) -> int:
return len(self.servers)
@property
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)
@property
def unassigned_vms(self) -> int:
return sum(1 for vm in self.vms if vm.server is None)
@property
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
@property
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
@property
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")
|