diff --git a/internal/controller/metalstackcluster_controller_test.go b/internal/controller/metalstackcluster_controller_test.go index b5e11e9..6764294 100644 --- a/internal/controller/metalstackcluster_controller_test.go +++ b/internal/controller/metalstackcluster_controller_test.go @@ -18,6 +18,7 @@ package controller import ( "context" + "net/http" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -25,6 +26,7 @@ import ( "github.com/stretchr/testify/mock" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" corev1 "k8s.io/api/core/v1" @@ -36,6 +38,7 @@ import ( metalnetwork "github.com/metal-stack/metal-go/api/client/network" "github.com/metal-stack/metal-go/api/models" metalgoclient "github.com/metal-stack/metal-go/test/client" + "github.com/metal-stack/metal-lib/httperrors" "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/metal-stack/metal-lib/pkg/testcommon" @@ -270,6 +273,17 @@ var _ = Describe("MetalStackCluster Controller", func() { "Type": Equal(v1alpha1.ClusterControlPlaneEndpointEnsured), "Status": Equal(corev1.ConditionTrue), }))) + + By("ssh keypair generation") + sshSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: owner.Name + "-ssh-keypair", + Namespace: resource.Namespace, + }, + } + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(sshSecret), sshSecret)).NotTo(HaveOccurred()) + Expect(sshSecret.Data).To(HaveKey("id_rsa")) + Expect(sshSecret.Data).To(HaveKey("id_rsa.pub")) }) }) Context("reconciliation when external resources are provided", func() { @@ -377,111 +391,76 @@ var _ = Describe("MetalStackCluster Controller", func() { When("referenced resources do not exist", func() { It("should fail reconciling", func() { + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - }) - }) - }) - - Context("When reconciling a MetalStackCluster resource", func() { - // const resourceName = "test-resource" - - // var ( - // ctx = context.Background() - - // resource *infrastructurev1alpha1.MetalStackCluster - // typeNamespacedName = types.NamespacedName{ - // Name: resourceName, - // Namespace: "default", - // } - // metalstackcluster = &infrastructurev1alpha1.MetalStackCluster{} - // ) - - // BeforeEach(func() { - // resource = &infrastructurev1alpha1.MetalStackCluster{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: resourceName, - // Namespace: "default", - // }, - // Spec: infrastructurev1alpha1.MetalStackClusterSpec{ - // ControlPlaneEndpoint: infrastructurev1alpha1.APIEndpoint{}, - // ProjectID: "test-project", - // NodeNetworkID: nil, - // ControlPlaneIP: nil, - // Partition: "test-partition", - // Firewall: &infrastructurev1alpha1.Firewall{}, - // }, - // } - - // By("creating the custom resource for the Kind MetalStackCluster") - // err := k8sClient.Get(ctx, typeNamespacedName, metalstackcluster) - // if err != nil && errors.IsNotFound(err) { - // Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - // } - // }) - - // AfterEach(func() { - // resource := &infrastructurev1alpha1.MetalStackCluster{} - // err := k8sClient.Get(ctx, typeNamespacedName, resource) - // Expect(err).NotTo(HaveOccurred()) - - // By("Cleanup the specific resource instance MetalStackCluster") - // Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - // }) - - // it should not do anything when the resource has no ownership yet - - // it should reconcile successfully when the resource gets an ownership - - // it automatically allocates dependent resources - - // it should generate an SSH secret - // it should allocate a node network - // it should allocate a control plane ip - // it should ensure a firewall deployment - - // it is idempotent! - // status conditions should be properly evaluated - - // it is possible to optionally provide control plane ip, node network id and firewall - - // it should delete the resource - - // it should delete all managed resources - - // it should not delete optionally provided resources - - // Context("should successfully reconcile the resource", Ordered, func() { - // It("should not do anything when the resource has no ownership yet", func() { - - // }) - - // By("setting an ownership ownership yet") - - // It("it should reconcile successfully", func() { + By("creating the cluster resource and setting the owner reference") + owner := &clusterv1beta1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "owner-", + Namespace: "default", + }, + } + Expect(k8sClient.Create(ctx, owner)).To(Succeed()) - // }) + resource.OwnerReferences = []metav1.OwnerReference{ + *metav1.NewControllerRef(owner, clusterv1beta1.GroupVersion.WithKind("Cluster")), + } + Expect(k8sClient.Update(ctx, resource)).To(Succeed()) - // It("it should automatically allocate dependent resources", func() { - // // it should generate an SSH secret - // // it should allocate a node network - // // it should allocate a control plane ip - // // it should ensure a firewall deployment - // }) - // }) - // controllerReconciler := &MetalStackClusterReconciler{ - // Client: k8sClient, - // Scheme: k8sClient.Scheme(), - // } + By("reconciling the resource") - // _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - // NamespacedName: typeNamespacedName, - // }) - // Expect(err).NotTo(HaveOccurred()) + typeNamespacedName := types.NamespacedName{ + Name: resource.Name, + Namespace: "default", + } - // resource := &infrastructurev1alpha1.MetalStackCluster{} - // err = k8sClient.Get(ctx, typeNamespacedName, resource) - // Expect(err).NotTo(HaveOccurred()) + controllerReconciler.MetalClient, _ = metalgoclient.NewMetalMockClient(testingT, &metalgoclient.MetalMockFns{ + IP: func(m *mock.Mock) { + m.On("FindIP", testcommon.MatchIgnoreContext(testingT, metalip.NewFindIPParams().WithID(controlPlaneIP)), nil). + Return(nil, &metalip.FindIPDefault{ + Payload: &httperrors.HTTPErrorResponse{ + StatusCode: http.StatusNotFound, + Message: "ip not found", + }, + }) + }, + Network: func(m *mock.Mock) { + m.On("FindNetwork", testcommon.MatchIgnoreContext(testingT, metalnetwork.NewFindNetworkParams().WithID(nodeNetworkID)), nil). + Return(nil, &metalnetwork.FindNetworkDefault{ + Payload: &httperrors.HTTPErrorResponse{ + StatusCode: http.StatusNotFound, + Message: "network not found", + }, + }) + }, + }) - // Expect(resource.Status.Conditions).To(ContainElement("bla")) + Eventually(func() error { + _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + return err + }).Should(MatchError(ContainSubstring("not found"))) + + Expect(k8sClient.Get(ctx, typeNamespacedName, resource)).ToNot(HaveOccurred()) + + Expect(resource.Status.Conditions).To(ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(v1alpha1.ClusterNodeNetworkEnsured), + "Status": Equal(corev1.ConditionFalse), + "Reason": Equal("InternalError"), + "Message": ContainSubstring("network not found"), + }))) + Expect(resource.Status.Conditions).To(ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(v1alpha1.ClusterFirewallDeploymentReady), + "Status": Equal(corev1.ConditionTrue), + }))) + Expect(resource.Status.Conditions).To(ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(v1alpha1.ClusterControlPlaneEndpointEnsured), + "Status": Equal(corev1.ConditionFalse), + "Reason": Equal("InternalError"), + "Message": ContainSubstring("ip not found"), + }))) + }) + }) }) })