Repository: syself/cluster-api-provider-hetzner
Pull request #753 (2023-04-05 13:09:05)

:seedling: Update code of placement group pkg
**What this PR does / why we need it**:
Updating code of placement group package to make it easier to read. Addingt function ClusterTagKey as method on HetznerCluster. Adding unit tests in api folder

**TODOs**:
- [x] squash commits
- [ ] include documentation
- [ ] add unit tests




Patch:
diff --git a/api/v1beta1/hetznercluster_types.go b/api/v1beta1/hetznercluster_types.go
index a5af6f1d..d4eab054 100644
--- a/api/v1beta1/hetznercluster_types.go
+++ b/api/v1beta1/hetznercluster_types.go
@@ -49,7 +49,7 @@ type HetznerClusterSpec struct {
 	ControlPlaneLoadBalancer LoadBalancerSpec `json:"controlPlaneLoadBalancer,omitempty"`
 
 	// +optional
-	HCloudPlacementGroup []HCloudPlacementGroupSpec `json:"hcloudPlacementGroups,omitempty"`
+	HCloudPlacementGroups []HCloudPlacementGroupSpec `json:"hcloudPlacementGroups,omitempty"`
 
 	// HetznerSecretRef is a reference to a token to be used when reconciling this cluster.
 	// This is generated in the security section under API TOKENS. Read & write is necessary.
@@ -66,9 +66,9 @@ type HetznerClusterStatus struct {
 
 	ControlPlaneLoadBalancer *LoadBalancerStatus `json:"controlPlaneLoadBalancer,omitempty"`
 	// +optional
-	HCloudPlacementGroup []HCloudPlacementGroupStatus `json:"hcloudPlacementGroups,omitempty"`
-	FailureDomains       clusterv1.FailureDomains     `json:"failureDomains,omitempty"`
-	Conditions           clusterv1.Conditions         `json:"conditions,omitempty"`
+	HCloudPlacementGroups []HCloudPlacementGroupStatus `json:"hcloudPlacementGroups,omitempty"`
+	FailureDomains        clusterv1.FailureDomains     `json:"failureDomains,omitempty"`
+	Conditions            clusterv1.Conditions         `json:"conditions,omitempty"`
 }
 
 // +kubebuilder:object:root=true
@@ -110,6 +110,11 @@ func (r *HetznerCluster) SetConditions(conditions clusterv1.Conditions) {
 	r.Status.Conditions = conditions
 }
 
+// ClusterTagKey generates the key for resources associated with a cluster.
+func (r *HetznerCluster) ClusterTagKey() string {
+	return NameHetznerProviderOwned + r.Name
+}
+
 func init() {
 	SchemeBuilder.Register(&HetznerCluster{}, &HetznerClusterList{})
 }
diff --git a/api/v1beta1/tags.go b/api/v1beta1/tags.go
index 61e1a364..d662d291 100644
--- a/api/v1beta1/tags.go
+++ b/api/v1beta1/tags.go
@@ -51,11 +51,6 @@ const (
 	MachineNameTagKey = "machine." + NameHetznerProviderPrefix + "name"
 )
 
-// ClusterTagKey generates the key for resources associated with a cluster.
-func ClusterTagKey(name string) string {
-	return fmt.Sprintf("%s%s", NameHetznerProviderOwned, name)
-}
-
 // ClusterHetznerCloudProviderTagKey generates the key for resources associated a cluster's HCloud cloud provider.
 func ClusterHetznerCloudProviderTagKey(name string) string {
 	return fmt.Sprintf("%s%s", NameKubernetesHetznerCloudProviderPrefix, name)
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index d71b07ae..90b004c1 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -1260,8 +1260,8 @@ func (in *HetznerClusterSpec) DeepCopyInto(out *HetznerClusterSpec) {
 		**out = **in
 	}
 	in.ControlPlaneLoadBalancer.DeepCopyInto(&out.ControlPlaneLoadBalancer)
-	if in.HCloudPlacementGroup != nil {
-		in, out := &in.HCloudPlacementGroup, &out.HCloudPlacementGroup
+	if in.HCloudPlacementGroups != nil {
+		in, out := &in.HCloudPlacementGroups, &out.HCloudPlacementGroups
 		*out = make([]HCloudPlacementGroupSpec, len(*in))
 		copy(*out, *in)
 	}
@@ -1291,8 +1291,8 @@ func (in *HetznerClusterStatus) DeepCopyInto(out *HetznerClusterStatus) {
 		*out = new(LoadBalancerStatus)
 		(*in).DeepCopyInto(*out)
 	}
-	if in.HCloudPlacementGroup != nil {
-		in, out := &in.HCloudPlacementGroup, &out.HCloudPlacementGroup
+	if in.HCloudPlacementGroups != nil {
+		in, out := &in.HCloudPlacementGroups, &out.HCloudPlacementGroups
 		*out = make([]HCloudPlacementGroupStatus, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
diff --git a/pkg/services/hcloud/loadbalancer/loadbalancer.go b/pkg/services/hcloud/loadbalancer/loadbalancer.go
index 571c9082..789d2e50 100644
--- a/pkg/services/hcloud/loadbalancer/loadbalancer.go
+++ b/pkg/services/hcloud/loadbalancer/loadbalancer.go
@@ -263,8 +263,6 @@ func createOptsFromSpec(hc *infrav1.HetznerCluster) hcloud.LoadBalancerCreateOpt
 
 	proxyprotocol := false
 
-	clusterTagKey := infrav1.ClusterTagKey(hc.Name)
-
 	var network *hcloud.Network
 	if hc.Status.Network != nil {
 		network = &hcloud.Network{ID: hc.Status.Network.ID}
@@ -278,7 +276,7 @@ func createOptsFromSpec(hc *infrav1.HetznerCluster) hcloud.LoadBalancerCreateOpt
 		Algorithm:        &hcloud.LoadBalancerAlgorithm{Type: algorithmType},
 		Location:         &hcloud.Location{Name: string(hc.Spec.ControlPlaneLoadBalancer.Region)},
 		Network:          network,
-		Labels:           map[string]string{clusterTagKey: string(infrav1.ResourceLifecycleOwned)},
+		Labels:           map[string]string{hc.ClusterTagKey(): string(infrav1.ResourceLifecycleOwned)},
 		PublicInterface:  &publicInterface,
 		Services: []hcloud.LoadBalancerCreateOptsService{
 			{
@@ -321,7 +319,7 @@ func (s *Service) Delete(ctx context.Context) (err error) {
 }
 
 func (s *Service) findLoadBalancer(ctx context.Context) (*hcloud.LoadBalancer, error) {
-	clusterTagKey := infrav1.ClusterTagKey(s.scope.HetznerCluster.Name)
+	clusterTagKey := s.scope.HetznerCluster.ClusterTagKey()
 	opts := hcloud.LoadBalancerListOpts{
 		ListOpts: hcloud.ListOpts{
 			LabelSelector: utils.LabelsToLabelSelector(map[string]string{
diff --git a/pkg/services/hcloud/network/network.go b/pkg/services/hcloud/network/network.go
index 9a73be1f..bff0b6ff 100644
--- a/pkg/services/hcloud/network/network.go
+++ b/pkg/services/hcloud/network/network.go
@@ -178,7 +178,7 @@ func apiToStatus(network *hcloud.Network) *infrav1.NetworkStatus {
 }
 
 func (s *Service) labels() map[string]string {
-	clusterTagKey := infrav1.ClusterTagKey(s.scope.HetznerCluster.Name)
+	clusterTagKey := s.scope.HetznerCluster.ClusterTagKey()
 	return map[string]string{
 		clusterTagKey: string(infrav1.ResourceLifecycleOwned),
 	}
diff --git a/pkg/services/hcloud/placementgroup/placementgroup.go b/pkg/services/hcloud/placementgroup/placementgroup.go
index d8d81784..04697633 100644
--- a/pkg/services/hcloud/placementgroup/placementgroup.go
+++ b/pkg/services/hcloud/placementgroup/placementgroup.go
@@ -19,6 +19,7 @@ package placementgroup
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"strings"
 
@@ -27,7 +28,6 @@ import (
 	"github.com/syself/cluster-api-provider-hetzner/pkg/scope"
 	hcloudutil "github.com/syself/cluster-api-provider-hetzner/pkg/services/hcloud/util"
 	"github.com/syself/cluster-api-provider-hetzner/pkg/utils"
-	kerrors "k8s.io/apimachinery/pkg/util/errors"
 	"sigs.k8s.io/cluster-api/util/record"
 )
 
@@ -48,85 +48,87 @@ func (s *Service) Reconcile(ctx context.Context) (err error) {
 	// find placement groups
 	placementGroups, err := s.findPlacementGroups(ctx)
 	if err != nil {
-		return fmt.Errorf("failed to find placement group: %w", err)
+		return fmt.Errorf("failed to find placement groups: %w", err)
 	}
 
-	s.scope.HetznerCluster.Status.HCloudPlacementGroup = apiToStatus(placementGroups, s.scope.HetznerCluster.Name)
-
-	placementGroupsSpec := s.scope.HetznerCluster.Spec.HCloudPlacementGroup
-	placementGroupsStatus := s.scope.HetznerCluster.Status.HCloudPlacementGroup
+	placementGroupsSpec := s.scope.HetznerCluster.Spec.HCloudPlacementGroups
+	placementGroupsStatus := statusFromHCloudPlacementGroups(placementGroups, s.scope.HetznerCluster.Name)
 
 	// Create arrays and maps to make diff
-	placementGroupNamesStatus := make([]string, len(placementGroupsStatus))
-	placementGroupNamesSpec := make([]string, len(placementGroupsSpec))
-	placementGroupStatusMap := make(map[string]infrav1.HCloudPlacementGroupStatus)
-	placementGroupSpecMap := make(map[string]infrav1.HCloudPlacementGroupSpec)
+	placementGroupNamesExisting := make([]string, len(placementGroupsStatus))
+	placementGroupNamesDesired := make([]string, len(placementGroupsSpec))
+	placementGroupExistingMap := make(map[string]infrav1.HCloudPlacementGroupStatus)
+	placementGroupDesiredMap := make(map[string]infrav1.HCloudPlacementGroupSpec)
 
 	for i, pgSpec := range placementGroupsSpec {
-		placementGroupNamesSpec[i] = pgSpec.Name
-		placementGroupSpecMap[pgSpec.Name] = placementGroupsSpec[i]
+		placementGroupNamesDesired[i] = pgSpec.Name
+		placementGroupDesiredMap[pgSpec.Name] = placementGroupsSpec[i]
 	}
 
 	for i, pgSts := range placementGroupsStatus {
-		placementGroupNamesStatus[i] = pgSts.Name
-		placementGroupStatusMap[pgSts.Name] = placementGroupsStatus[i]
+		placementGroupNamesExisting[i] = pgSts.Name
+		placementGroupExistingMap[pgSts.Name] = placementGroupsStatus[i]
 	}
 
-	// Make diff and create/delete placement groups
-	toCreate, toDelete := utils.DifferenceOfStringSlices(placementGroupNamesSpec, placementGroupNamesStatus)
+	// make diff of existing and desired placement groups
+	toCreate, toDelete := utils.DifferenceOfStringSlices(placementGroupNamesDesired, placementGroupNamesExisting)
+
+	var multierr error
 
-	var multierr []error
-	// Create
+	// create new placement groups
 	for _, pgName := range toCreate {
-		name := fmt.Sprintf("%s-%s", s.scope.HetznerCluster.Name, placementGroupSpecMap[pgName].Name)
-		clusterTagKey := infrav1.ClusterTagKey(s.scope.HetznerCluster.Name)
-		if _, err := s.scope.HCloudClient.CreatePlacementGroup(ctx, hcloud.PlacementGroupCreateOpts{
+		name := fmt.Sprintf("%s-%s", s.scope.HetznerCluster.Name, pgName)
+		clusterTagKey := s.scope.HetznerCluster.ClusterTagKey()
+		opts := hcloud.PlacementGroupCreateOpts{
 			Name:   name,
-			Type:   hcloud.PlacementGroupType(placementGroupSpecMap[pgName].Type),
+			Type:   hcloud.PlacementGroupType(placementGroupDesiredMap[pgName].Type),
 			Labels: map[string]string{clusterTagKey: string(infrav1.ResourceLifecycleOwned)},
-		}); err != nil {
+		}
+
+		if _, err := s.scope.HCloudClient.CreatePlacementGroup(ctx, opts); err != nil {
 			hcloudutil.HandleRateLimitExceeded(s.scope.HetznerCluster, err, "CreatePlacementGroup")
-			multierr = append(multierr, err)
+			multierr = errors.Join(multierr, fmt.Errorf("failed to create placement group %q: %w", pgName, err))
 		}
 	}
 
-	// Delete
+	// delete old placement groups
 	for _, pgName := range toDelete {
-		if err := s.scope.HCloudClient.DeletePlacementGroup(ctx, placementGroupStatusMap[pgName].ID); err != nil {
+		id := placementGroupExistingMap[pgName].ID
+		if err := s.scope.HCloudClient.DeletePlacementGroup(ctx, id); err != nil {
 			hcloudutil.HandleRateLimitExceeded(s.scope.HetznerCluster, err, "DeletePlacementGroup")
-			multierr = append(multierr, err)
+			multierr = errors.Join(multierr, fmt.Errorf("failed to delete placement group %v: %w", id, err))
 		}
 	}
 
-	if err := kerrors.NewAggregate(multierr); err != nil {
-		return fmt.Errorf("aggregate error - creating/deleting placement groups: %w", err)
+	if multierr != nil {
+		return fmt.Errorf("aggregate error - creating/deleting placement groups: %w", multierr)
 	}
 
 	// Update status
 	placementGroups, err = s.findPlacementGroups(ctx)
 	if err != nil {
-		return fmt.Errorf("failed to find placement group: %w", err)
+		return fmt.Errorf("failed to find placement groups: %w", err)
 	}
 
-	s.scope.HetznerCluster.Status.HCloudPlacementGroup = apiToStatus(placementGroups, s.scope.HetznerCluster.Name)
+	s.scope.HetznerCluster.Status.HCloudPlacementGroups = statusFromHCloudPlacementGroups(placementGroups, s.scope.HetznerCluster.Name)
 	return nil
 }
 
 // Delete implements deletion of placement groups.
 func (s *Service) Delete(ctx context.Context) (err error) {
 	// Delete placement groups which are not in status but in specs
-	var multierr []error
-	for _, pg := range s.scope.HetznerCluster.Status.HCloudPlacementGroup {
+	var multierr error
+	for _, pg := range s.scope.HetznerCluster.Status.HCloudPlacementGroups {
 		if err := s.scope.HCloudClient.DeletePlacementGroup(ctx, pg.ID); err != nil {
 			hcloudutil.HandleRateLimitExceeded(s.scope.HetznerCluster, err, "DeletePlacementGroup")
 			if !hcloud.IsError(err, hcloud.ErrorCodeNotFound) {
-				multierr = append(multierr, err)
+				multierr = errors.Join(multierr, err)
 			}
 		}
 	}
 
-	if err := kerrors.NewAggregate(multierr); err != nil {
-		return fmt.Errorf("failed to delete placement groups: %w", err)
+	if multierr != nil {
+		return fmt.Errorf("aggregate error - deleting placement groups: %w", err)
 	}
 
 	record.Eventf(s.scope.HetznerCluster, "PlacementGroupsDeleted", "Deleted placement groups")
@@ -135,7 +137,7 @@ func (s *Service) Delete(ctx context.Context) (err error) {
 }
 
 func (s *Service) findPlacementGroups(ctx context.Context) ([]*hcloud.PlacementGroup, error) {
-	clusterTagKey := infrav1.ClusterTagKey(s.scope.HetznerCluster.Name)
+	clusterTagKey := s.scope.HetznerCluster.ClusterTagKey()
 	labels := map[string]string{clusterTagKey: string(infrav1.ResourceLifecycleOwned)}
 	opts := hcloud.PlacementGroupListOpts{}
 	opts.LabelSelector = utils.LabelsToLabelSelector(labels)
@@ -148,14 +150,14 @@ func (s *Service) findPlacementGroups(ctx context.Context) ([]*hcloud.PlacementG
 	return placementGroups, nil
 }
 
-// gets the information of the Hetzner load balancer object and returns it in our status object.
-func apiToStatus(placementGroups []*hcloud.PlacementGroup, clusterName string) []infrav1.HCloudPlacementGroupStatus {
+// statusFromHCloudPlacementGroups gets the information of the Hetzner placement groups and returns it in our status object.
+func statusFromHCloudPlacementGroups(placementGroups []*hcloud.PlacementGroup, clusterName string) []infrav1.HCloudPlacementGroupStatus {
 	status := make([]infrav1.HCloudPlacementGroupStatus, len(placementGroups))
 	for i, pg := range placementGroups {
 		status[i] = infrav1.HCloudPlacementGroupStatus{
 			ID:     pg.ID,
 			Server: pg.Servers,
-			Name:   strings.TrimPrefix(pg.Name, fmt.Sprintf("%s-", clusterName)),
+			Name:   strings.TrimPrefix(pg.Name, clusterName+"-"),
 			Type:   string(pg.Type),
 		}
 	}
diff --git a/pkg/services/hcloud/server/server.go b/pkg/services/hcloud/server/server.go
index 96aab9bd..7c168f4d 100644
--- a/pkg/services/hcloud/server/server.go
+++ b/pkg/services/hcloud/server/server.go
@@ -293,7 +293,7 @@ func (s *Service) createServer(ctx context.Context) (*hcloud.Server, error) {
 	// set placement group if necessary
 	if s.scope.HCloudMachine.Spec.PlacementGroupName != nil {
 		var foundPlacementGroupInStatus bool
-		for _, pgSts := range s.scope.HetznerCluster.Status.HCloudPlacementGroup {
+		for _, pgSts := range s.scope.HetznerCluster.Status.HCloudPlacementGroups {
 			if *s.scope.HCloudMachine.Spec.PlacementGroupName == pgSts.Name {
 				foundPlacementGroupInStatus = true
 				opts.PlacementGroup = &hcloud.PlacementGroup{
@@ -607,9 +607,9 @@ func createLabels(hcloudClusterName, hcloudMachineName string, isControlPlane bo
 	}
 
 	return map[string]string{
-		infrav1.ClusterTagKey(hcloudClusterName): string(infrav1.ResourceLifecycleOwned),
-		infrav1.MachineNameTagKey:                hcloudMachineName,
-		"machine_type":                           machineType,
+		infrav1.NameHetznerProviderOwned + hcloudClusterName: string(infrav1.ResourceLifecycleOwned),
+		infrav1.MachineNameTagKey:                            hcloudMachineName,
+		"machine_type":                                       machineType,
 	}
 }
 


Test patch:
diff --git a/api/v1beta1/hetznercluster_types_test.go b/api/v1beta1/hetznercluster_types_test.go
new file mode 100644
index 00000000..67137c41
--- /dev/null
+++ b/api/v1beta1/hetznercluster_types_test.go
@@ -0,0 +1,31 @@
+/*
+Copyright 2023 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1beta1
+
+import (
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("Test ClusterTagKey", func() {
+	Context("Test ClusterTagKey", func() {
+		hetznerCluster := HetznerCluster{}
+		hetznerCluster.Name = "hetzner_cluster_name"
+
+		Expect(hetznerCluster.ClusterTagKey()).To(Equal("caph-cluster-hetzner_cluster_name"))
+	})
+})
diff --git a/controllers/controllers_suite_test.go b/controllers/controllers_suite_test.go
index 00e2b5a7..5328476a 100644
--- a/controllers/controllers_suite_test.go
+++ b/controllers/controllers_suite_test.go
@@ -158,7 +158,7 @@ func getDefaultHetznerClusterSpec() infrav1.HetznerClusterSpec {
 			NetworkZone:     "eu-central",
 			SubnetCIDRBlock: "10.0.0.0/24",
 		},
-		HCloudPlacementGroup: []infrav1.HCloudPlacementGroupSpec{
+		HCloudPlacementGroups: []infrav1.HCloudPlacementGroupSpec{
 			{
 				Name: defaultPlacementGroupName,
 				Type: "spread",
diff --git a/controllers/hcloudmachine_controller_test.go b/controllers/hcloudmachine_controller_test.go
index 5acca3a3..ef19bcc2 100644
--- a/controllers/hcloudmachine_controller_test.go
+++ b/controllers/hcloudmachine_controller_test.go
@@ -173,7 +173,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 			Eventually(func() bool {
 				servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 					ListOpts: hcloud.ListOpts{
-						LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(infraCluster.Name): "owned"}),
+						LabelSelector: utils.LabelsToLabelSelector(map[string]string{infraCluster.ClusterTagKey(): "owned"}),
 					},
 				})
 				if err != nil {
@@ -213,7 +213,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 			Eventually(func() int {
 				servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 					ListOpts: hcloud.ListOpts{
-						LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(infraCluster.Name): "owned"}),
+						LabelSelector: utils.LabelsToLabelSelector(map[string]string{infraCluster.ClusterTagKey(): "owned"}),
 					},
 				})
 				if err != nil {
@@ -302,7 +302,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				Eventually(func() int {
 					servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 						ListOpts: hcloud.ListOpts{
-							LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(infraCluster.Name): "owned"}),
+							LabelSelector: utils.LabelsToLabelSelector(map[string]string{infraCluster.ClusterTagKey(): "owned"}),
 						},
 					})
 					if err != nil {
@@ -315,7 +315,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 
 		Context("without placement groups", func() {
 			BeforeEach(func() {
-				infraCluster.Spec.HCloudPlacementGroup = nil
+				infraCluster.Spec.HCloudPlacementGroups = nil
 				Expect(testEnv.Create(ctx, infraCluster)).To(Succeed())
 				infraMachine.Spec.PlacementGroupName = nil
 				Expect(testEnv.Create(ctx, infraMachine)).To(Succeed())
@@ -325,7 +325,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				Eventually(func() int {
 					servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 						ListOpts: hcloud.ListOpts{
-							LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(infraCluster.Name): "owned"}),
+							LabelSelector: utils.LabelsToLabelSelector(map[string]string{infraCluster.ClusterTagKey(): "owned"}),
 						},
 					})
 					if err != nil {
@@ -338,7 +338,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 
 		Context("without placement groups, but with placement group in hcloudMachine spec", func() {
 			BeforeEach(func() {
-				infraCluster.Spec.HCloudPlacementGroup = nil
+				infraCluster.Spec.HCloudPlacementGroups = nil
 				Expect(testEnv.Create(ctx, infraCluster)).To(Succeed())
 				Expect(testEnv.Create(ctx, infraMachine)).To(Succeed())
 			})
@@ -367,7 +367,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				Eventually(func() int {
 					servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 						ListOpts: hcloud.ListOpts{
-							LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(infraCluster.Name): "owned"}),
+							LabelSelector: utils.LabelsToLabelSelector(map[string]string{infraCluster.ClusterTagKey(): "owned"}),
 						},
 					})
 					if err != nil {
diff --git a/controllers/hetznercluster_controller_test.go b/controllers/hetznercluster_controller_test.go
index 39add724..4cd0f525 100644
--- a/controllers/hetznercluster_controller_test.go
+++ b/controllers/hetznercluster_controller_test.go
@@ -194,7 +194,7 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 			Eventually(func() error {
 				loadBalancers, err := hcloudClient.ListLoadBalancers(ctx, hcloud.LoadBalancerListOpts{
 					ListOpts: hcloud.ListOpts{
-						LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(instance.Name): "owned"}),
+						LabelSelector: utils.LabelsToLabelSelector(map[string]string{instance.ClusterTagKey(): "owned"}),
 					},
 				})
 				if err != nil {
@@ -233,7 +233,7 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 			Eventually(func() int {
 				loadBalancers, err := hcloudClient.ListLoadBalancers(ctx, hcloud.LoadBalancerListOpts{
 					ListOpts: hcloud.ListOpts{
-						LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(instance.Name): "owned"}),
+						LabelSelector: utils.LabelsToLabelSelector(map[string]string{instance.ClusterTagKey(): "owned"}),
 					},
 				})
 				if err != nil {
@@ -267,7 +267,7 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 			Eventually(func() int {
 				loadBalancers, err := hcloudClient.ListLoadBalancers(ctx, hcloud.LoadBalancerListOpts{
 					ListOpts: hcloud.ListOpts{
-						LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(instance.Name): "owned"}),
+						LabelSelector: utils.LabelsToLabelSelector(map[string]string{instance.ClusterTagKey(): "owned"}),
 					},
 				})
 				if err != nil {
@@ -295,7 +295,7 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 			Eventually(func() int {
 				loadBalancers, err := hcloudClient.ListLoadBalancers(ctx, hcloud.LoadBalancerListOpts{
 					ListOpts: hcloud.ListOpts{
-						LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(instance.Name): "owned"}),
+						LabelSelector: utils.LabelsToLabelSelector(map[string]string{instance.ClusterTagKey(): "owned"}),
 					},
 				})
 				if err != nil {
@@ -389,7 +389,6 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 
 			It("should not create load balancer and cluster should be ready", func() {
 				key := client.ObjectKey{Namespace: instance.Namespace, Name: instance.Name}
-				fmt.Println("------------------------------------------------------------------------------------------", key)
 				Eventually(func() bool {
 					if err := testEnv.Get(ctx, key, instance); err != nil {
 						fmt.Println("Did not find instance")
@@ -496,7 +495,7 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 			Eventually(func() int {
 				servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 					ListOpts: hcloud.ListOpts{
-						LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(instance.Name): "owned"}),
+						LabelSelector: utils.LabelsToLabelSelector(map[string]string{instance.ClusterTagKey(): "owned"}),
 					},
 				})
 				if err != nil {
@@ -577,7 +576,7 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 		DescribeTable("create and delete placement groups without error",
 			func(placementGroups []infrav1.HCloudPlacementGroupSpec) {
 				// Create the HetznerCluster object
-				instance.Spec.HCloudPlacementGroup = placementGroups
+				instance.Spec.HCloudPlacementGroups = placementGroups
 				Expect(testEnv.Create(ctx, instance)).To(Succeed())
 				defer func() {
 					Expect(testEnv.Cleanup(ctx, instance)).To(Succeed())
@@ -597,7 +596,7 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 				Eventually(func() int {
 					pgs, err := hcloudClient.ListPlacementGroups(ctx, hcloud.PlacementGroupListOpts{
 						ListOpts: hcloud.ListOpts{
-							LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(instance.Name): "owned"}),
+							LabelSelector: utils.LabelsToLabelSelector(map[string]string{instance.ClusterTagKey(): "owned"}),
 						},
 					})
 					if err != nil {
@@ -631,13 +630,13 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 				func(newPlacementGroupSpec []infrav1.HCloudPlacementGroupSpec) {
 					ph, err := patch.NewHelper(instance, testEnv)
 					Expect(err).ShouldNot(HaveOccurred())
-					instance.Spec.HCloudPlacementGroup = newPlacementGroupSpec
+					instance.Spec.HCloudPlacementGroups = newPlacementGroupSpec
 					Expect(ph.Patch(ctx, instance, patch.WithStatusObservedGeneration{})).To(Succeed())
 
 					Eventually(func() int {
 						pgs, err := hcloudClient.ListPlacementGroups(ctx, hcloud.PlacementGroupListOpts{
 							ListOpts: hcloud.ListOpts{
-								LabelSelector: utils.LabelsToLabelSelector(map[string]string{infrav1.ClusterTagKey(instance.Name): "owned"}),
+								LabelSelector: utils.LabelsToLabelSelector(map[string]string{instance.ClusterTagKey(): "owned"}),
 							},
 						})
 						if err != nil {
@@ -959,12 +958,12 @@ var _ = Describe("HetznerCluster validation", func() {
 		})
 
 		It("should fail with an empty placementGroup name", func() {
-			hetznerCluster.Spec.HCloudPlacementGroup = append(hetznerCluster.Spec.HCloudPlacementGroup, infrav1.HCloudPlacementGroupSpec{})
+			hetznerCluster.Spec.HCloudPlacementGroups = append(hetznerCluster.Spec.HCloudPlacementGroups, infrav1.HCloudPlacementGroupSpec{})
 			Expect(testEnv.Create(ctx, hetznerCluster)).ToNot(Succeed())
 		})
 
 		It("should fail with a wrong placementGroup type", func() {
-			hetznerCluster.Spec.HCloudPlacementGroup = append(hetznerCluster.Spec.HCloudPlacementGroup, infrav1.HCloudPlacementGroupSpec{
+			hetznerCluster.Spec.HCloudPlacementGroups = append(hetznerCluster.Spec.HCloudPlacementGroups, infrav1.HCloudPlacementGroupSpec{
 				Name: "newName",
 				Type: "wrong-type",
 			})
diff --git a/pkg/services/hcloud/loadbalancer/loadbalancer_test.go b/pkg/services/hcloud/loadbalancer/loadbalancer_test.go
index cd598c2a..e07101e0 100644
--- a/pkg/services/hcloud/loadbalancer/loadbalancer_test.go
+++ b/pkg/services/hcloud/loadbalancer/loadbalancer_test.go
@@ -97,7 +97,6 @@ var _ = Describe("createOptsFromSpec", func() {
 		hetznerCluster.Name = "hetzner-cluster"
 
 		publicInterface := true
-		clusterTagKey := infrav1.ClusterTagKey(hetznerCluster.Name)
 		proxyprotocol := false
 
 		wantCreateOpts = hcloud.LoadBalancerCreateOpts{
@@ -106,7 +105,7 @@ var _ = Describe("createOptsFromSpec", func() {
 			Algorithm:        &hcloud.LoadBalancerAlgorithm{Type: hcloud.LoadBalancerAlgorithmTypeLeastConnections},
 			Location:         &hcloud.Location{Name: lbRegion},
 			Network:          &hcloud.Network{ID: networkID},
-			Labels:           map[string]string{clusterTagKey: string(infrav1.ResourceLifecycleOwned)},
+			Labels:           map[string]string{hetznerCluster.ClusterTagKey(): string(infrav1.ResourceLifecycleOwned)},
 			PublicInterface:  &publicInterface,
 			Services: []hcloud.LoadBalancerCreateOptsService{
 				{
diff --git a/pkg/services/hcloud/placementgroup/placementgroup_test.go b/pkg/services/hcloud/placementgroup/placementgroup_test.go
index b8b6ce92..e7834c7a 100644
--- a/pkg/services/hcloud/placementgroup/placementgroup_test.go
+++ b/pkg/services/hcloud/placementgroup/placementgroup_test.go
@@ -22,10 +22,10 @@ import (
 	infrav1 "github.com/syself/cluster-api-provider-hetzner/api/v1beta1"
 )
 
-var _ = Describe("apiToStatus", func() {
+var _ = Describe("statusFromHCloudPlacementGroups", func() {
 	var sts []infrav1.HCloudPlacementGroupStatus
 	BeforeEach(func() {
-		sts = apiToStatus(placementGroups, "cluster-name")
+		sts = statusFromHCloudPlacementGroups(placementGroups, "cluster-name")
 	})
 	It("should have three placement groups", func() {
 		Expect(len(sts)).To(Equal(3))
diff --git a/pkg/services/hcloud/server/server_test.go b/pkg/services/hcloud/server/server_test.go
index c1b2053a..67a6d835 100644
--- a/pkg/services/hcloud/server/server_test.go
+++ b/pkg/services/hcloud/server/server_test.go
@@ -60,14 +60,14 @@ var _ = DescribeTable("createLabels",
 		Expect(createLabels(hcloudClusterName, hcloudMachineName, isControlPlane)).To(Equal(expectedOutput))
 	},
 	Entry("is_controlplane", "hcloudCluster", "hcloudMachine", true, map[string]string{
-		infrav1.ClusterTagKey("hcloudCluster"): string(infrav1.ResourceLifecycleOwned),
-		infrav1.MachineNameTagKey:              "hcloudMachine",
-		"machine_type":                         "control_plane",
+		"caph-cluster-hcloudCluster": string(infrav1.ResourceLifecycleOwned),
+		infrav1.MachineNameTagKey:    "hcloudMachine",
+		"machine_type":               "control_plane",
 	}),
 	Entry("is_worker", "hcloudCluster", "hcloudMachine", false, map[string]string{
-		infrav1.ClusterTagKey("hcloudCluster"): string(infrav1.ResourceLifecycleOwned),
-		infrav1.MachineNameTagKey:              "hcloudMachine",
-		"machine_type":                         "worker",
+		"caph-cluster-hcloudCluster": string(infrav1.ResourceLifecycleOwned),
+		infrav1.MachineNameTagKey:    "hcloudMachine",
+		"machine_type":               "worker",
 	}),
 )
 
diff --git a/test/helpers/defaults.go b/test/helpers/defaults.go
index cdd3fa26..3481a854 100644
--- a/test/helpers/defaults.go
+++ b/test/helpers/defaults.go
@@ -37,9 +37,7 @@ const (
 	DefaultWWN2 = "eui.0025388801b4dff2"
 )
 
-var (
-	defaultPlacementGroupName = "caph-placement-group"
-)
+var defaultPlacementGroupName = "caph-placement-group"
 
 // BareMetalHost returns a bare metal host given options.
 func BareMetalHost(name, namespace string, opts ...HostOpts) *infrav1.HetznerBareMetalHost {
@@ -201,7 +199,7 @@ func GetDefaultHetznerClusterSpec() infrav1.HetznerClusterSpec {
 			NetworkZone:     "eu-central",
 			SubnetCIDRBlock: "10.0.0.0/24",
 		},
-		HCloudPlacementGroup: []infrav1.HCloudPlacementGroupSpec{
+		HCloudPlacementGroups: []infrav1.HCloudPlacementGroupSpec{
 			{
 				Name: defaultPlacementGroupName,
 				Type: "spread",