Skip to content

Commit

Permalink
tests: webhook: add placement test, cleanup unit tests code
Browse files Browse the repository at this point in the history
- Added unit tests for template validator placement check in webhook.
- Improved webhook unit test code to make it simpler to read and extend.

Signed-off-by: Andrej Krejcir <[email protected]>
  • Loading branch information
akrejcir committed Nov 12, 2024
1 parent 2b6da7e commit 418ba69
Showing 1 changed file with 128 additions and 75 deletions.
203 changes: 128 additions & 75 deletions webhooks/ssp_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,120 +23,89 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

apps "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/ptr"
"kubevirt.io/controller-lifecycle-operator-sdk/api"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

sspv1beta2 "kubevirt.io/ssp-operator/api/v1beta2"
"kubevirt.io/ssp-operator/internal"
"kubevirt.io/ssp-operator/internal/common"
)

var _ = Describe("SSP Validation", func() {

var (
client client.Client
objects = make([]runtime.Object, 0)
apiClient client.Client
createIntercept func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.CreateOption) error

validator admission.CustomValidator
ctx context.Context
)

JustBeforeEach(func() {
scheme := runtime.NewScheme()
// add our own scheme
Expect(sspv1beta2.SchemeBuilder.AddToScheme(scheme)).To(Succeed())
// add more schemes
Expect(v1.AddToScheme(scheme)).To(Succeed())
BeforeEach(func() {
createIntercept = func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.CreateOption) error {
return client.Create(ctx, obj, opts...)
}

client = fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(objects...).Build()
apiClient = fake.NewClientBuilder().
WithScheme(common.Scheme).
WithInterceptorFuncs(interceptor.Funcs{
Create: func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.CreateOption) error {
return createIntercept(ctx, client, obj, opts...)
},
}).
Build()

validator = newSspValidator(client)
ctx = context.Background()
validator = newSspValidator(apiClient)
})

Context("creating SSP CR", func() {
const (
templatesNamespace = "test-templates-ns"
)

BeforeEach(func() {
objects = append(objects, &v1.Namespace{
err := apiClient.Create(ctx, &sspv1beta2.SSP{
ObjectMeta: metav1.ObjectMeta{
Name: templatesNamespace,
ResourceVersion: "1",
Name: "test-ssp",
Namespace: "test-ns",
},
Spec: sspv1beta2.SSPSpec{},
})
Expect(err).ToNot(HaveOccurred())
})

AfterEach(func() {
objects = make([]runtime.Object, 0)
})

Context("when one is already present", func() {
BeforeEach(func() {
// add an SSP CR to fake client
objects = append(objects, &sspv1beta2.SSP{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ssp",
Namespace: "test-ns",
ResourceVersion: "1",
},
Spec: sspv1beta2.SSPSpec{
CommonTemplates: sspv1beta2.CommonTemplates{
Namespace: templatesNamespace,
},
},
})
})

It("should be rejected", func() {
ssp := &sspv1beta2.SSP{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ssp2",
Namespace: "test-ns2",
},
Spec: sspv1beta2.SSPSpec{
CommonTemplates: sspv1beta2.CommonTemplates{
Namespace: templatesNamespace,
},
},
}
It("should reject SSP when one is already present", func() {
ssp := &sspv1beta2.SSP{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ssp2",
Namespace: "test-ns2",
},
Spec: sspv1beta2.SSPSpec{},
}

_, err := validator.ValidateCreate(ctx, ssp)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("creation failed, an SSP CR already exists in namespace test-ns: test-ssp"))
})
_, err := validator.ValidateCreate(ctx, ssp)
Expect(err).To(MatchError(ContainSubstring("creation failed, an SSP CR already exists in namespace test-ns: test-ssp")))
})
})

Context("DataImportCronTemplates", func() {
const (
templatesNamespace = "test-templates-ns"
)

var (
oldSSP *sspv1beta2.SSP
newSSP *sspv1beta2.SSP
)

BeforeEach(func() {
objects = append(objects, &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: templatesNamespace,
ResourceVersion: "1",
},
})

oldSSP = &sspv1beta2.SSP{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ssp",
Namespace: "test-ns",
},
Spec: sspv1beta2.SSPSpec{
CommonTemplates: sspv1beta2.CommonTemplates{
Namespace: templatesNamespace,
Namespace: "test-templates-ns",
DataImportCronTemplates: []sspv1beta2.DataImportCronTemplate{
{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -151,13 +120,9 @@ var _ = Describe("SSP Validation", func() {
newSSP = oldSSP.DeepCopy()
})

AfterEach(func() {
objects = make([]runtime.Object, 0)
})

It("should validate dataImportCronTemplates on create", func() {
_, err := validator.ValidateCreate(ctx, newSSP)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(ContainSubstring("missing name in DataImportCronTemplate")))

newSSP.Spec.CommonTemplates.DataImportCronTemplates[0].Name = "test-name"

Expand All @@ -167,7 +132,7 @@ var _ = Describe("SSP Validation", func() {

It("should validate dataImportCronTemplates on update", func() {
_, err := validator.ValidateUpdate(ctx, oldSSP, newSSP)
Expect(err).To(HaveOccurred())
Expect(err).To(MatchError(ContainSubstring("missing name in DataImportCronTemplate")))

newSSP.Spec.CommonTemplates.DataImportCronTemplates[0].Name = "test-name"

Expand All @@ -176,9 +141,97 @@ var _ = Describe("SSP Validation", func() {
})
})

Context("validate placement", func() {
It("should not call create API, if placement is nil", func() {
createIntercept = func(_ context.Context, _ client.WithWatch, _ client.Object, _ ...client.CreateOption) error {
Fail("Called create API")
return nil
}

ssp := &sspv1beta2.SSP{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ssp",
Namespace: "test-ns",
},
Spec: sspv1beta2.SSPSpec{
TemplateValidator: &sspv1beta2.TemplateValidator{
Replicas: ptr.To(int32(2)),
Placement: nil,
},
},
}

_, err := validator.ValidateCreate(ctx, ssp)
Expect(err).ToNot(HaveOccurred())
})

It("should call create API dry run", func() {
placement := &api.NodePlacement{
NodeSelector: map[string]string{
"test-label": "test-value",
},
Affinity: &v1.Affinity{
PodAffinity: &v1.PodAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{{
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
}},
},
},
Tolerations: []v1.Toleration{{
Key: "key",
Value: "value",
}},
}

var createWasCalled bool
createIntercept = func(ctx context.Context, cli client.WithWatch, obj client.Object, opts ...client.CreateOption) error {
deployment, ok := obj.(*apps.Deployment)
if !ok {
Fail("Expected created object to be Deployment.")
}

createOptions := &client.CreateOptions{}
for _, opt := range opts {
opt.ApplyToCreate(createOptions)
}

if len(createOptions.DryRun) != 1 || createOptions.DryRun[0] != metav1.DryRunAll {
Fail("Create call should be dry run.")
}

Expect(deployment.Spec.Template.Spec.NodeSelector).To(Equal(placement.NodeSelector))
Expect(deployment.Spec.Template.Spec.Affinity).To(Equal(placement.Affinity))
Expect(deployment.Spec.Template.Spec.Tolerations).To(Equal(placement.Tolerations))
createWasCalled = true
return nil
}

ssp := &sspv1beta2.SSP{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ssp",
Namespace: "test-ns",
},
Spec: sspv1beta2.SSPSpec{
TemplateValidator: &sspv1beta2.TemplateValidator{
Replicas: ptr.To(int32(2)),
Placement: placement,
},
},
}

_, err := validator.ValidateCreate(ctx, ssp)
Expect(err).ToNot(HaveOccurred())

Expect(createWasCalled).To(BeTrue())
})
})
})

func TestWebhook(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "API Suite")
RunSpecs(t, "Webhook Suite")
}

0 comments on commit 418ba69

Please sign in to comment.