Repository: syself/cluster-api-provider-hetzner
Pull request #902 (2023-09-11 14:49:18)

:seedling: Refactor conditions of provider machine objects
**What this PR does / why we need it**:
Refactoring conditions of HetznerBareMetalMachine and HCloudMachine to make them more readable and more expressive.

Actively deleting deprecated conditions from objects

**TODOs**:
- [x] squash commits
- [ ] include documentation
- [ ] add unit tests




Patch:
diff --git a/api/v1beta1/conditions_const.go b/api/v1beta1/conditions_const.go
index 8bc357d7..edc7ac3c 100644
--- a/api/v1beta1/conditions_const.go
+++ b/api/v1beta1/conditions_const.go
@@ -32,32 +32,39 @@ const (
 const (
 	// LoadBalancerAttachedToNetworkCondition reports on whether the load balancer is attached to a network.
 	LoadBalancerAttachedToNetworkCondition clusterv1.ConditionType = "LoadBalancerAttachedToNetwork"
-	// LoadBalancerAttachFailedReason is used when load balancer could not be attached to network.
-	LoadBalancerAttachFailedReason = "LoadBalancerAttachFailed"
 	// LoadBalancerNoNetworkFoundReason is used when no network could be found.
 	LoadBalancerNoNetworkFoundReason = "LoadBalancerNoNetworkFound"
 )
 
 const (
-	// InstanceReadyCondition reports on current status of the instance. Ready indicates the instance is in a Running state.
-	InstanceReadyCondition clusterv1.ConditionType = "InstanceReady"
-	// InstanceTerminatedReason instance is in a terminated state.
-	InstanceTerminatedReason = "InstanceTerminated"
+	// ServerCreateSucceededCondition reports on current status of the instance. Ready indicates the instance is in a Running state.
+	ServerCreateSucceededCondition clusterv1.ConditionType = "ServerCreateSucceeded"
 	// InstanceHasNonExistingPlacementGroupReason instance has a placement group name that does not exist.
 	InstanceHasNonExistingPlacementGroupReason = "InstanceHasNonExistingPlacementGroup"
-	// ServerOffReason instance is off.
-	ServerOffReason = "ServerOff"
-	// InstanceAsControlPlaneUnreachableReason control plane is (not yet) reachable.
-	InstanceAsControlPlaneUnreachableReason = "InstanceAsControlPlaneUnreachable"
 	// SSHKeyNotFoundReason indicates that ssh key could not be found.
 	SSHKeyNotFoundReason = "SSHKeyNotFound"
 )
 
 const (
-	// InstanceBootstrapReadyCondition reports on current status of the instance. BootstrapReady indicates the bootstrap is ready.
-	InstanceBootstrapReadyCondition clusterv1.ConditionType = "InstanceBootstrapReady"
-	// InstanceBootstrapNotReadyReason bootstrap not ready yet.
-	InstanceBootstrapNotReadyReason = "InstanceBootstrapNotReady"
+	// ServerAvailableCondition indicates the instance is in a Running state.
+	ServerAvailableCondition clusterv1.ConditionType = "ServerAvailable"
+	// ServerTerminatingReason instance is in a terminated state.
+	ServerTerminatingReason = "InstanceTerminated"
+	// ServerStartingReason instance is in a terminated state.
+	ServerStartingReason = "ServerStarting"
+	// ServerOffReason instance is off.
+	ServerOffReason = "ServerOff"
+	// NetworkAttachFailedReason is used when server could not be attached to network.
+	NetworkAttachFailedReason = "NetworkAttachFailed"
+	// LoadBalancerAttachFailedReason is used when server could not be attached to network.
+	LoadBalancerAttachFailedReason = "LoadBalancerAttachFailed"
+)
+
+const (
+	// BootstrapReadyCondition  indicates that bootstrap is ready.
+	BootstrapReadyCondition clusterv1.ConditionType = "BootstrapReady"
+	// BootstrapNotReadyReason bootstrap not ready yet.
+	BootstrapNotReadyReason = "BootstrapNotReady"
 )
 
 const (
@@ -114,6 +121,31 @@ const (
 	RescueSSHSecretMissingReason = "RescueSSHSecretMissing"
 )
 
+const (
+	// HostAssociateSucceededCondition indicates that a host has been associated.
+	HostAssociateSucceededCondition clusterv1.ConditionType = "HostAssociateSucceeded"
+	// NoAvailableHostReason indicates that there is no available host.
+	NoAvailableHostReason = "NoAvailableHost"
+	// HostAssociateFailedReason indicates that asssociating a host failed.
+	HostAssociateFailedReason = "HostAssociateFailed"
+)
+
+const (
+	// HostProvisionSucceededCondition indicates that a host has been provisioned.
+	HostProvisionSucceededCondition clusterv1.ConditionType = "HostProvisionSucceeded"
+	// StillProvisioningReason indicates that asssociating a host is still provisioning.
+	StillProvisioningReason = "StillProvisioning"
+)
+
+// deprecated .
+
+const (
+	// InstanceReadyCondition reports on current status of the instance. Ready indicates the instance is in a Running state.
+	InstanceReadyCondition clusterv1.ConditionType = "InstanceReady"
+	// InstanceBootstrapReadyCondition reports on current status of the instance. BootstrapReady indicates the bootstrap is ready.
+	InstanceBootstrapReadyCondition clusterv1.ConditionType = "InstanceBootstrapReady"
+)
+
 const (
 	// AssociateBMHCondition reports on whether the Hetzner cluster is in ready state.
 	AssociateBMHCondition clusterv1.ConditionType = "AssociateBMHCondition"
diff --git a/controllers/hetznerbaremetalmachine_controller.go b/controllers/hetznerbaremetalmachine_controller.go
index 5af200c5..b45b794d 100644
--- a/controllers/hetznerbaremetalmachine_controller.go
+++ b/controllers/hetznerbaremetalmachine_controller.go
@@ -141,6 +141,8 @@ func (r *HetznerBareMetalMachineReconciler) Reconcile(ctx context.Context, req r
 			conditions.MarkTrue(hbmMachine, infrav1.HCloudTokenAvailableCondition)
 		}
 
+		conditions.SetSummary(hbmMachine)
+
 		if err := machineScope.Close(ctx); err != nil && reterr == nil {
 			reterr = err
 		}
diff --git a/pkg/scope/baremetalmachine.go b/pkg/scope/baremetalmachine.go
index 5be321b2..8b8ebc35 100644
--- a/pkg/scope/baremetalmachine.go
+++ b/pkg/scope/baremetalmachine.go
@@ -25,7 +25,6 @@ import (
 	"sigs.k8s.io/cluster-api/util"
 	"sigs.k8s.io/cluster-api/util/conditions"
 	"sigs.k8s.io/cluster-api/util/patch"
-
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	infrav1 "github.com/syself/cluster-api-provider-hetzner/api/v1beta1"
diff --git a/pkg/scope/cluster.go b/pkg/scope/cluster.go
index 1762999f..66748ec3 100644
--- a/pkg/scope/cluster.go
+++ b/pkg/scope/cluster.go
@@ -28,7 +28,6 @@ import (
 	"k8s.io/client-go/tools/clientcmd"
 	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
 	"sigs.k8s.io/cluster-api/util/conditions"
-
 	"sigs.k8s.io/cluster-api/util/patch"
 	"sigs.k8s.io/cluster-api/util/secret"
 	"sigs.k8s.io/controller-runtime/pkg/client"
diff --git a/pkg/scope/machine.go b/pkg/scope/machine.go
index 0481d178..5a6fdf64 100644
--- a/pkg/scope/machine.go
+++ b/pkg/scope/machine.go
@@ -160,20 +160,20 @@ func (m *MachineScope) SetReady(ready bool) {
 	m.HCloudMachine.Status.Ready = ready
 }
 
-// HasInstanceReadyCondition checks whether InstanceReady condition is set on true.
-func (m *MachineScope) HasInstanceReadyCondition() bool {
-	return conditions.IsTrue(m.HCloudMachine, infrav1.InstanceReadyCondition)
+// HasServerAvailableCondition checks whether ServerAvailable condition is set on true.
+func (m *MachineScope) HasServerAvailableCondition() bool {
+	return conditions.IsTrue(m.HCloudMachine, infrav1.ServerAvailableCondition)
 }
 
-// HasInstanceTerminatedCondition checks the whether InstanceReady condition is false with reason "terminated".
-func (m *MachineScope) HasInstanceTerminatedCondition() bool {
-	return conditions.IsFalse(m.HCloudMachine, infrav1.InstanceReadyCondition) &&
-		conditions.GetReason(m.HCloudMachine, infrav1.InstanceReadyCondition) == infrav1.InstanceTerminatedReason
+// HasServerTerminatedCondition checks the whether ServerAvailable condition is false with reason "terminated".
+func (m *MachineScope) HasServerTerminatedCondition() bool {
+	return conditions.IsFalse(m.HCloudMachine, infrav1.ServerAvailableCondition) &&
+		conditions.GetReason(m.HCloudMachine, infrav1.ServerAvailableCondition) == infrav1.ServerTerminatingReason
 }
 
 // HasShutdownTimedOut checks the whether the HCloud server is terminated.
 func (m *MachineScope) HasShutdownTimedOut() bool {
-	return time.Now().After(conditions.GetLastTransitionTime(m.HCloudMachine, infrav1.InstanceReadyCondition).Time.Add(maxShutDownTime))
+	return time.Now().After(conditions.GetLastTransitionTime(m.HCloudMachine, infrav1.ServerAvailableCondition).Time.Add(maxShutDownTime))
 }
 
 // IsBootstrapDataReady checks the readiness of a capi machine's bootstrap data.
diff --git a/pkg/services/baremetal/baremetal/baremetal.go b/pkg/services/baremetal/baremetal/baremetal.go
index 7978eaf7..7d680132 100644
--- a/pkg/services/baremetal/baremetal/baremetal.go
+++ b/pkg/services/baremetal/baremetal/baremetal.go
@@ -77,23 +77,25 @@ func NewService(scope *scope.BareMetalMachineScope) *Service {
 
 // Reconcile implements reconcilement of HetznerBareMetalMachines.
 func (s *Service) Reconcile(ctx context.Context) (res reconcile.Result, err error) {
+	// delete the deprecated condition from existing machine objects
+	conditions.Delete(s.scope.BareMetalMachine, infrav1.InstanceReadyCondition)
+	conditions.Delete(s.scope.BareMetalMachine, infrav1.InstanceBootstrapReadyCondition)
+	conditions.Delete(s.scope.BareMetalMachine, infrav1.AssociateBMHCondition)
+
 	// Make sure bootstrap data is available and populated. If not, return, we
 	// will get an event from the machine update when the flag is set to true.
 	if !s.scope.IsBootstrapReady() {
 		conditions.MarkFalse(
 			s.scope.BareMetalMachine,
-			infrav1.InstanceBootstrapReadyCondition,
-			infrav1.InstanceBootstrapNotReadyReason,
+			infrav1.BootstrapReadyCondition,
+			infrav1.BootstrapNotReadyReason,
 			clusterv1.ConditionSeverityInfo,
 			"bootstrap not ready yet",
 		)
 		return res, nil
 	}
 
-	conditions.MarkTrue(
-		s.scope.BareMetalMachine,
-		infrav1.InstanceBootstrapReadyCondition,
-	)
+	conditions.MarkTrue(s.scope.BareMetalMachine, infrav1.BootstrapReadyCondition)
 
 	// Check if the bareMetalmachine is associated with a host already. If not, associate a new host.
 	if !s.scope.BareMetalMachine.HasHostAnnotation() {
@@ -103,6 +105,8 @@ func (s *Service) Reconcile(ctx context.Context) (res reconcile.Result, err erro
 		}
 	}
 
+	conditions.MarkTrue(s.scope.BareMetalMachine, infrav1.HostAssociateSucceededCondition)
+
 	// update the machine
 	if err := s.update(ctx); err != nil {
 		return checkForRequeueError(err, "failed to update machine")
@@ -115,7 +119,6 @@ func (s *Service) Reconcile(ctx context.Context) (res reconcile.Result, err erro
 
 	// set machine ready
 	s.scope.BareMetalMachine.Status.Ready = true
-	conditions.MarkTrue(s.scope.BareMetalMachine, infrav1.InstanceReadyCondition)
 
 	return res, nil
 }
@@ -240,7 +243,7 @@ func (s *Service) update(ctx context.Context) error {
 	// if machine is a control plane, the host should be set as target of load balancer
 	if s.scope.IsControlPlane() {
 		if err := s.reconcileLoadBalancerAttachment(ctx, host); err != nil {
-			return fmt.Errorf("failed to reconcile load balancer attachement: %w", err)
+			return fmt.Errorf("failed to reconcile load balancer attachment: %w", err)
 		}
 	}
 
@@ -271,6 +274,13 @@ func (s *Service) associate(ctx context.Context) error {
 	}
 	if host == nil {
 		s.scope.V(1).Info("No available host found. Requeuing.")
+		conditions.MarkFalse(
+			s.scope.BareMetalMachine,
+			infrav1.HostAssociateSucceededCondition,
+			infrav1.NoAvailableHostReason,
+			clusterv1.ConditionSeverityWarning,
+			"no available host",
+		)
 		return &scope.RequeueAfterError{RequeueAfter: requeueAfter}
 	}
 
@@ -284,7 +294,15 @@ func (s *Service) associate(ctx context.Context) error {
 	s.setHostSpec(host)
 
 	if err := analyzePatchError(helper.Patch(ctx, host), false); err != nil {
-		return fmt.Errorf("failed to patch host: %w", err)
+		reterr := fmt.Errorf("failed to patch host: %w", err)
+		conditions.MarkFalse(
+			s.scope.BareMetalMachine,
+			infrav1.HostAssociateSucceededCondition,
+			infrav1.HostAssociateFailedReason,
+			clusterv1.ConditionSeverityWarning,
+			reterr.Error(),
+		)
+		return reterr
 	}
 
 	s.ensureMachineAnnotation(host)
@@ -534,6 +552,13 @@ func (s *Service) setProviderID(ctx context.Context) error {
 	}
 
 	if host.Spec.Status.ProvisioningState != infrav1.StateProvisioned {
+		conditions.MarkFalse(
+			s.scope.BareMetalMachine,
+			infrav1.HostProvisionSucceededCondition,
+			infrav1.StillProvisioningReason,
+			clusterv1.ConditionSeverityInfo,
+			"still provisioning - current state %q", host.Spec.Status.ProvisioningState,
+		)
 		// no need for requeue error since host update will trigger a reconciliation
 		return nil
 	}
@@ -541,6 +566,8 @@ func (s *Service) setProviderID(ctx context.Context) error {
 	// set providerID
 	providerID := providerIDFromServerID(host.Spec.ServerID)
 	s.scope.BareMetalMachine.Spec.ProviderID = &providerID
+	conditions.MarkTrue(s.scope.BareMetalMachine, infrav1.HostProvisionSucceededCondition)
+
 	return nil
 }
 
@@ -583,7 +610,6 @@ func (s *Service) updateMachineAddresses(host *infrav1.HetznerBareMetalHost) {
 	bareMetalMachineOld := s.scope.BareMetalMachine.DeepCopy()
 
 	s.scope.BareMetalMachine.Status.Addresses = addrs
-	conditions.MarkTrue(s.scope.BareMetalMachine, infrav1.AssociateBMHCondition)
 
 	// Update lastUpdated when status changed
 	if !equality.Semantic.DeepEqual(s.scope.BareMetalMachine.Status, bareMetalMachineOld.Status) {
diff --git a/pkg/services/hcloud/loadbalancer/loadbalancer.go b/pkg/services/hcloud/loadbalancer/loadbalancer.go
index 749f24ba..c1c7d66c 100644
--- a/pkg/services/hcloud/loadbalancer/loadbalancer.go
+++ b/pkg/services/hcloud/loadbalancer/loadbalancer.go
@@ -90,7 +90,7 @@ func (s *Service) Reconcile(ctx context.Context) (reconcile.Result, error) {
 	}
 
 	if err := s.reconcileNetworkAttachement(ctx, lb); err != nil {
-		return reconcile.Result{}, fmt.Errorf("failed to reconcile network attachement: %w", err)
+		return reconcile.Result{}, fmt.Errorf("failed to reconcile network attachment: %w", err)
 	}
 
 	if err := s.reconcileServices(ctx, lb); err != nil {
diff --git a/pkg/services/hcloud/server/server.go b/pkg/services/hcloud/server/server.go
index 5db6f45a..4ffb77dd 100644
--- a/pkg/services/hcloud/server/server.go
+++ b/pkg/services/hcloud/server/server.go
@@ -60,6 +60,10 @@ func NewService(scope *scope.MachineScope) *Service {
 
 // Reconcile implements reconcilement of HCloudMachines.
 func (s *Service) Reconcile(ctx context.Context) (res reconcile.Result, err error) {
+	// delete the deprecated condition from existing machine objects
+	conditions.Delete(s.scope.HCloudMachine, infrav1.InstanceReadyCondition)
+	conditions.Delete(s.scope.HCloudMachine, infrav1.InstanceBootstrapReadyCondition)
+
 	// detect failure domain
 	failureDomain, err := s.scope.GetFailureDomain()
 	if err != nil {
@@ -73,15 +77,15 @@ func (s *Service) Reconcile(ctx context.Context) (res reconcile.Result, err erro
 	if !s.scope.IsBootstrapDataReady() {
 		conditions.MarkFalse(
 			s.scope.HCloudMachine,
-			infrav1.InstanceBootstrapReadyCondition,
-			infrav1.InstanceBootstrapNotReadyReason,
+			infrav1.BootstrapReadyCondition,
+			infrav1.BootstrapNotReadyReason,
 			clusterv1.ConditionSeverityInfo,
 			"bootstrap not ready yet",
 		)
 		return reconcile.Result{RequeueAfter: 10 * time.Second}, nil
 	}
 
-	conditions.MarkTrue(s.scope.HCloudMachine, infrav1.InstanceBootstrapReadyCondition)
+	conditions.MarkTrue(s.scope.HCloudMachine, infrav1.BootstrapReadyCondition)
 
 	// try to find an existing server
 	server, err := s.findServer(ctx)
@@ -118,9 +122,16 @@ func (s *Service) Reconcile(ctx context.Context) (res reconcile.Result, err erro
 		return s.handleServerStatusOff(ctx, server)
 	case hcloud.ServerStatusStarting:
 		// Requeue here so that server does not switch back and forth between off and starting.
-		// If we don't return here, the condition InstanceReady would get marked as true in this
+		// If we don't return here, the condition ServerAvailable would get marked as true in this
 		// case. However, if the server is stuck and does not power on, we should not mark the
-		// condition InstanceReady as true to be able to remediate the server after a timeout.
+		// condition ServerAvailable as true to be able to remediate the server after a timeout.
+		conditions.MarkFalse(
+			s.scope.HCloudMachine,
+			infrav1.ServerAvailableCondition,
+			infrav1.ServerStartingReason,
+			clusterv1.ConditionSeverityInfo,
+			"server is starting",
+		)
 		return reconcile.Result{RequeueAfter: 10 * time.Second}, nil
 	case hcloud.ServerStatusRunning: // do nothing
 	default:
@@ -131,23 +142,39 @@ func (s *Service) Reconcile(ctx context.Context) (res reconcile.Result, err erro
 
 	// check whether server is attached to the network
 	if err := s.reconcileNetworkAttachment(ctx, server); err != nil {
-		return res, fmt.Errorf("failed to reconcile network attachement: %w", err)
+		reterr := fmt.Errorf("failed to reconcile network attachment: %w", err)
+		conditions.MarkFalse(
+			s.scope.HCloudMachine,
+			infrav1.ServerAvailableCondition,
+			infrav1.NetworkAttachFailedReason,
+			clusterv1.ConditionSeverityError,
+			reterr.Error(),
+		)
+		return res, reterr
 	}
 
 	// nothing to do any more for worker nodes
 	if !s.scope.IsControlPlane() {
+		conditions.MarkTrue(s.scope.HCloudMachine, infrav1.ServerAvailableCondition)
 		s.scope.SetReady(true)
-		conditions.MarkTrue(s.scope.HCloudMachine, infrav1.InstanceReadyCondition)
 		return res, nil
 	}
 
 	// all control planes have to be attached to the load balancer if it exists
 	if err := s.reconcileLoadBalancerAttachment(ctx, server); err != nil {
-		return res, fmt.Errorf("failed to reconcile load balancer attachement: %w", err)
+		reterr := fmt.Errorf("failed to reconcile load balancer attachment: %w", err)
+		conditions.MarkFalse(
+			s.scope.HCloudMachine,
+			infrav1.ServerAvailableCondition,
+			infrav1.LoadBalancerAttachFailedReason,
+			clusterv1.ConditionSeverityError,
+			reterr.Error(),
+		)
+		return res, reterr
 	}
 
 	s.scope.SetReady(true)
-	conditions.MarkTrue(s.scope.HCloudMachine, infrav1.InstanceReadyCondition)
+	conditions.MarkTrue(s.scope.HCloudMachine, infrav1.ServerAvailableCondition)
 
 	return res, nil
 }
@@ -318,7 +345,7 @@ func (s *Service) createServer(ctx context.Context) (*hcloud.Server, error) {
 		}
 		if !foundPlacementGroupInStatus {
 			conditions.MarkFalse(s.scope.HCloudMachine,
-				infrav1.InstanceReadyCondition,
+				infrav1.ServerCreateSucceededCondition,
 				infrav1.InstanceHasNonExistingPlacementGroupReason,
 				clusterv1.ConditionSeverityError,
 				"Placement group %q does not exist in cluster",
@@ -345,7 +372,7 @@ func (s *Service) createServer(ctx context.Context) (*hcloud.Server, error) {
 	// find matching keys and store them
 	opts.SSHKeys, err = filterHCloudSSHKeys(sshKeysAPI, sshKeySpecs)
 	if err != nil {
-		conditions.MarkFalse(s.scope.HCloudMachine, infrav1.InstanceReadyCondition, infrav1.SSHKeyNotFoundReason, clusterv1.ConditionSeverityError, err.Error())
+		conditions.MarkFalse(s.scope.HCloudMachine, infrav1.ServerCreateSucceededCondition, infrav1.SSHKeyNotFoundReason, clusterv1.ConditionSeverityError, err.Error())
 		return nil, fmt.Errorf("error with ssh keys: %w", err)
 	}
 
@@ -374,6 +401,7 @@ func (s *Service) createServer(ctx context.Context) (*hcloud.Server, error) {
 		return nil, fmt.Errorf("failed to create HCloud server %s: %w", s.scope.HCloudMachine.Name, err)
 	}
 
+	conditions.MarkTrue(s.scope.HCloudMachine, infrav1.ServerCreateSucceededCondition)
 	record.Eventf(s.scope.HCloudMachine, "SuccessfulCreate", "Created new server with id %d", server.ID)
 	return server, nil
 }
@@ -445,11 +473,11 @@ func (s *Service) handleServerStatusOff(ctx context.Context, server *hcloud.Serv
 	// Check if server is in ServerStatusOff and turn it on. This is to avoid a bug of Hetzner where
 	// sometimes machines are created and not turned on
 
-	instanceReadyCondition := conditions.Get(s.scope.HCloudMachine, infrav1.InstanceReadyCondition)
-	if instanceReadyCondition != nil &&
-		instanceReadyCondition.Status == corev1.ConditionFalse &&
-		instanceReadyCondition.Reason == infrav1.ServerOffReason {
-		if time.Now().Before(instanceReadyCondition.LastTransitionTime.Time.Add(serverOffTimeout)) {
+	serverAvailableCondition := conditions.Get(s.scope.HCloudMachine, infrav1.ServerAvailableCondition)
+	if serverAvailableCondition != nil &&
+		serverAvailableCondition.Status == corev1.ConditionFalse &&
+		serverAvailableCondition.Reason == infrav1.ServerOffReason {
+		if time.Now().Before(serverAvailableCondition.LastTransitionTime.Time.Add(serverOffTimeout)) {
 			// Not yet timed out, try again to power on
 			if err := s.scope.HCloudClient.PowerOnServer(ctx, server); err != nil {
 				hcloudutil.HandleRateLimitExceeded(s.scope.HCloudMachine, err, "PowerOnServer")
@@ -476,7 +504,7 @@ func (s *Service) handleServerStatusOff(ctx context.Context, server *hcloud.Serv
 		}
 		conditions.MarkFalse(
 			s.scope.HCloudMachine,
-			infrav1.InstanceReadyCondition,
+			infrav1.ServerAvailableCondition,
 			infrav1.ServerOffReason,
 			clusterv1.ConditionSeverityInfo,
 			"server is switched off",
@@ -492,15 +520,15 @@ func (s *Service) handleDeleteServerStatusRunning(ctx context.Context, server *h
 	// 1. The server has not yet been tried to shut down and still is marked as "ready".
 	// 2. The server has been tried to shut down without an effect and the timeout is not reached yet.
 
-	if s.scope.HasInstanceReadyCondition() || (s.scope.HasInstanceTerminatedCondition() && !s.scope.HasShutdownTimedOut()) {
+	if s.scope.HasServerAvailableCondition() || (s.scope.HasServerTerminatedCondition() && !s.scope.HasShutdownTimedOut()) {
 		if err := s.scope.HCloudClient.ShutdownServer(ctx, server); err != nil {
 			hcloudutil.HandleRateLimitExceeded(s.scope.HCloudMachine, err, "ShutdownServer")
 			return res, fmt.Errorf("failed to shutdown server: %w", err)
 		}
 
 		conditions.MarkFalse(s.scope.HCloudMachine,
-			infrav1.InstanceReadyCondition,
-			infrav1.InstanceTerminatedReason,
+			infrav1.ServerAvailableCondition,
+			infrav1.ServerTerminatingReason,
 			clusterv1.ConditionSeverityInfo,
 			"Instance has been shut down",
 		)


Test patch:
diff --git a/controllers/controllers_suite_test.go b/controllers/controllers_suite_test.go
index 468f2039..96e581d5 100644
--- a/controllers/controllers_suite_test.go
+++ b/controllers/controllers_suite_test.go
@@ -40,7 +40,7 @@ import (
 
 const (
 	defaultPodNamespace = "caph-system"
-	timeout             = time.Second * 25
+	timeout             = time.Second * 3
 )
 
 func TestControllers(t *testing.T) {
diff --git a/controllers/hcloudmachine_controller_test.go b/controllers/hcloudmachine_controller_test.go
index 0c68034d..df1ca10a 100644
--- a/controllers/hcloudmachine_controller_test.go
+++ b/controllers/hcloudmachine_controller_test.go
@@ -17,7 +17,6 @@ limitations under the License.
 package controllers
 
 import (
-	"fmt"
 	"time"
 
 	"github.com/hetznercloud/hcloud-go/hcloud"
@@ -188,7 +187,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				if err := testEnv.Get(ctx, key, infraMachine); err != nil {
 					return false
 				}
-				return isPresentAndFalseWithReason(key, infraMachine, infrav1.InstanceBootstrapReadyCondition, infrav1.InstanceBootstrapNotReadyReason)
+				return isPresentAndFalseWithReason(key, infraMachine, infrav1.BootstrapReadyCondition, infrav1.BootstrapNotReadyReason)
 			}, timeout, time.Second).Should(BeTrue())
 
 			By("setting the bootstrap data")
@@ -201,16 +200,17 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				return ph.Patch(ctx, capiMachine, patch.WithStatusObservedGeneration{})
 			}, timeout, time.Second).Should(BeNil())
 
-			// Check whether bootstrap condition is ready
+			By("checking whether bootstrap condition is ready")
+
 			Eventually(func() bool {
 				if err := testEnv.Get(ctx, key, infraMachine); err != nil {
 					return false
 				}
-				objectCondition := conditions.Get(infraMachine, infrav1.InstanceBootstrapReadyCondition)
-				fmt.Println(objectCondition)
-				return isPresentAndTrue(key, infraMachine, infrav1.InstanceBootstrapReadyCondition)
+				return isPresentAndTrue(key, infraMachine, infrav1.BootstrapReadyCondition)
 			}, timeout, time.Second).Should(BeTrue())
 
+			By("listing hcloud servers")
+
 			Eventually(func() int {
 				servers, err := hcloudClient.ListServers(ctx, hcloud.ServerListOpts{
 					ListOpts: hcloud.ListOpts{
@@ -222,6 +222,24 @@ var _ = Describe("HCloudMachineReconciler", func() {
 				}
 				return len(servers)
 			}, timeout, time.Second).Should(BeNumerically(">", 0))
+
+			By("checking if server created condition is set")
+
+			Eventually(func() bool {
+				if err := testEnv.Get(ctx, key, infraMachine); err != nil {
+					return false
+				}
+				return isPresentAndTrue(key, infraMachine, infrav1.ServerCreateSucceededCondition)
+			}, timeout, time.Second).Should(BeTrue())
+
+			By("checking if server available condition is set")
+
+			Eventually(func() bool {
+				if err := testEnv.Get(ctx, key, infraMachine); err != nil {
+					return false
+				}
+				return isPresentAndTrue(key, infraMachine, infrav1.ServerAvailableCondition)
+			}, timeout, time.Second).Should(BeTrue())
 		})
 	})
 
@@ -349,7 +367,7 @@ var _ = Describe("HCloudMachineReconciler", func() {
 					if err := testEnv.Get(ctx, key, infraMachine); err != nil {
 						return false
 					}
-					return isPresentAndFalseWithReason(key, infraMachine, infrav1.InstanceReadyCondition, infrav1.InstanceHasNonExistingPlacementGroupReason)
+					return isPresentAndFalseWithReason(key, infraMachine, infrav1.ServerCreateSucceededCondition, infrav1.InstanceHasNonExistingPlacementGroupReason)
 				}, timeout).Should(BeTrue())
 			})
 		})
diff --git a/controllers/hetznerbaremetalmachine_controller_test.go b/controllers/hetznerbaremetalmachine_controller_test.go
index f300384b..62e7c72c 100644
--- a/controllers/hetznerbaremetalmachine_controller_test.go
+++ b/controllers/hetznerbaremetalmachine_controller_test.go
@@ -42,7 +42,6 @@ import (
 
 var _ = Describe("HetznerBareMetalMachineReconciler", func() {
 	var (
-		host           *infrav1.HetznerBareMetalHost
 		bmMachine      *infrav1.HetznerBareMetalMachine
 		hetznerCluster *infrav1.HetznerCluster
 		capiCluster    *clusterv1.Cluster
@@ -57,8 +56,7 @@ var _ = Describe("HetznerBareMetalMachineReconciler", func() {
 		osSSHSecret     *corev1.Secret
 		bootstrapSecret *corev1.Secret
 
-		key     client.ObjectKey
-		hostKey client.ObjectKey
+		key client.ObjectKey
 
 		robotClient     *robotmock.Client
 		rescueSSHClient *sshmock.Client
@@ -114,15 +112,6 @@ var _ = Describe("HetznerBareMetalMachineReconciler", func() {
 		}
 		Expect(testEnv.Create(ctx, hetznerCluster)).To(Succeed())
 
-		host = helpers.BareMetalHost(
-			hostName,
-			testNs.Name,
-			helpers.WithRootDeviceHintWWN(),
-			helpers.WithHetznerClusterRef(hetznerClusterName),
-		)
-		Expect(testEnv.Create(ctx, host)).To(Succeed())
-		hostKey = client.ObjectKey{Namespace: testNs.Name, Name: hostName}
-
 		hetznerSecret = getDefaultHetznerSecret(testNs.Name)
 		Expect(testEnv.Create(ctx, hetznerSecret)).To(Succeed())
 
@@ -176,105 +165,300 @@ var _ = Describe("HetznerBareMetalMachineReconciler", func() {
 	})
 
 	AfterEach(func() {
-		Expect(testEnv.Cleanup(ctx, testNs, capiCluster, hetznerCluster, host,
+		Expect(testEnv.Cleanup(ctx, testNs, capiCluster, hetznerCluster,
 			hetznerSecret, rescueSSHSecret, osSSHSecret, bootstrapSecret)).To(Succeed())
 	})
 
-	Context("Basic test", func() {
+	Context("Tests with host", func() {
+		var (
+			host    *infrav1.HetznerBareMetalHost
+			hostKey client.ObjectKey
+		)
+
 		BeforeEach(func() {
-			capiMachine = &clusterv1.Machine{
-				ObjectMeta: metav1.ObjectMeta{
-					GenerateName: "capi-machine-",
-					Namespace:    testNs.Name,
-					Finalizers:   []string{clusterv1.MachineFinalizer},
-					Labels: map[string]string{
-						clusterv1.ClusterNameLabel: capiCluster.Name,
-					},
-				},
-				Spec: clusterv1.MachineSpec{
-					ClusterName: capiCluster.Name,
-					InfrastructureRef: corev1.ObjectReference{
-						APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
-						Kind:       "HetznerBareMetalMachine",
-						Name:       bmMachineName,
-					},
-					FailureDomain: &defaultFailureDomain,
-				},
-			}
-			Expect(testEnv.Create(ctx, capiMachine)).To(Succeed())
+			host = helpers.BareMetalHost(
+				hostName,
+				testNs.Name,
+				helpers.WithRootDeviceHintWWN(),
+				helpers.WithHetznerClusterRef(hetznerClusterName),
+			)
+			Expect(testEnv.Create(ctx, host)).To(Succeed())
+			hostKey = client.ObjectKey{Namespace: testNs.Name, Name: hostName}
+		})
 
-			bmMachine = &infrav1.HetznerBareMetalMachine{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      bmMachineName,
-					Namespace: testNs.Name,
-					Labels: map[string]string{
-						clusterv1.ClusterNameLabel: capiCluster.Name,
+		AfterEach(func() {
+			Expect(testEnv.Cleanup(ctx, host)).To(Succeed())
+		})
+
+		Context("Basic test", func() {
+			BeforeEach(func() {
+				capiMachine = &clusterv1.Machine{
+					ObjectMeta: metav1.ObjectMeta{
+						GenerateName: "capi-machine-",
+						Namespace:    testNs.Name,
+						Finalizers:   []string{clusterv1.MachineFinalizer},
+						Labels: map[string]string{
+							clusterv1.ClusterNameLabel: capiCluster.Name,
+						},
 					},
-					OwnerReferences: []metav1.OwnerReference{
-						{
-							APIVersion: "cluster.x-k8s.io/v1beta1",
-							Kind:       "Machine",
-							Name:       capiMachine.Name,
-							UID:        capiMachine.UID,
+					Spec: clusterv1.MachineSpec{
+						ClusterName: capiCluster.Name,
+						InfrastructureRef: corev1.ObjectReference{
+							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
+							Kind:       "HetznerBareMetalMachine",
+							Name:       bmMachineName,
 						},
+						FailureDomain: &defaultFailureDomain,
 					},
-				},
-				Spec: getDefaultHetznerBareMetalMachineSpec(),
-			}
-			Expect(testEnv.Create(ctx, bmMachine)).To(Succeed())
+				}
+				Expect(testEnv.Create(ctx, capiMachine)).To(Succeed())
+
+				bmMachine = &infrav1.HetznerBareMetalMachine{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      bmMachineName,
+						Namespace: testNs.Name,
+						Labels: map[string]string{
+							clusterv1.ClusterNameLabel: capiCluster.Name,
+						},
+						OwnerReferences: []metav1.OwnerReference{
+							{
+								APIVersion: "cluster.x-k8s.io/v1beta1",
+								Kind:       "Machine",
+								Name:       capiMachine.Name,
+								UID:        capiMachine.UID,
+							},
+						},
+					},
+					Spec: getDefaultHetznerBareMetalMachineSpec(),
+				}
+				Expect(testEnv.Create(ctx, bmMachine)).To(Succeed())
 
-			key = client.ObjectKey{Namespace: testNs.Name, Name: bmMachineName}
-		})
+				key = client.ObjectKey{Namespace: testNs.Name, Name: bmMachineName}
+			})
 
-		It("creates the bare metal machine and provisions host", func() {
-			// Check whether bootstrap condition is not ready
-			Eventually(func() bool {
-				if err := testEnv.Get(ctx, key, bmMachine); err != nil {
-					return false
-				}
-				return isPresentAndFalseWithReason(key, bmMachine, infrav1.InstanceBootstrapReadyCondition, infrav1.InstanceBootstrapNotReadyReason)
-			}, timeout, time.Second).Should(BeTrue())
+			It("creates the bare metal machine and provisions host", func() {
+				// Check whether bootstrap condition is not ready
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+						return false
+					}
+					return isPresentAndFalseWithReason(key, bmMachine, infrav1.BootstrapReadyCondition, infrav1.BootstrapNotReadyReason)
+				}, timeout, time.Second).Should(BeTrue())
 
-			Eventually(func() error {
-				ph, err := patch.NewHelper(capiMachine, testEnv)
-				Expect(err).ShouldNot(HaveOccurred())
-				capiMachine.Spec.Bootstrap = clusterv1.Bootstrap{
-					DataSecretName: pointer.String("bootstrap-secret"),
-				}
-				return ph.Patch(ctx, capiMachine, patch.WithStatusObservedGeneration{})
-			}, timeout, time.Second).Should(BeNil())
+				Eventually(func() error {
+					ph, err := patch.NewHelper(capiMachine, testEnv)
+					Expect(err).ShouldNot(HaveOccurred())
+					capiMachine.Spec.Bootstrap = clusterv1.Bootstrap{
+						DataSecretName: pointer.String("bootstrap-secret"),
+					}
+					return ph.Patch(ctx, capiMachine, patch.WithStatusObservedGeneration{})
+				}, timeout, time.Second).Should(BeNil())
 
-			// Check whether bootstrap condition is ready
-			Eventually(func() bool {
-				if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+				// Check whether bootstrap condition is ready
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+						return false
+					}
+					return isPresentAndTrue(key, bmMachine, infrav1.BootstrapReadyCondition)
+				}, timeout, time.Second).Should(BeTrue())
+
+				defer func() {
+					Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
+				}()
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, hostKey, host); err != nil {
+						return false
+					}
+					if host.Spec.Status.ProvisioningState == infrav1.StateProvisioned {
+						return true
+					}
 					return false
-				}
-				return isPresentAndTrue(key, bmMachine, infrav1.InstanceBootstrapReadyCondition)
-			}, timeout, time.Second).Should(BeTrue())
+				}, timeout).Should(BeTrue())
 
-			defer func() {
-				Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
-			}()
-			Eventually(func() bool {
-				if err := testEnv.Get(ctx, hostKey, host); err != nil {
-					return false
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+						return false
+					}
+					return bmMachine.Status.Ready
+				}, timeout).Should(BeTrue())
+			})
+		})
+
+		Context("Basic test", func() {
+			BeforeEach(func() {
+				capiMachine = &clusterv1.Machine{
+					ObjectMeta: metav1.ObjectMeta{
+						GenerateName: "capi-machine-",
+						Namespace:    testNs.Name,
+						Finalizers:   []string{clusterv1.MachineFinalizer},
+						Labels: map[string]string{
+							clusterv1.ClusterNameLabel: capiCluster.Name,
+						},
+					},
+					Spec: clusterv1.MachineSpec{
+						ClusterName: capiCluster.Name,
+						InfrastructureRef: corev1.ObjectReference{
+							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
+							Kind:       "HetznerBareMetalMachine",
+							Name:       bmMachineName,
+						},
+						FailureDomain: &defaultFailureDomain,
+						Bootstrap: clusterv1.Bootstrap{
+							DataSecretName: pointer.String("bootstrap-secret"),
+						},
+					},
 				}
-				if host.Spec.Status.ProvisioningState == infrav1.StateProvisioned {
-					return true
+				Expect(testEnv.Create(ctx, capiMachine)).To(Succeed())
+
+				bmMachine = &infrav1.HetznerBareMetalMachine{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      bmMachineName,
+						Namespace: testNs.Name,
+						Labels: map[string]string{
+							clusterv1.ClusterNameLabel: capiCluster.Name,
+						},
+						OwnerReferences: []metav1.OwnerReference{
+							{
+								APIVersion: "cluster.x-k8s.io/v1beta1",
+								Kind:       "Machine",
+								Name:       capiMachine.Name,
+								UID:        capiMachine.UID,
+							},
+						},
+					},
+					Spec: getDefaultHetznerBareMetalMachineSpec(),
 				}
-				return false
-			}, timeout).Should(BeTrue())
+				Expect(testEnv.Create(ctx, bmMachine)).To(Succeed())
+
+				key = client.ObjectKey{Namespace: testNs.Name, Name: bmMachineName}
+			})
+
+			It("creates the bare metal machine", func() {
+				defer func() {
+					Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
+				}()
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+						return false
+					}
+					return true
+				}, timeout).Should(BeTrue())
+			})
+
+			It("sets the finalizer", func() {
+				defer func() {
+					Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
+				}()
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+						return false
+					}
+					for _, finalizer := range bmMachine.GetFinalizers() {
+						if finalizer == infrav1.BareMetalMachineFinalizer {
+							return true
+						}
+					}
+					return false
+				}, timeout).Should(BeTrue())
+			})
+
+			It("reaches state ready and provisions the host", func() {
+				defer func() {
+					Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
+				}()
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, hostKey, host); err != nil {
+						return false
+					}
+					if host.Spec.Status.ProvisioningState == infrav1.StateProvisioned {
+						return true
+					}
+					return false
+				}, timeout).Should(BeTrue())
 
-			Eventually(func() bool {
-				if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+						return false
+					}
+					return bmMachine.Status.Ready
+				}, timeout).Should(BeTrue())
+			})
+
+			It("sets the appropriate conditions that a host is associated and later provisioned", func() {
+				defer func() {
+					Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
+				}()
+
+				By("checking that the host is associated")
+				Eventually(func() bool {
+					return isPresentAndTrue(key, bmMachine, infrav1.HostAssociateSucceededCondition)
+				}, timeout).Should(BeTrue())
+
+				By("checking that the host is provisioned")
+				Eventually(func() bool {
+					return isPresentAndTrue(key, bmMachine, infrav1.HostProvisionSucceededCondition)
+				}, timeout).Should(BeTrue())
+
+				By("checking that the bare metal machine is ready")
+				Eventually(func() bool {
+					return isPresentAndTrue(key, bmMachine, clusterv1.ReadyCondition)
+				}, timeout).Should(BeTrue())
+			})
+
+			It("deletes successfully", func() {
+				// Delete the bmMachine object
+				Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
+
+				// Make sure the it has been deleted
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+						return true
+					}
 					return false
-				}
-				return bmMachine.Status.Ready
-			}, timeout).Should(BeTrue())
+				}, timeout, time.Second).Should(BeTrue())
+
+				// Make sure the host has been deprovisioned.
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, hostKey, host); err != nil {
+						return false
+					}
+					return host.Spec.Status.ProvisioningState == infrav1.StateNone
+				}, timeout, time.Second).Should(BeTrue())
+			})
+
+			It("sets a failure reason when maintenance mode is set on the host", func() {
+				defer func() {
+					Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
+				}()
+
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+						fmt.Println("failed to get bm machine. ", err)
+						return false
+					}
+					return bmMachine.Status.Ready
+				}, timeout, time.Second).Should(BeTrue())
+
+				By("setting maintenance mode")
+
+				ph, err := patch.NewHelper(host, testEnv)
+				Expect(err).ShouldNot(HaveOccurred())
+				maintenanceMode := true
+				host.Spec.MaintenanceMode = &maintenanceMode
+				Expect(ph.Patch(ctx, host, patch.WithStatusObservedGeneration{})).To(Succeed())
+
+				// It sets a failure reason
+				Eventually(func() bool {
+					if err := testEnv.Get(ctx, key, bmMachine); err != nil {
+						return false
+					}
+					return bmMachine.Status.FailureMessage != nil && *bmMachine.Status.FailureMessage == baremetal.FailureMessageMaintenanceMode
+				}, timeout).Should(BeTrue())
+			})
 		})
 	})
 
-	Context("Basic test", func() {
+	Context("Tests without host", func() {
 		BeforeEach(func() {
 			capiMachine = &clusterv1.Machine{
 				ObjectMeta: metav1.ObjectMeta{
@@ -293,9 +477,6 @@ var _ = Describe("HetznerBareMetalMachineReconciler", func() {
 						Name:       bmMachineName,
 					},
 					FailureDomain: &defaultFailureDomain,
-					Bootstrap: clusterv1.Bootstrap{
-						DataSecretName: pointer.String("bootstrap-secret"),
-					},
 				},
 			}
 			Expect(testEnv.Create(ctx, capiMachine)).To(Succeed())
@@ -323,105 +504,40 @@ var _ = Describe("HetznerBareMetalMachineReconciler", func() {
 			key = client.ObjectKey{Namespace: testNs.Name, Name: bmMachineName}
 		})
 
-		It("creates the bare metal machine", func() {
-			defer func() {
-				Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
-			}()
-			Eventually(func() bool {
-				if err := testEnv.Get(ctx, key, bmMachine); err != nil {
-					return false
-				}
-				return true
-			}, timeout).Should(BeTrue())
-		})
-
-		It("sets the finalizer", func() {
-			defer func() {
-				Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
-			}()
+		It("creates the bare metal machine and sets condition that no host is available", func() {
+			// Check whether bootstrap condition is not ready
 			Eventually(func() bool {
 				if err := testEnv.Get(ctx, key, bmMachine); err != nil {
 					return false
 				}
-				for _, finalizer := range bmMachine.GetFinalizers() {
-					if finalizer == infrav1.BareMetalMachineFinalizer {
-						return true
-					}
-				}
-				return false
-			}, timeout).Should(BeTrue())
-		})
-
-		It("reaches state ready and provisions the host", func() {
-			defer func() {
-				Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
-			}()
-			Eventually(func() bool {
-				if err := testEnv.Get(ctx, hostKey, host); err != nil {
-					return false
-				}
-				if host.Spec.Status.ProvisioningState == infrav1.StateProvisioned {
-					return true
-				}
-				return false
-			}, timeout).Should(BeTrue())
+				return isPresentAndFalseWithReason(key, bmMachine, infrav1.BootstrapReadyCondition, infrav1.BootstrapNotReadyReason)
+			}, timeout, time.Second).Should(BeTrue())
 
-			Eventually(func() bool {
-				if err := testEnv.Get(ctx, key, bmMachine); err != nil {
-					return false
+			Eventually(func() error {
+				ph, err := patch.NewHelper(capiMachine, testEnv)
+				Expect(err).ShouldNot(HaveOccurred())
+				capiMachine.Spec.Bootstrap = clusterv1.Bootstrap{
+					DataSecretName: pointer.String("bootstrap-secret"),
 				}
-				return bmMachine.Status.Ready
-			}, timeout).Should(BeTrue())
-		})
-
-		It("deletes successfully", func() {
-			// Delete the bmMachine object
-			Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
+				return ph.Patch(ctx, capiMachine, patch.WithStatusObservedGeneration{})
+			}, timeout, time.Second).Should(BeNil())
 
-			// Make sure the it has been deleted
+			// Check whether bootstrap condition is ready
 			Eventually(func() bool {
 				if err := testEnv.Get(ctx, key, bmMachine); err != nil {
-					return true
-				}
-				return false
-			}, timeout, time.Second).Should(BeTrue())
-
-			// Make sure the host has been deprovisioned.
-			Eventually(func() bool {
-				if err := testEnv.Get(ctx, hostKey, host); err != nil {
 					return false
 				}
-				return host.Spec.Status.ProvisioningState == infrav1.StateNone
+				return isPresentAndTrue(key, bmMachine, infrav1.BootstrapReadyCondition)
 			}, timeout, time.Second).Should(BeTrue())
-		})
 
-		It("sets a failure reason when maintenance mode is set on the host", func() {
 			defer func() {
 				Expect(testEnv.Delete(ctx, bmMachine)).To(Succeed())
 			}()
 
-			Eventually(func() bool {
-				if err := testEnv.Get(ctx, key, bmMachine); err != nil {
-					fmt.Println("failed to get bm machine. ", err)
-					return false
-				}
-				return bmMachine.Status.Ready
-			}, timeout, time.Second).Should(BeTrue())
+			By("checking that the bare metal machine has set the appropriate condition")
 
-			By("setting maintenance mode")
-
-			ph, err := patch.NewHelper(host, testEnv)
-			Expect(err).ShouldNot(HaveOccurred())
-			maintenanceMode := true
-			host.Spec.MaintenanceMode = &maintenanceMode
-			Expect(ph.Patch(ctx, host, patch.WithStatusObservedGeneration{})).To(Succeed())
-
-			// It sets a failure reason
 			Eventually(func() bool {
-				if err := testEnv.Get(ctx, key, bmMachine); err != nil {
-					return false
-				}
-				return bmMachine.Status.FailureMessage != nil && *bmMachine.Status.FailureMessage == baremetal.FailureMessageMaintenanceMode
+				return isPresentAndFalseWithReason(key, bmMachine, infrav1.HostAssociateSucceededCondition, infrav1.NoAvailableHostReason)
 			}, timeout).Should(BeTrue())
 		})
 	})
diff --git a/controllers/hetznercluster_controller_test.go b/controllers/hetznercluster_controller_test.go
index 9e58cc88..742f1038 100644
--- a/controllers/hetznercluster_controller_test.go
+++ b/controllers/hetznercluster_controller_test.go
@@ -124,7 +124,7 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
 			lbName string
 		)
 		BeforeEach(func() {
-			testNs, err = testEnv.CreateNamespace(ctx, "lb-attachement")
+			testNs, err = testEnv.CreateNamespace(ctx, "lb-attachment")
 			Expect(err).NotTo(HaveOccurred())
 			namespace = testNs.Name
 
diff --git a/pkg/services/hcloud/server/server_test.go b/pkg/services/hcloud/server/server_test.go
index 89884f1c..5a8abca8 100644
--- a/pkg/services/hcloud/server/server_test.go
+++ b/pkg/services/hcloud/server/server_test.go
@@ -216,26 +216,26 @@ var _ = Describe("handleServerStatusOff", func() {
 		res, err := service.handleServerStatusOff(context.Background(), server)
 		Expect(err).To(Succeed())
 		Expect(res).Should(Equal(reconcile.Result{RequeueAfter: 30 * time.Second}))
-		Expect(conditions.GetReason(hcloudMachine, infrav1.InstanceReadyCondition)).To(Equal(infrav1.ServerOffReason))
+		Expect(conditions.GetReason(hcloudMachine, infrav1.ServerAvailableCondition)).To(Equal(infrav1.ServerOffReason))
 		Expect(server.Status).To(Equal(hcloud.ServerStatusRunning))
 	})
 
 	It("tries to power on server again if it is not timed out", func() {
-		conditions.MarkFalse(hcloudMachine, infrav1.InstanceReadyCondition, infrav1.ServerOffReason, clusterv1.ConditionSeverityInfo, "")
+		conditions.MarkFalse(hcloudMachine, infrav1.ServerAvailableCondition, infrav1.ServerOffReason, clusterv1.ConditionSeverityInfo, "")
 		service := newTestService(hcloudMachine, client)
 		res, err := service.handleServerStatusOff(context.Background(), server)
 		Expect(err).To(Succeed())
 		Expect(res).Should(Equal(reconcile.Result{RequeueAfter: 30 * time.Second}))
 		Expect(server.Status).To(Equal(hcloud.ServerStatusRunning))
-		Expect(conditions.GetReason(hcloudMachine, infrav1.InstanceReadyCondition)).To(Equal(infrav1.ServerOffReason))
+		Expect(conditions.GetReason(hcloudMachine, infrav1.ServerAvailableCondition)).To(Equal(infrav1.ServerOffReason))
 	})
 
 	It("sets a failure message if it timed out", func() {
-		conditions.MarkFalse(hcloudMachine, infrav1.InstanceReadyCondition, infrav1.ServerOffReason, clusterv1.ConditionSeverityInfo, "")
+		conditions.MarkFalse(hcloudMachine, infrav1.ServerAvailableCondition, infrav1.ServerOffReason, clusterv1.ConditionSeverityInfo, "")
 		// manipulate lastTransitionTime
 		conditionsList := hcloudMachine.GetConditions()
 		for i, c := range conditionsList {
-			if c.Type == infrav1.InstanceReadyCondition {
+			if c.Type == infrav1.ServerAvailableCondition {
 				conditionsList[i].LastTransitionTime = metav1.NewTime(time.Now().Add(-time.Hour))
 			}
 		}
@@ -249,12 +249,12 @@ var _ = Describe("handleServerStatusOff", func() {
 	})
 
 	It("tries to power on server and sets new condition if different one is set", func() {
-		conditions.MarkTrue(hcloudMachine, infrav1.InstanceReadyCondition)
+		conditions.MarkTrue(hcloudMachine, infrav1.ServerAvailableCondition)
 		service := newTestService(hcloudMachine, client)
 		res, err := service.handleServerStatusOff(context.Background(), server)
 		Expect(err).To(Succeed())
 		Expect(res).Should(Equal(reconcile.Result{RequeueAfter: 30 * time.Second}))
-		Expect(conditions.GetReason(hcloudMachine, infrav1.InstanceReadyCondition)).To(Equal(infrav1.ServerOffReason))
+		Expect(conditions.GetReason(hcloudMachine, infrav1.ServerAvailableCondition)).To(Equal(infrav1.ServerOffReason))
 		Expect(server.Status).To(Equal(hcloud.ServerStatusRunning))
 	})
 })