115115import com .cloud .vm .UserVmManager ;
116116import com .cloud .vm .VirtualMachine ;
117117import com .cloud .vm .VmDetailConstants ;
118- import com .cloud .vm .dao .VMInstanceDao ;
119118import org .apache .cloudstack .api .ApiCommandResourceType ;
120119import org .apache .cloudstack .context .CallContext ;
121120import org .apache .logging .log4j .Level ;
@@ -151,8 +150,6 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
151150 @ Inject
152151 protected LoadBalancerDao loadBalancerDao ;
153152 @ Inject
154- protected VMInstanceDao vmInstanceDao ;
155- @ Inject
156153 protected UserVmManager userVmManager ;
157154 @ Inject
158155 protected LaunchPermissionDao launchPermissionDao ;
@@ -176,8 +173,33 @@ protected void init() {
176173 kubernetesClusterNodeNamePrefix = getKubernetesClusterNodeNamePrefix ();
177174 }
178175
176+ protected List <HostVO > filterHostsByAffinityConstraints (List <HostVO > hosts , AffinityConstraints constraints , DataCenter zone )
177+ throws InsufficientServerCapacityException {
178+ if (constraints .hasHostAffinity && Objects .nonNull (constraints .requiredHostId )) {
179+ hosts = hosts .stream ().filter (host -> host .getId () == constraints .requiredHostId .longValue ()).collect (Collectors .toList ());
180+ if (CollectionUtils .isEmpty (hosts )) {
181+ String msg = String .format ("Cannot find capacity for Kubernetes cluster: host affinity requires all VMs on host %d but it is not available in zone %s" ,
182+ constraints .requiredHostId , zone .getName ());
183+ throw new InsufficientServerCapacityException (msg , DataCenter .class , zone .getId ());
184+ }
185+ }
186+
187+ if (constraints .hasHostAntiAffinity ) {
188+ hosts = hosts .stream ().filter (host -> !constraints .antiAffinityOccupiedHosts .contains (host .getId ())).collect (Collectors .toList ());
189+ if (CollectionUtils .isEmpty (hosts )) {
190+ String msg = String .format ("Cannot find capacity for Kubernetes cluster: host anti-affinity requires each VM on a separate host, " +
191+ "but all %d available hosts in zone %s are already occupied by existing cluster VMs" ,
192+ constraints .antiAffinityOccupiedHosts .size (), zone .getName ());
193+ throw new InsufficientServerCapacityException (msg , DataCenter .class , zone .getId ());
194+ }
195+ }
196+
197+ return hosts ;
198+ }
199+
179200 protected DeployDestination plan (final long nodesCount , final DataCenter zone , final ServiceOffering offering ,
180- final Long domainId , final Long accountId , final Hypervisor .HypervisorType hypervisorType , CPU .CPUArch arch ) throws InsufficientServerCapacityException {
201+ final Long domainId , final Long accountId , final Hypervisor .HypervisorType hypervisorType ,
202+ CPU .CPUArch arch , KubernetesClusterNodeType nodeType ) throws InsufficientServerCapacityException {
181203 final int cpu_requested = offering .getCpu () * offering .getSpeed ();
182204 final long ram_requested = offering .getRamSize () * 1024L * 1024L ;
183205 boolean useDedicatedHosts = false ;
@@ -198,18 +220,22 @@ protected DeployDestination plan(final long nodesCount, final DataCenter zone, f
198220 if (hosts .isEmpty ()) {
199221 hosts = resourceManager .listAllHostsInOneZoneByType (Host .Type .Routing , zone .getId ());
200222 }
201- if (hypervisorType != null ) {
223+ if (Objects . nonNull ( hypervisorType ) ) {
202224 hosts = hosts .stream ().filter (x -> x .getHypervisorType () == hypervisorType ).collect (Collectors .toList ());
203225 }
204- if (arch != null ) {
226+ if (Objects . nonNull ( arch ) ) {
205227 hosts = hosts .stream ().filter (x -> x .getArch ().equals (arch )).collect (Collectors .toList ());
206228 }
207229 if (CollectionUtils .isEmpty (hosts )) {
208230 String msg = String .format ("Cannot find enough capacity for Kubernetes cluster(requested cpu=%d memory=%s) with offering: %s hypervisor: %s and arch: %s" ,
209- cpu_requested * nodesCount , toHumanReadableSize (ram_requested * nodesCount ), offering .getName (), clusterTemplate .getHypervisorType ().toString (), arch .getType ());
231+ cpu_requested * nodesCount , toHumanReadableSize (ram_requested * nodesCount ), offering .getName (), clusterTemplate .getHypervisorType ().toString (),
232+ Objects .nonNull (arch ) ? arch .getType () : "null" );
210233 logAndThrow (Level .WARN , msg , new InsufficientServerCapacityException (msg , DataCenter .class , zone .getId ()));
211234 }
212235
236+ AffinityConstraints affinityConstraints = resolveAffinityConstraints (nodeType , domainId , accountId );
237+ hosts = filterHostsByAffinityConstraints (hosts , affinityConstraints , zone );
238+
213239 final Map <String , Pair <HostVO , Integer >> hosts_with_resevered_capacity = new ConcurrentHashMap <String , Pair <HostVO , Integer >>();
214240 for (HostVO h : hosts ) {
215241 hosts_with_resevered_capacity .put (h .getUuid (), new Pair <HostVO , Integer >(h , 0 ));
@@ -229,6 +255,9 @@ protected DeployDestination plan(final long nodesCount, final DataCenter zone, f
229255 continue ;
230256 }
231257 int reserved = hp .second ();
258+ if (affinityConstraints .hasHostAntiAffinity && reserved > 0 ) {
259+ continue ;
260+ }
232261 reserved ++;
233262 ClusterVO cluster = clusterDao .findById (h .getClusterId ());
234263 ClusterDetailsVO cluster_detail_cpu = clusterDetailsDao .findDetail (cluster .getId (), "cpuOvercommitRatio" );
@@ -263,10 +292,17 @@ protected DeployDestination plan(final long nodesCount, final DataCenter zone, f
263292 }
264293 return new DeployDestination (zone , null , null , null );
265294 }
266- String msg = String .format ("Cannot find enough capacity for Kubernetes cluster(requested cpu=%d memory=%s) with offering: %s hypervisor: %s and arch: %s" ,
267- cpu_requested * nodesCount , toHumanReadableSize (ram_requested * nodesCount ), offering .getName (), clusterTemplate .getHypervisorType ().toString (), arch .getType ());
268-
269- logger .warn (msg );
295+ String msg ;
296+ if (affinityConstraints .hasHostAntiAffinity ) {
297+ msg = String .format ("Cannot find enough capacity for Kubernetes cluster (requested cpu=%d memory=%s) with offering: %s. " +
298+ "Host anti-affinity requires %d separate hosts but not enough suitable hosts are available in zone %s" ,
299+ cpu_requested * nodesCount , toHumanReadableSize (ram_requested * nodesCount ), offering .getName (),
300+ nodesCount , zone .getName ());
301+ } else {
302+ msg = String .format ("Cannot find enough capacity for Kubernetes cluster(requested cpu=%d memory=%s) with offering: %s hypervisor: %s and arch: %s" ,
303+ cpu_requested * nodesCount , toHumanReadableSize (ram_requested * nodesCount ), offering .getName (), clusterTemplate .getHypervisorType ().toString (),
304+ Objects .nonNull (arch ) ? arch .getType () : "null" );
305+ }
270306 throw new InsufficientServerCapacityException (msg , DataCenter .class , zone .getId ());
271307 }
272308
@@ -295,7 +331,7 @@ protected Map<String, DeployDestination> planKubernetesCluster(Long domainId, Lo
295331 if (logger .isDebugEnabled ()) {
296332 logger .debug ("Checking deployment destination for {} nodes on Kubernetes cluster : {} in zone : {}" , nodeType .name (), kubernetesCluster .getName (), zone .getName ());
297333 }
298- DeployDestination planForNodeType = plan (nodes , zone , nodeOffering , domainId , accountId , hypervisorType , arch );
334+ DeployDestination planForNodeType = plan (nodes , zone , nodeOffering , domainId , accountId , hypervisorType , arch , nodeType );
299335 destinationMap .put (nodeType .name (), planForNodeType );
300336 }
301337 return destinationMap ;
0 commit comments