diff --git a/pkg/services/hcloud/server/server.go b/pkg/services/hcloud/server/server.go index ea92954ea..dc3741480 100644 --- a/pkg/services/hcloud/server/server.go +++ b/pkg/services/hcloud/server/server.go @@ -59,7 +59,7 @@ const ( var hcloudImageURLCommandDir = "/shared" -var errServerCreateNotPossible = fmt.Errorf("server create not possible - need action") +var errServerCreateNotPossible = errors.New("server create not possible - need action") // Service defines struct with machine scope to reconcile HCloudMachines. type Service struct { @@ -251,13 +251,9 @@ func (s *Service) handleBootStateUnset(ctx context.Context) (reconcile.Result, e ) return reconcile.Result{}, nil } - if errors.Is(err, errServerCreateNotPossible) { err = fmt.Errorf("createServerFromImageNameOrURL failed: %w", err) s.scope.Error(err, "") - conditions.MarkFalse(hm, infrav1.ServerCreateSucceededCondition, - "ServerCreateNotPossible", clusterv1.ConditionSeverityWarning, - "%s", err.Error()) return reconcile.Result{RequeueAfter: 5 * time.Minute}, nil } return reconcile.Result{}, fmt.Errorf("failed to create server: %w", err) diff --git a/pkg/services/hcloud/server/server_test.go b/pkg/services/hcloud/server/server_test.go index 624c8d2a5..b9b6f8d69 100644 --- a/pkg/services/hcloud/server/server_test.go +++ b/pkg/services/hcloud/server/server_test.go @@ -1229,6 +1229,33 @@ var _ = Describe("Reconcile", func() { Expect(isPresentAndFalseWithReason(service.scope.HCloudMachine, infrav1.HetznerAPIReachableCondition, infrav1.RateLimitExceededReason)).To(BeTrue()) }) + It("requeues for 5 minutes when server creation is not possible while creating a server", func() { + By("setting the bootstrap data") + err = testEnv.Create(ctx, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bootstrapsecret", + Namespace: testNs.Name, + }, + Data: map[string][]byte{ + "value": []byte("dummy-bootstrap-data"), + }, + }) + Expect(err).To(BeNil()) + + service.scope.Machine.Spec.Bootstrap.DataSecretName = ptr.To("bootstrapsecret") + + By("ensuring that the mock hcloud client returns no server type") + hcloudClient.On("GetServerType", mock.Anything, mock.Anything).Return(nil, nil).Once() + + By("calling reconcile") + res, err := service.Reconcile(ctx) + Expect(err).To(BeNil()) + Expect(res).To(Equal(reconcile.Result{RequeueAfter: 5 * time.Minute})) + + By("ensuring the server type not found condition is set") + Expect(isPresentAndFalseWithReason(service.scope.HCloudMachine, infrav1.ServerCreateSucceededCondition, infrav1.ServerTypeNotFoundReason)).To(BeTrue()) + }) + It("sets condition HCloudCredentialsInvalid when HCloud API returns 'unauthorized' error while finding server", func() { By("setting the bootstrap data") err = testEnv.Create(ctx, &corev1.Secret{ diff --git a/test/helpers/envtest.go b/test/helpers/envtest.go index 048f79830..b9dc8841d 100644 --- a/test/helpers/envtest.go +++ b/test/helpers/envtest.go @@ -25,6 +25,7 @@ import ( "path" "path/filepath" goruntime "runtime" + "strings" "time" g "github.com/onsi/ginkgo/v2" @@ -146,7 +147,7 @@ func NewTestEnvironment() *TestEnvironment { initializeWebhookInEnvironment() if _, err := env.Start(); err != nil { - panic(err) + panic(wrapEnvtestStartError(err)) } logLevel := "info" @@ -221,6 +222,24 @@ func NewTestEnvironment() *TestEnvironment { } } +func wrapEnvtestStartError(err error) error { + if err == nil { + return nil + } + + // Check whether the error is about missing binaries in $PATH, for example: + // + // unable to start control plane itself: failed to start the controlplane. + // retried 5 times: exec: "etcd": executable file not found in $PATH" + errText := err.Error() + if !strings.Contains(errText, "$PATH") { + // This looks like another error, so do not add the hint. + return err + } + + return fmt.Errorf("%w\nHint: set KUBEBUILDER_ASSETS=$PWD/hack/tools/bin/k8s/1.??.0-linux-amd64", err) +} + // StartManager starts the manager and sets a cancel function into the testEnv object. func (t *TestEnvironment) StartManager(ctx context.Context) error { ctx, cancel := context.WithCancel(ctx)