diff --git a/cluster-autoscaler/cloudprovider/hetzner/hetzner_cloud_provider.go b/cluster-autoscaler/cloudprovider/hetzner/hetzner_cloud_provider.go index 046a8b456e81..0a2b5a0e5112 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hetzner_cloud_provider.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hetzner_cloud_provider.go @@ -17,6 +17,7 @@ limitations under the License. package hetzner import ( + "context" "fmt" "regexp" "strconv" @@ -178,6 +179,31 @@ func (d *HetznerCloudProvider) Refresh() error { return nil } +// Check if any defined placement groups could potentially have more than the maximum allowed number of nodes +func getLargePlacementGroups(nodeGroups map[string]*hetznerNodeGroup, threshold int) []int64 { + placementGroupTotals := make(map[int64]int) + + // Calculate totals for each placement group + for _, nodeGroup := range nodeGroups { + if nodeGroup.placementGroup == nil || nodeGroup.placementGroup.ID == 0 { + continue + } + + placementGroupID := nodeGroup.placementGroup.ID + placementGroupTotals[placementGroupID] += nodeGroup.maxSize + } + + // Collect placement groups with total maxSize > threshold + var largePlacementGroupIDs []int64 + for id, totalMaxSize := range placementGroupTotals { + if totalMaxSize > threshold { + largePlacementGroupIDs = append(largePlacementGroupIDs, id) + } + } + + return largePlacementGroupIDs +} + // BuildHetzner builds the Hetzner cloud provider. func BuildHetzner(_ config.AutoscalingOptions, do cloudprovider.NodeGroupDiscoveryOptions, rl *cloudprovider.ResourceLimiter) cloudprovider.CloudProvider { manager, err := newManager() @@ -225,6 +251,57 @@ func BuildHetzner(_ config.AutoscalingOptions, do cloudprovider.NodeGroupDiscove targetSize: len(servers), clusterUpdateMutex: &clusterUpdateLock, } + + // If a placement group was specified, check with the API to see if it exists + if manager.clusterConfig.IsUsingNewFormat { + + placementGroupRef := manager.clusterConfig.NodeConfigs[spec.name].PlacementGroup + + if placementGroupRef == "" { + continue + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + placementGroup, _, err := manager.client.PlacementGroup.Get(ctx, placementGroupRef) + + // Check if an error occurred + if err != nil { + if err == context.DeadlineExceeded { + klog.Fatalf("Timed out checking if placement group `%s` exists.", placementGroupRef) + } else { + klog.Fatalf("Failed to verify if placement group `%s` exists error: %v", placementGroupRef, err) + } + } + + // If the placement group exists, add it to the node group config + if placementGroup != nil { + manager.nodeGroups[spec.name].placementGroup = placementGroup + } else { + klog.Fatalf("The requested placement group `%s` does not appear to exist.", placementGroupRef) + } + } + } + + // Get placement groups with total maxSize over the maximum allowed + maxPlacementGroupSize := 10 + + largePlacementGroups := getLargePlacementGroups(manager.nodeGroups, maxPlacementGroupSize) + + // Fail if we have placement groups over the max size + if len(largePlacementGroups) > 0 { + + // Gather placement group names + var placementGroupIDs string + for i, placementGroupID := range largePlacementGroups { + if i > 0 { + placementGroupIDs += ", " + } + placementGroupIDs += strconv.FormatInt(placementGroupID, 10) + } + + klog.Fatalf("The following placement groups have a potential size over the allowed maximum of %d: %s.", maxPlacementGroupSize, placementGroupIDs) } return provider diff --git a/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go b/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go index 6dd9488d9a3b..1308e5ce7c50 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go @@ -73,9 +73,10 @@ type ImageList struct { // NodeConfig holds the configuration for a single nodepool type NodeConfig struct { - CloudInit string - Taints []apiv1.Taint - Labels map[string]string + CloudInit string + PlacementGroup string + Taints []apiv1.Taint + Labels map[string]string } // LegacyConfig holds the configuration in the legacy format diff --git a/cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go b/cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go index c0f7510a8b3d..7e5ee63549bb 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go @@ -48,6 +48,7 @@ type hetznerNodeGroup struct { instanceType string clusterUpdateMutex *sync.Mutex + placementGroup *hcloud.PlacementGroup } type hetznerNodeGroupSpec struct { @@ -485,6 +486,7 @@ func createServer(n *hetznerNodeGroup) error { EnableIPv4: n.manager.publicIPv4, EnableIPv6: n.manager.publicIPv6, }, + PlacementGroup: n.placementGroup, } if n.manager.sshKey != nil { opts.SSHKeys = []*hcloud.SSHKey{n.manager.sshKey}