From 15446c2ccfc117a0feb5a5249c2e62714b2cd3d7 Mon Sep 17 00:00:00 2001 From: Piotr Marcinkiewicz Date: Tue, 3 Nov 2020 17:06:13 +0100 Subject: [OOM-K8S-CERT-EXTERNAL-PROVIDER] Add CertificateRequest controller test - Add CertificateRequest controller test with fake K8s API Issue-ID: OOM-2559 Signed-off-by: Piotr Marcinkiewicz Change-Id: I4e32c2d28f5c4ea35dd013119dfc31acb1646582 --- .../certificate_request_controller_test.go | 119 ++++++++++++++++++++- .../cmpv2_issuer_controller_test.go | 47 ++------ .../src/cmpv2provisioner/cmpv2_provisioner_test.go | 66 ++---------- .../src/testdata/provider.go | 38 ++++++- .../src/testdata/utils.go | 56 ++++++++++ 5 files changed, 227 insertions(+), 99 deletions(-) create mode 100644 certServiceK8sExternalProvider/src/testdata/utils.go diff --git a/certServiceK8sExternalProvider/src/cmpv2controller/certificate_request_controller_test.go b/certServiceK8sExternalProvider/src/cmpv2controller/certificate_request_controller_test.go index 2c401cce..f5869ea2 100644 --- a/certServiceK8sExternalProvider/src/cmpv2controller/certificate_request_controller_test.go +++ b/certServiceK8sExternalProvider/src/cmpv2controller/certificate_request_controller_test.go @@ -21,13 +21,51 @@ package cmpv2controller import ( + "context" "testing" cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "onap.org/oom-certservice/k8s-external-provider/src/cmpv2api" + provisioners "onap.org/oom-certservice/k8s-external-provider/src/cmpv2provisioner" + provisionersdata "onap.org/oom-certservice/k8s-external-provider/src/cmpv2provisioner/csr/testdata" + "onap.org/oom-certservice/k8s-external-provider/src/testdata" + x509 "onap.org/oom-certservice/k8s-external-provider/src/x509/testdata" +) + +const ( + group = "certmanager.onap.org" + certificateRequestName = "testRequest" + recorderBufferSize = 3 ) -const group = "certmanager.onap.org" +func Test_shouldSaveCorrectSignedPems_whenRequestReceived(t *testing.T) { + verifiedIssuer := getVerifiedIssuer() + createProvisioner(verifiedIssuer) + fakeClient := fake.NewFakeClientWithScheme(testdata.GetScheme(), &verifiedIssuer, + getValidCertificateRequest(), getValidPrivateKeySecret()) + fakeRecorder := record.NewFakeRecorder(recorderBufferSize) + controller := getCertRequestController(fakeRecorder, fakeClient) + fakeRequest := testdata.GetFakeRequest(certificateRequestName) + + res, err := controller.Reconcile(fakeRequest) + + signedPEM, trustedCAs := getCertificates(controller, fakeRequest.NamespacedName) + assert.Nil(t, err) + assert.NotNil(t, res) + assert.Equal(t, <-fakeRecorder.Events, "Normal Issued Certificate issued") + testdata.VerifyCertsAreEqualToExpected(t, signedPEM, trustedCAs) + clearProvisioner() +} func Test_shouldBeInvalidCMPv2CertificateRequest_whenEmpty(t *testing.T) { request := new(cmapi.CertificateRequest) @@ -50,3 +88,82 @@ func Test_shouldBeValidCMPv2CertificateRequest_whenKindIsCMPvIssuer(t *testing.T assert.True(t, isCMPv2CertificateRequest(request)) } + +func getCertificates(controller CertificateRequestController, namespacedName types.NamespacedName) ([]byte, []byte) { + certificateRequest := new(cmapi.CertificateRequest) + _ = controller.Client.Get(context.Background(), namespacedName, certificateRequest) + + signedPEM := certificateRequest.Status.Certificate + trustedCAs := certificateRequest.Status.CA + + return signedPEM, trustedCAs +} + +func getValidPrivateKeySecret() *v1.Secret { + const privateKeySecretKey = "tls.key" + + return &v1.Secret{ + Data: map[string][]byte{ + privateKeySecretKey: provisionersdata.PrivateKeyBytes, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: testdata.PrivateKeySecret, + Namespace: testdata.Namespace, + }, + } +} + +func getValidCertificateRequest() *cmapi.CertificateRequest { + return &cmapi.CertificateRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "", + APIVersion: testdata.APIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: certificateRequestName, + Namespace: testdata.Namespace, + Annotations: map[string]string{ + privateKeySecretNameAnnotation: testdata.PrivateKeySecret, + }, + }, + + Spec: cmapi.CertificateRequestSpec{ + IssuerRef: cmmeta.ObjectReference{ + Group: cmpv2api.GroupVersion.Group, + Kind: cmpv2api.CMPv2IssuerKind, + Name: testdata.IssuerObjectName, + }, + Request: []byte(x509.ValidCertificateSignRequest), + }, + } +} + +func getCertRequestController(fakeRecorder *record.FakeRecorder, fakeClient client.Client) CertificateRequestController { + controller := CertificateRequestController{ + Client: fakeClient, + Log: ctrl.Log.WithName("controllers").WithName("CertificateRequest"), + Recorder: fakeRecorder, + } + return controller +} + +func getVerifiedIssuer() cmpv2api.CMPv2Issuer { + issuer, _ := testdata.GetValidIssuerWithSecret() + issuer.Status = cmpv2api.CMPv2IssuerStatus{ + Conditions: []cmpv2api.CMPv2IssuerCondition{{ + Type: cmpv2api.ConditionReady, + Status: cmpv2api.ConditionTrue}}, + } + return issuer +} + +func createProvisioner(verifiedIssuer cmpv2api.CMPv2Issuer) { + provisionerFactory := provisioners.ProvisionerFactoryMock{} + fakeProvisioner, _ := provisionerFactory.CreateProvisioner(&verifiedIssuer, v1.Secret{}) + + provisioners.Store(testdata.GetIssuerStoreKey(), fakeProvisioner) +} + +func clearProvisioner() { + provisioners.Store(testdata.GetIssuerStoreKey(), nil) +} diff --git a/certServiceK8sExternalProvider/src/cmpv2controller/cmpv2_issuer_controller_test.go b/certServiceK8sExternalProvider/src/cmpv2controller/cmpv2_issuer_controller_test.go index 88aaf5ec..f4cb6944 100644 --- a/certServiceK8sExternalProvider/src/cmpv2controller/cmpv2_issuer_controller_test.go +++ b/certServiceK8sExternalProvider/src/cmpv2controller/cmpv2_issuer_controller_test.go @@ -24,43 +24,37 @@ import ( "testing" "github.com/go-logr/logr" - certmanager "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/record" "k8s.io/utils/clock" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" "onap.org/oom-certservice/k8s-external-provider/src/cmpv2api" - certserviceapi "onap.org/oom-certservice/k8s-external-provider/src/cmpv2api" provisioners "onap.org/oom-certservice/k8s-external-provider/src/cmpv2provisioner" "onap.org/oom-certservice/k8s-external-provider/src/testdata" ) func Test_shouldPrepareAndVerifyCMPv2Issuer_whenRequestReceived(t *testing.T) { - scheme := initScheme() + scheme := testdata.GetScheme() issuer, secret := testdata.GetValidIssuerWithSecret() - fakeClient := getFakeClient(scheme, issuer, secret) - fakeRequest := getFakeRequest() - fakeRecorder := record.NewFakeRecorder(3) - controller := getController(fakeRecorder, fakeClient) + fakeClient := fake.NewFakeClientWithScheme(scheme, &issuer, &secret) + fakeRequest := testdata.GetFakeRequest(testdata.IssuerObjectName) + fakeRecorder := record.NewFakeRecorder(recorderBufferSize) + controller := getCMPv2IssuerController(fakeRecorder, fakeClient) res, err := controller.Reconcile(fakeRequest) expectedProvisioner, _ := controller.ProvisionerFactory.CreateProvisioner(&issuer, secret) - actualProvisioner, _ := provisioners.Load(types.NamespacedName{Name: testdata.IssuerObjectName, Namespace: testdata.Namespace}) + actualProvisioner, _ := provisioners.Load(testdata.GetIssuerStoreKey()) assert.Nil(t, err) assert.NotNil(t, res) assert.Equal(t, <-fakeRecorder.Events, "Normal Verified CMPv2Issuer verified and ready to sign certificates") assert.NotNil(t, actualProvisioner) assert.ObjectsAreEqual(expectedProvisioner, actualProvisioner) + clearProvisioner() } func Test_shouldBeValidCMPv2IssuerSpec_whenAllFieldsAreSet(t *testing.T) { @@ -100,7 +94,7 @@ func test_shouldBeInvalidCMPv2IssuerSpec_whenFunctionApplied(t *testing.T, trans assert.NotNil(t, err) } -func getController(fakeRecorder *record.FakeRecorder, mockClient client.Client) CMPv2IssuerController { +func getCMPv2IssuerController(fakeRecorder *record.FakeRecorder, mockClient client.Client) CMPv2IssuerController { controller := CMPv2IssuerController{ Log: ctrl.Log.WithName("controllers").WithName("CertificateRequest"), Clock: clock.RealClock{}, @@ -111,31 +105,6 @@ func getController(fakeRecorder *record.FakeRecorder, mockClient client.Client) return controller } -func getFakeRequest() reconcile.Request { - fakeRequest := reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: testdata.Namespace, - Name: testdata.IssuerObjectName, - }, - } - return fakeRequest -} - -func getFakeClient(scheme *runtime.Scheme, issuer cmpv2api.CMPv2Issuer, secret apiv1.Secret) client.Client { - fakeClient := func() client.Client { - return fake.NewFakeClientWithScheme(scheme, &issuer, &secret) - }() - return fakeClient -} - -func initScheme() *runtime.Scheme { - scheme := runtime.NewScheme() - _ = clientgoscheme.AddToScheme(scheme) - _ = certmanager.AddToScheme(scheme) - _ = certserviceapi.AddToScheme(scheme) - return scheme -} - type MockLogger struct { mock.Mock } diff --git a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_test.go b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_test.go index a483c72c..cfafe959 100644 --- a/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_test.go +++ b/certServiceK8sExternalProvider/src/cmpv2provisioner/cmpv2_provisioner_test.go @@ -21,10 +21,7 @@ package cmpv2provisioner import ( - "bytes" "context" - "io/ioutil" - "log" "testing" "time" @@ -32,10 +29,10 @@ import ( "github.com/stretchr/testify/assert" apiv1 "k8s.io/api/core/v1" apimach "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "onap.org/oom-certservice/k8s-external-provider/src/certserviceclient" "onap.org/oom-certservice/k8s-external-provider/src/cmpv2api" + "onap.org/oom-certservice/k8s-external-provider/src/testdata" ) const ISSUER_NAME = "cmpv2-issuer" @@ -44,7 +41,7 @@ const ISSUER_NAMESPACE = "onap" func Test_shouldCreateCorrectCertServiceCA(t *testing.T) { issuer := createIssuerAndCerts(ISSUER_NAME, ISSUER_URL) - provisioner, err := New(&issuer, &certServiceClientMock{}) + provisioner, err := New(&issuer, &certserviceclient.CertServiceClientMock{}) assert.Nil(t, err) assert.Equal(t, provisioner.name, issuer.Name, "Unexpected provisioner name.") @@ -53,34 +50,31 @@ func Test_shouldCreateCorrectCertServiceCA(t *testing.T) { func Test_shouldSuccessfullyLoadPreviouslyStoredProvisioner(t *testing.T) { issuer := createIssuerAndCerts(ISSUER_NAME, ISSUER_URL) - provisioner, err := New(&issuer, &certServiceClientMock{}) + provisioner, err := New(&issuer, &certserviceclient.CertServiceClientMock{}) assert.Nil(t, err) - issuerNamespaceName := createIssuerNamespaceName(ISSUER_NAMESPACE, ISSUER_NAME) + issuerNamespaceName := testdata.CreateIssuerNamespaceName(ISSUER_NAMESPACE, ISSUER_NAME) Store(issuerNamespaceName, provisioner) provisioner, ok := Load(issuerNamespaceName) - verifyThatConditionIsTrue(ok, "Provisioner could not be loaded.", t) + testdata.VerifyThatConditionIsTrue(ok, "Provisioner could not be loaded.", t) assert.Equal(t, provisioner.name, issuer.Name, "Unexpected provisioner name.") assert.Equal(t, provisioner.url, issuer.Spec.URL, "Unexpected provisioner url.") } func Test_shouldReturnCorrectSignedPemsWhenParametersAreCorrect(t *testing.T) { - const EXPECTED_SIGNED_FILENAME = "testdata/expected_signed.pem" - const EXPECTED_TRUSTED_FILENAME = "testdata/expected_trusted.pem" - issuer := createIssuerAndCerts(ISSUER_NAME, ISSUER_URL) provisionerFactory := ProvisionerFactoryMock{} provisioner, err := provisionerFactory.CreateProvisioner(&issuer, apiv1.Secret{}) - issuerNamespaceName := createIssuerNamespaceName(ISSUER_NAMESPACE, ISSUER_NAME) + issuerNamespaceName := testdata.CreateIssuerNamespaceName(ISSUER_NAMESPACE, ISSUER_NAME) Store(issuerNamespaceName, provisioner) provisioner, ok := Load(issuerNamespaceName) - verifyThatConditionIsTrue(ok, "Provisioner could not be loaded", t) + testdata.VerifyThatConditionIsTrue(ok, "Provisioner could not be loaded", t) ctx := context.Background() request := createCertificateRequest() @@ -90,21 +84,7 @@ func Test_shouldReturnCorrectSignedPemsWhenParametersAreCorrect(t *testing.T) { assert.Nil(t, err) - verifyThatConditionIsTrue(areSlicesEqual(signedPEM, readFile(EXPECTED_SIGNED_FILENAME)), "Signed pem is different than expected.", t) - verifyThatConditionIsTrue(areSlicesEqual(trustedCAs, readFile(EXPECTED_TRUSTED_FILENAME)), "Trusted CAs pem is different than expected.", t) -} - -func verifyThatConditionIsTrue(cond bool, message string, t *testing.T) { - if !cond { - t.Fatal(message) - } -} - -func createIssuerNamespaceName(namespace string, name string) types.NamespacedName { - return types.NamespacedName{ - Namespace: namespace, - Name: name, - } + testdata.VerifyCertsAreEqualToExpected(t, signedPEM, trustedCAs) } func createIssuerAndCerts(name string, url string) cmpv2api.CMPv2Issuer { @@ -114,14 +94,6 @@ func createIssuerAndCerts(name string, url string) cmpv2api.CMPv2Issuer { return issuer } -func readFile(filename string) []byte { - certRequest, err := ioutil.ReadFile(filename) - if err != nil { - log.Fatal(err) - } - return certRequest -} - func createCertificateRequest() *cmapi.CertificateRequest { const CERTIFICATE_DURATION = "1h" const ISSUER_KIND = "CMPv2Issuer" @@ -140,33 +112,17 @@ func createCertificateRequest() *cmapi.CertificateRequest { request.Spec.IssuerRef.Name = ISSUER_NAME request.Spec.IssuerRef.Kind = ISSUER_KIND request.Spec.IssuerRef.Group = ISSUER_GROUP - request.Spec.Request = readFile(SPEC_REQUEST_FILENAME) + request.Spec.Request = testdata.ReadFile(SPEC_REQUEST_FILENAME) request.Spec.IsCA = true cond := new(cmapi.CertificateRequestCondition) cond.Type = CONDITION_TYPE request.Status.Conditions = []cmapi.CertificateRequestCondition{*cond} - request.Status.Certificate = readFile(STATUS_CERTIFICATE_FILENAME) + request.Status.Certificate = testdata.ReadFile(STATUS_CERTIFICATE_FILENAME) return request } func getPrivateKeyBytes() []byte { - return readFile("testdata/test_private_key.pem") -} - -func areSlicesEqual(slice1 []byte, slice2 []byte) bool { - return bytes.Compare(slice1, slice2) == 0 -} - -type certServiceClientMock struct { - getCertificatesFunc func(csr []byte, key []byte) (*certserviceclient.CertificatesResponse, error) -} - -func (client *certServiceClientMock) GetCertificates(csr []byte, key []byte) (*certserviceclient.CertificatesResponse, error) { - return client.getCertificatesFunc(csr, key) -} - -func (client *certServiceClientMock) CheckHealth() error { - return nil + return testdata.ReadFile("testdata/test_private_key.pem") } diff --git a/certServiceK8sExternalProvider/src/testdata/provider.go b/certServiceK8sExternalProvider/src/testdata/provider.go index 2e352cbf..6bb420c3 100644 --- a/certServiceK8sExternalProvider/src/testdata/provider.go +++ b/certServiceK8sExternalProvider/src/testdata/provider.go @@ -21,8 +21,13 @@ package testdata import ( + cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + scheme2 "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "onap.org/oom-certservice/k8s-external-provider/src/cmpv2api" ) @@ -36,15 +41,15 @@ const ( KeySecretKey = "cmpv2Issuer-key.pem" CertSecretKey = "cmpv2Issuer-cert.pem" CacertSecretKey = "cacert.pem" - Namespace = "default" - IssuerObjectName = "fakeIssuer" + Namespace = "onap" + IssuerObjectName = "cmpv2-issuer" Kind = "CMPv2Issuer" APIVersion = "v1" + PrivateKeySecret = "privateKeySecretName" ) func GetValidIssuerWithSecret() (cmpv2api.CMPv2Issuer, v1.Secret) { issuer := cmpv2api.CMPv2Issuer{ - TypeMeta: metav1.TypeMeta{ APIVersion: APIVersion, Kind: Kind, @@ -55,8 +60,8 @@ func GetValidIssuerWithSecret() (cmpv2api.CMPv2Issuer, v1.Secret) { }, Spec: GetValidCMPv2IssuerSpec(), } - secret := v1.Secret{ + secret := v1.Secret{ Data: map[string][]byte{ KeySecretKey: KeyBytes, CertSecretKey: CertBytes, @@ -87,3 +92,28 @@ func GetValidCMPv2IssuerSpec() cmpv2api.CMPv2IssuerSpec { return issuerSpec } +func GetScheme() *runtime.Scheme { + scheme := runtime.NewScheme() + _ = scheme2.AddToScheme(scheme) + _ = cmapi.AddToScheme(scheme) + _ = cmpv2api.AddToScheme(scheme) + return scheme +} + +func GetFakeRequest(objectName string) reconcile.Request { + fakeRequest := reconcile.Request{ + NamespacedName: CreateIssuerNamespaceName(Namespace, objectName), + } + return fakeRequest +} + +func GetIssuerStoreKey() types.NamespacedName { + return CreateIssuerNamespaceName(Namespace, IssuerObjectName) +} + +func CreateIssuerNamespaceName(namespace string, name string) types.NamespacedName { + return types.NamespacedName{ + Namespace: namespace, + Name: name, + } +} diff --git a/certServiceK8sExternalProvider/src/testdata/utils.go b/certServiceK8sExternalProvider/src/testdata/utils.go new file mode 100644 index 00000000..af22195d --- /dev/null +++ b/certServiceK8sExternalProvider/src/testdata/utils.go @@ -0,0 +1,56 @@ +/* + * ============LICENSE_START======================================================= + * oom-certservice-k8s-external-provider + * ================================================================================ + * Copyright (C) 2020 Nokia. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package testdata + +import ( + "bytes" + "io/ioutil" + "log" + "testing" +) + +func ReadFile(filename string) []byte { + certRequest, err := ioutil.ReadFile(filename) + if err != nil { + log.Fatal(err) + } + return certRequest +} + +func VerifyCertsAreEqualToExpected(t *testing.T, signedPEM []byte, trustedCAs []byte) { + expectedSignedFilename := "../cmpv2provisioner/testdata/expected_signed.pem" + expectedTrustedFilename := "../cmpv2provisioner/testdata/expected_trusted.pem" + + VerifyThatConditionIsTrue(AreSlicesEqual(signedPEM, + ReadFile(expectedSignedFilename)), "Signed pem is different than expected.", t) + VerifyThatConditionIsTrue(AreSlicesEqual(trustedCAs, + ReadFile(expectedTrustedFilename)), "Trusted CAs pem is different than expected.", t) +} + +func AreSlicesEqual(slice1 []byte, slice2 []byte) bool { + return bytes.Compare(slice1, slice2) == 0 +} + +func VerifyThatConditionIsTrue(cond bool, message string, t *testing.T) { + if !cond { + t.Fatal(message) + } +} -- cgit 1.2.3-korg