Spaces:
Sleeping
Sleeping
File size: 6,587 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 |
from solverforge_legacy.solver.score import (
ConstraintFactory,
ConstraintCollectors,
Joiners,
HardSoftScore,
constraint_provider,
)
from .domain import Server, VM
# Constraint names
CPU_CAPACITY = "cpuCapacity"
MEMORY_CAPACITY = "memoryCapacity"
STORAGE_CAPACITY = "storageCapacity"
ANTI_AFFINITY = "antiAffinity"
AFFINITY = "affinity"
MINIMIZE_SERVERS_USED = "minimizeServersUsed"
BALANCE_UTILIZATION = "balanceUtilization"
PRIORITIZE_PLACEMENT = "prioritizePlacement"
@constraint_provider
def define_constraints(factory: ConstraintFactory):
return [
# Hard constraints
cpu_capacity(factory),
memory_capacity(factory),
storage_capacity(factory),
anti_affinity(factory),
# Soft constraints
affinity(factory),
minimize_servers_used(factory),
balance_utilization(factory),
prioritize_placement(factory),
]
##############################################
# Hard constraints
##############################################
def cpu_capacity(factory: ConstraintFactory):
"""
Hard constraint: Server CPU capacity cannot be exceeded.
Groups VMs by server and penalizes if total CPU exceeds capacity.
"""
return (
factory.for_each(VM)
.filter(lambda vm: vm.server is not None)
.group_by(lambda vm: vm.server, ConstraintCollectors.sum(lambda vm: vm.cpu_cores))
.filter(lambda server, total_cpu: total_cpu > server.cpu_cores)
.penalize(
HardSoftScore.ONE_HARD,
lambda server, total_cpu: total_cpu - server.cpu_cores,
)
.as_constraint(CPU_CAPACITY)
)
def memory_capacity(factory: ConstraintFactory):
"""
Hard constraint: Server memory capacity cannot be exceeded.
Groups VMs by server and penalizes if total memory exceeds capacity.
"""
return (
factory.for_each(VM)
.filter(lambda vm: vm.server is not None)
.group_by(lambda vm: vm.server, ConstraintCollectors.sum(lambda vm: vm.memory_gb))
.filter(lambda server, total_memory: total_memory > server.memory_gb)
.penalize(
HardSoftScore.ONE_HARD,
lambda server, total_memory: total_memory - server.memory_gb,
)
.as_constraint(MEMORY_CAPACITY)
)
def storage_capacity(factory: ConstraintFactory):
"""
Hard constraint: Server storage capacity cannot be exceeded.
Groups VMs by server and penalizes if total storage exceeds capacity.
"""
return (
factory.for_each(VM)
.filter(lambda vm: vm.server is not None)
.group_by(lambda vm: vm.server, ConstraintCollectors.sum(lambda vm: vm.storage_gb))
.filter(lambda server, total_storage: total_storage > server.storage_gb)
.penalize(
HardSoftScore.ONE_HARD,
lambda server, total_storage: total_storage - server.storage_gb,
)
.as_constraint(STORAGE_CAPACITY)
)
def anti_affinity(factory: ConstraintFactory):
"""
Hard constraint: VMs in the same anti-affinity group must be on different servers.
This is commonly used for database replicas, redundant services, etc.
Penalizes each pair of VMs that violate the constraint.
"""
return (
factory.for_each_unique_pair(
VM,
Joiners.equal(lambda vm: vm.anti_affinity_group),
Joiners.equal(lambda vm: vm.server),
)
.filter(lambda vm1, vm2: vm1.anti_affinity_group is not None)
.filter(lambda vm1, vm2: vm1.server is not None)
.penalize(HardSoftScore.ONE_HARD)
.as_constraint(ANTI_AFFINITY)
)
##############################################
# Soft constraints
##############################################
def affinity(factory: ConstraintFactory):
"""
Soft constraint: VMs in the same affinity group should be on the same server.
This is commonly used for tightly coupled services that benefit from
low-latency communication. Penalizes each pair of VMs on different servers.
"""
return (
factory.for_each_unique_pair(
VM,
Joiners.equal(lambda vm: vm.affinity_group),
)
.filter(lambda vm1, vm2: vm1.affinity_group is not None)
.filter(lambda vm1, vm2: vm1.server is not None and vm2.server is not None)
.filter(lambda vm1, vm2: vm1.server != vm2.server)
.penalize(HardSoftScore.ONE_SOFT, lambda vm1, vm2: 100)
.as_constraint(AFFINITY)
)
def minimize_servers_used(factory: ConstraintFactory):
"""
Soft constraint: Minimize the number of servers in use.
Consolidating VMs onto fewer servers reduces power consumption,
cooling costs, and management overhead. Each active server incurs a cost.
Weight is lower than prioritize_placement to ensure VMs get assigned
before optimizing for server consolidation.
"""
return (
factory.for_each(VM)
.filter(lambda vm: vm.server is not None)
.group_by(lambda vm: vm.server, ConstraintCollectors.count())
.penalize(HardSoftScore.ONE_SOFT, lambda server, count: 100)
.as_constraint(MINIMIZE_SERVERS_USED)
)
def balance_utilization(factory: ConstraintFactory):
"""
Soft constraint: Balance utilization across active servers.
Avoids hotspots by penalizing servers with high utilization.
Uses a squared penalty to favor balanced distribution over consolidation
when both are possible.
"""
return (
factory.for_each(VM)
.filter(lambda vm: vm.server is not None)
.group_by(lambda vm: vm.server, ConstraintCollectors.sum(lambda vm: vm.cpu_cores))
.penalize(
HardSoftScore.ONE_SOFT,
lambda server, total_cpu: int((total_cpu / server.cpu_cores) ** 2 * 10) if server.cpu_cores > 0 else 0,
)
.as_constraint(BALANCE_UTILIZATION)
)
def prioritize_placement(factory: ConstraintFactory):
"""
Soft constraint: Higher-priority VMs should be placed.
Penalizes unassigned VMs weighted by their priority. Higher priority VMs
incur a larger penalty when unassigned, encouraging the solver to place
them first.
Base penalty of 10000 ensures VMs are always placed before optimizing
other soft constraints. Priority adds 0-5000 additional penalty.
"""
return (
factory.for_each(VM)
.filter(lambda vm: vm.server is None)
.penalize(HardSoftScore.ONE_SOFT, lambda vm: 10000 + vm.priority * 1000)
.as_constraint(PRIORITIZE_PLACEMENT)
)
|