diff --git a/controllers/webhook/metricsadmission_test.go b/controllers/webhook/metricsadmission_test.go new file mode 100644 index 00000000..e53cf4e1 --- /dev/null +++ b/controllers/webhook/metricsadmission_test.go @@ -0,0 +1,100 @@ +package webhook_test + +import ( + "context" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + + metricsv1 "github.com/ondat/metrics-exporter/api/config.storageos.com/v1" + storageoscomv1 "github.com/storageos/operator/api/v1" +) + +const clusterName = "storageoscluster" + +var _ = Describe("StorageOSCluster Validating Webhook", func() { + var cluster storageoscomv1.StorageOSCluster + BeforeEach(func() { + cluster = storageoscomv1.StorageOSCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: clusterName, + }, + Spec: storageoscomv1.StorageOSClusterSpec{ + Metrics: storageoscomv1.Metrics{ + Enabled: true, + }, + }, + } + }) + + AfterEach(func() { + cluster = storageoscomv1.StorageOSCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: clusterName, + }, + } + err := k8sClient.Delete(context.TODO(), &cluster) + // to satisfy our linter + Expect(err).To(Or(HaveOccurred(), Not(HaveOccurred()))) + }) + + Context("When the .spec.metrics is invalid", func() { + It("refuses invalid .logLevel", func() { + cluster.Spec.Metrics.LogLevel = "foo" + Expect(k8sClient.Create(context.TODO(), &cluster)). + To(MatchError( + newInvalidError(field.NotSupported(field.NewPath("spec.metrics.logLevel"), + "foo", []string{"debug", "info", "warn", "error", "dpanic", "panic", "fatal"})))) + }) + + It("refuses invalid .timeout", func() { + cluster.Spec.Metrics.Timeout = -1 + err := k8sClient.Create(context.TODO(), &cluster) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(MatchRegexp("spec.metrics.timeout.*should be greater than.*1")) + }) + + It("refuses invalid .disabledCollectors", func() { + cluster.Spec.Metrics.DisabledCollectors = []metricsv1.MetricsExporterCollector{ + metricsv1.MetricsExporterCollector("bad"), + } + Expect(k8sClient.Create(context.TODO(), &cluster)). + To(MatchError(newInvalidError(field.NotSupported( + field.NewPath("spec.metrics.disabledCollectors"), "bad", []string{ + "diskstats", + "filesystem", + })))) + }) + }) + + Context("When the .spec.metrics is valid", func() { + It("should accept it", func() { + cluster.Spec.Metrics.LogLevel = "debug" + cluster.Spec.Metrics.Timeout = 1 + cluster.Spec.Metrics.DisabledCollectors = []metricsv1.MetricsExporterCollector{ + metricsv1.MetricsExporterCollectorDiskStats, + } + Expect(k8sClient.Create(context.TODO(), &cluster)).NotTo(HaveOccurred()) + }) + }) + + Context("When the .spec.metrics are empty", func() { + It("should accept it", func() { + Expect(k8sClient.Create(context.TODO(), &cluster)).NotTo(HaveOccurred()) + }) + }) +}) + +func newInvalidError(errs ...*field.Error) *apierrors.StatusError { + return apierrors.NewInvalid( + schema.GroupKind{Group: "storageos.com", Kind: "StorageOSCluster"}, + "storageoscluster", + errs) +} diff --git a/controllers/webhook/suite_test.go b/controllers/webhook/suite_test.go new file mode 100644 index 00000000..821a039e --- /dev/null +++ b/controllers/webhook/suite_test.go @@ -0,0 +1,139 @@ +package webhook_test + +// heavily inspired by +// https://github.com/kubernetes-sigs/kubebuilder/blob/master/docs/book/src/cronjob-tutorial/testdata/project/api/v1/webhook_suite_test.go + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + //+kubebuilder:scaffold:imports + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + storageoscomv1 "github.com/storageos/operator/api/v1" + "github.com/storageos/operator/controllers/webhook" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +const namespace = "storageos" + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Webhook Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + }, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := runtime.NewScheme() + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + err = storageoscomv1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1beta1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).NotTo(HaveOccurred()) + + webhook, err := webhook.NewStorageOSClusterWebhook(mgr.GetClient(), mgr.GetScheme()) + Expect(err).NotTo(HaveOccurred()) + Expect(webhook.SetupWithManager(mgr)).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:webhook + + go func() { + defer GinkgoRecover() + err = mgr.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + + nm := corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + Expect(k8sClient.Create(context.TODO(), &nm)).NotTo(HaveOccurred()) +}, 60) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +})