From 4b524595ad7671abfd4eecf9de6f2e5058e88f16 Mon Sep 17 00:00:00 2001 From: Leon Date: Sun, 26 Jan 2025 17:03:04 +0800 Subject: [PATCH] add test --- .../apps/component/component_controller.go | 2 +- .../component/transformer_component_tls.go | 79 +---- .../transformer_component_tls_test.go | 298 +++++++++++++++++- 3 files changed, 290 insertions(+), 89 deletions(-) diff --git a/controllers/apps/component/component_controller.go b/controllers/apps/component/component_controller.go index 0d8b3bec522..fac098fe836 100644 --- a/controllers/apps/component/component_controller.go +++ b/controllers/apps/component/component_controller.go @@ -155,7 +155,7 @@ func (r *ComponentReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // handle component system accounts &componentAccountTransformer{}, // handle the TLS configuration - &componentTLSTransformer{Client: r.Client}, + &componentTLSTransformer{}, // rerender parameters after v-scale and h-scale &componentRelatedParametersTransformer{Client: r.Client}, // resolve and build vars for template and Env diff --git a/controllers/apps/component/transformer_component_tls.go b/controllers/apps/component/transformer_component_tls.go index 7b08d938dd2..828cefed7cb 100644 --- a/controllers/apps/component/transformer_component_tls.go +++ b/controllers/apps/component/transformer_component_tls.go @@ -46,9 +46,7 @@ type tlsIssuer interface { } // componentTLSTransformer handles the TLS configuration for the component. -type componentTLSTransformer struct { - client.Client -} +type componentTLSTransformer struct{} var _ graph.Transformer = &componentTLSTransformer{} @@ -83,10 +81,7 @@ func (t *componentTLSTransformer) Transform(ctx graph.TransformContext, dag *gra return t.updateVolumeNVolumeMount(compDef, synthesizedComp) } else { // the issuer and secretObj may be nil - if err = t.handleDelete(transCtx.Context, transCtx.Client, dag, issuer, secretObj); err != nil { - return err - } - return t.removeVolumeNVolumeMount(compDef, synthesizedComp) + return t.handleDelete(transCtx.Context, transCtx.Client, dag, issuer, secretObj) } } @@ -331,91 +326,23 @@ func (t *componentTLSTransformer) updateVolumeNVolumeMount(compDef *appsv1.Compo func (t *componentTLSTransformer) composeTLSVolume(compDef *appsv1.ComponentDefinition, synthesizedComp *component.SynthesizedComponent) (*corev1.Volume, error) { - var secretName string - var ca, cert, key *string - - tls := synthesizedComp.TLSConfig - switch tls.Issuer.Name { - case appsv1.IssuerKubeBlocks: - secretName = tlsSecretName(synthesizedComp.ClusterName, synthesizedComp.Name) - ca = compDef.Spec.TLS.CAFile - cert = compDef.Spec.TLS.CertFile - key = compDef.Spec.TLS.KeyFile - case appsv1.IssuerUserProvided: - secretName = tls.Issuer.SecretRef.Name - if len(tls.Issuer.SecretRef.CA) > 0 { - ca = &tls.Issuer.SecretRef.CA - } - if len(tls.Issuer.SecretRef.Cert) > 0 { - cert = &tls.Issuer.SecretRef.Cert - } - if len(tls.Issuer.SecretRef.Key) > 0 { - key = &tls.Issuer.SecretRef.Key - } - } - volume := corev1.Volume{ Name: compDef.Spec.TLS.VolumeName, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ - SecretName: secretName, - Items: []corev1.KeyToPath{}, + SecretName: tlsSecretName(synthesizedComp.ClusterName, synthesizedComp.Name), Optional: ptr.To(false), }, }, } - - addItem := func(source, target *string) error { - if target != nil && source == nil { - return fmt.Errorf("%s is required but not provided", *target) - } - if target != nil && source != nil { - volume.VolumeSource.Secret.Items = - append(volume.VolumeSource.Secret.Items, corev1.KeyToPath{Key: *source, Path: *target}) - } - return nil - } - if err := addItem(ca, compDef.Spec.TLS.CAFile); err != nil { - return nil, err - } - if err := addItem(cert, compDef.Spec.TLS.CertFile); err != nil { - return nil, err - } - if err := addItem(key, compDef.Spec.TLS.KeyFile); err != nil { - return nil, err - } - if compDef.Spec.TLS.DefaultMode != nil { volume.VolumeSource.Secret.DefaultMode = ptr.To(*compDef.Spec.TLS.DefaultMode) } else { volume.VolumeSource.Secret.DefaultMode = ptr.To(int32(0600)) } - return &volume, nil } -func (t *componentTLSTransformer) removeVolumeNVolumeMount(compDef *appsv1.ComponentDefinition, - synthesizedComp *component.SynthesizedComponent) error { - if compDef.Spec.TLS == nil || len(compDef.Spec.TLS.VolumeName) == 0 { - return nil - } - - // remove the volume - synthesizedComp.PodSpec.Volumes = slices.DeleteFunc(synthesizedComp.PodSpec.Volumes, func(vol corev1.Volume) bool { - return vol.Name == compDef.Spec.TLS.VolumeName - }) - - // remove the volume mount - for i := range synthesizedComp.PodSpec.Containers { - synthesizedComp.PodSpec.Containers[i].VolumeMounts = - slices.DeleteFunc(synthesizedComp.PodSpec.Containers[i].VolumeMounts, func(m corev1.VolumeMount) bool { - return m.Name == compDef.Spec.TLS.VolumeName - }) - } - - return nil -} - func tlsSecretName(clusterName, compName string) string { return clusterName + "-" + compName + "-tls-certs" } diff --git a/controllers/apps/component/transformer_component_tls_test.go b/controllers/apps/component/transformer_component_tls_test.go index 1ba923d2049..30c78e1033f 100644 --- a/controllers/apps/component/transformer_component_tls_test.go +++ b/controllers/apps/component/transformer_component_tls_test.go @@ -20,25 +20,299 @@ along with this program. If not, see . package component import ( + "fmt" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" - testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + appsutil "github.com/apecloud/kubeblocks/controllers/apps/util" + "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/component" + "github.com/apecloud/kubeblocks/pkg/controller/graph" + "github.com/apecloud/kubeblocks/pkg/controller/model" ) -var _ = Describe("TLS self-signed cert function", func() { - cleanEnv := func() { - // must wait until resources deleted and no longer exist before the testcases start, - // otherwise if later it needs to create some new resource objects with the same name, - // in race conditions, it will find the existence of old objects, resulting failure to - // create the new objects. - By("clean resources") +var _ = Describe("TLS transformer test", func() { + const ( + compDefName = "test-compdef" + clusterName = "test-cluster" + compName = "comp" + ) + + var ( + reader *appsutil.MockReader + dag *graph.DAG + transCtx *componentTransformContext + + tls = &appsv1.TLS{ + VolumeName: "tls", + MountPath: "/etc/pki/tls", + DefaultMode: ptr.To(int32(0600)), + CAFile: ptr.To("ca.pem"), + CertFile: ptr.To("cert.pem"), + KeyFile: ptr.To("key.pem"), + } + + tlsConfig4KB = &appsv1.TLSConfig{ + Enable: true, + Issuer: &appsv1.Issuer{ + Name: appsv1.IssuerKubeBlocks, + }, + } + + tlsSecret4User = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testCtx.DefaultNamespace, + Name: "tls-secret-4-user", + }, + Data: map[string][]byte{ + "ca": []byte("ca-4-user"), + "cert": []byte("cert-4-user"), + "key": []byte("key-4-user"), + }, + } + tlsConfig4User = &appsv1.TLSConfig{ + Enable: true, + Issuer: &appsv1.Issuer{ + Name: appsv1.IssuerUserProvided, + SecretRef: &appsv1.TLSSecretRef{ + Namespace: tlsSecret4User.Namespace, + Name: tlsSecret4User.Name, + CA: "ca", + Cert: "cert", + Key: "key", + }, + }, + } + + newDAG = func(graphCli model.GraphClient, comp *appsv1.Component) *graph.DAG { + d := graph.NewDAG() + graphCli.Root(d, comp, comp, model.ActionStatusPtr()) + return d + } + ) + + BeforeEach(func() { + reader = &appsutil.MockReader{ + Objects: []client.Object{tlsSecret4User}, + } + + compDef := &appsv1.ComponentDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: compDefName, + }, + Spec: appsv1.ComponentDefinitionSpec{}, + } + comp := &appsv1.Component{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testCtx.DefaultNamespace, + Name: constant.GenerateClusterComponentName(clusterName, compName), + Labels: map[string]string{ + constant.AppManagedByLabelKey: constant.AppName, + constant.AppInstanceLabelKey: clusterName, + constant.KBAppComponentLabelKey: compName, + }, + }, + Spec: appsv1.ComponentSpec{}, + } - testapps.ClearClusterResourcesWithRemoveFinalizerOption(&testCtx) + graphCli := model.NewGraphClient(reader) + dag = newDAG(graphCli, comp) + + transCtx = &componentTransformContext{ + Context: ctx, + Client: graphCli, + EventRecorder: nil, + Logger: logger, + CompDef: compDef, + Component: comp, + ComponentOrig: comp.DeepCopy(), + SynthesizeComponent: &component.SynthesizedComponent{ + Namespace: testCtx.DefaultNamespace, + ClusterName: clusterName, + Name: compName, + PodSpec: &corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "app", + }, + }, + }, + }, + } + }) + + checkTLSSecret := func(exist bool, issuer ...appsv1.IssuerName) { + graphCli := transCtx.Client.(model.GraphClient) + objs := graphCli.FindAll(dag, &corev1.Secret{}) + if !exist { + Expect(len(objs)).Should(Equal(0)) + } else { + Expect(objs).Should(HaveLen(1)) + secret := objs[0].(*corev1.Secret) + Expect(secret.GetName()).Should(Equal(tlsSecretName(clusterName, compName))) + if issuer[0] == appsv1.IssuerKubeBlocks { + Expect(secret.Data).Should(HaveKey(*tls.CAFile)) + Expect(secret.Data).Should(HaveKey(*tls.CertFile)) + Expect(secret.Data).Should(HaveKey(*tls.KeyFile)) + } else { + Expect(secret.Data).Should(HaveKeyWithValue(*tls.CAFile, tlsSecret4User.Data[tlsConfig4User.Issuer.SecretRef.CA])) + Expect(secret.Data).Should(HaveKeyWithValue(*tls.CertFile, tlsSecret4User.Data[tlsConfig4User.Issuer.SecretRef.Cert])) + Expect(secret.Data).Should(HaveKeyWithValue(*tls.KeyFile, tlsSecret4User.Data[tlsConfig4User.Issuer.SecretRef.Key])) + } + } + } + + checkVolumeNMounts := func(exist bool) { + targetVolume := corev1.Volume{ + Name: tls.VolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: tlsSecretName(clusterName, compName), + Optional: ptr.To(false), + DefaultMode: tls.DefaultMode, + }, + }, + } + targetVolumeMount := corev1.VolumeMount{ + Name: tls.VolumeName, + MountPath: tls.MountPath, + ReadOnly: true, + } + + podSpec := transCtx.SynthesizeComponent.PodSpec + if exist { + Expect(podSpec.Volumes).Should(ContainElements(targetVolume)) + for _, c := range podSpec.Containers { + Expect(c.VolumeMounts).Should(ContainElements(targetVolumeMount)) + } + } else { + Expect(podSpec.Volumes).ShouldNot(ContainElements(targetVolume)) + for _, c := range podSpec.Containers { + Expect(c.VolumeMounts).ShouldNot(ContainElements(targetVolumeMount)) + } + } } - BeforeEach(cleanEnv) + Context("provision", func() { + It("w/o define, disabled", func() { + transformer := &componentTLSTransformer{} + err := transformer.Transform(transCtx, dag) + Expect(err).Should(BeNil()) + + // check the secret, volume and mounts + checkTLSSecret(false) + checkVolumeNMounts(false) + }) + + It("w/o define, enabled", func() { + // enable the TLS + transCtx.SynthesizeComponent.TLSConfig = tlsConfig4KB + + transformer := &componentTLSTransformer{} + err := transformer.Transform(transCtx, dag) + Expect(err).ShouldNot(BeNil()) + Expect(err.Error()).Should(ContainSubstring( + fmt.Sprintf("the TLS is enabled but the component definition %s doesn't support it", transCtx.CompDef.Name))) + }) + + It("w/ define, disabled", func() { + // define the TLS + transCtx.CompDef.Spec.TLS = tls + + transformer := &componentTLSTransformer{} + err := transformer.Transform(transCtx, dag) + Expect(err).Should(BeNil()) + + // check the secret, volume and mounts + checkTLSSecret(false) + checkVolumeNMounts(false) + }) + + It("w/ define, enabled - kb", func() { + // define and enable the TLS + transCtx.CompDef.Spec.TLS = tls + transCtx.SynthesizeComponent.TLSConfig = tlsConfig4KB + + transformer := &componentTLSTransformer{} + err := transformer.Transform(transCtx, dag) + Expect(err).Should(BeNil()) + + // check the secret, volume and mounts + checkTLSSecret(true, appsv1.IssuerKubeBlocks) + checkVolumeNMounts(true) + }) + + It("w/ define, enabled - user", func() { + // define and enable the TLS + transCtx.CompDef.Spec.TLS = tls + transCtx.SynthesizeComponent.TLSConfig = tlsConfig4User + + transformer := &componentTLSTransformer{} + err := transformer.Transform(transCtx, dag) + Expect(err).Should(BeNil()) + + // check the secret, volume and mounts + checkTLSSecret(true, appsv1.IssuerUserProvided) + checkVolumeNMounts(true) + }) + }) + + Context("update & disable", func() { + BeforeEach(func() { + // mock the TLS secret object + reader.Objects = append(reader.Objects, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testCtx.DefaultNamespace, + Name: tlsSecretName(clusterName, compName), + }, + }) + }) + + It("update", func() { + // define and enable the TLS + transCtx.CompDef.Spec.TLS = tls + transCtx.SynthesizeComponent.TLSConfig = tlsConfig4User // user only + + // update the certs + tlsSecret4User.Data = map[string][]byte{ + "ca": []byte("ca-4-user-updated"), + "cert": []byte("cert-4-user-updated"), + "key": []byte("key-4-user-updated"), + } + + transformer := &componentTLSTransformer{} + err := transformer.Transform(transCtx, dag) + Expect(err).Should(BeNil()) + + // check the secret updated + checkTLSSecret(true, appsv1.IssuerUserProvided) + }) + + It("disable after provision", func() { + // define the TLS + transCtx.CompDef.Spec.TLS = tls + + transformer := &componentTLSTransformer{} + err := transformer.Transform(transCtx, dag) + Expect(err).Should(BeNil()) + + // check the secret, volume and mounts to be deleted + graphCli := transCtx.Client.(model.GraphClient) - AfterEach(cleanEnv) + objs := graphCli.FindAll(dag, &corev1.Secret{}) + Expect(objs).Should(HaveLen(1)) + Expect(graphCli.IsAction(dag, objs[0], model.ActionDeletePtr())).Should(BeTrue()) + secret := objs[0].(*corev1.Secret) + Expect(secret.GetName()).Should(Equal(tlsSecretName(clusterName, compName))) - // TODO: test + checkVolumeNMounts(false) + }) + }) })