Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SKS-1825: Set the default APIGroup and Kind for IPPool CR used in ElfMachineTemplate #172

Merged
merged 1 commit into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ resources:
kind: ElfMachineTemplate
path: github.com/smartxworks/cluster-api-provider-elf/api/v1beta1
version: v1beta1
webhooks:
defaulting: true
validation: true
webhookVersion: v1
version: "3"
20 changes: 20 additions & 0 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,23 @@ webhooks:
resources:
- elfmachines
sideEffects: None
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /mutate-infrastructure-cluster-x-k8s-io-v1beta1-elfmachinetemplate
failurePolicy: Fail
name: mutation.elfmachinetemplate.infrastructure.x-k8s.io
rules:
- apiGroups:
- infrastructure.cluster.x-k8s.io
apiVersions:
- v1beta1
operations:
- CREATE
- UPDATE
resources:
- elfmachinetemplates
sideEffects: None
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ require (
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.3.0
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/protobuf v1.31.0 // indirect
Expand Down
7 changes: 7 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ func main() {
}).SetupWebhookWithManager(mgr); err != nil {
return err
}

if err := (&webhooks.ElfMachineTemplateMutation{
Client: mgr.GetClient(),
Logger: mgr.GetLogger().WithName("ElfMachineTemplateMutation"),
}).SetupWebhookWithManager(mgr); err != nil {
return err
}
}

if err := controllers.AddClusterControllerToManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: elfClusterConcurrency}); err != nil {
Expand Down
7 changes: 7 additions & 0 deletions test/helpers/envtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ func NewTestEnvironment() *TestEnvironment {
return err
}

if err := (&webhooks.ElfMachineTemplateMutation{
Client: mgr.GetClient(),
Logger: mgr.GetLogger().WithName("ElfMachineTemplateMutation"),
}).SetupWebhookWithManager(mgr); err != nil {
return err
}

return nil
}

Expand Down
76 changes: 76 additions & 0 deletions webhooks/elfmachine_webhook_mutation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,79 @@ limitations under the License.
*/

package webhooks

import (
"context"
"encoding/json"
"testing"

. "github.com/onsi/gomega"
"gomodules.xyz/jsonpatch/v2"
admissionv1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

infrav1 "github.com/smartxworks/cluster-api-provider-elf/api/v1beta1"
"github.com/smartxworks/cluster-api-provider-elf/pkg/version"
"github.com/smartxworks/cluster-api-provider-elf/test/fake"
)

func init() {
scheme = runtime.NewScheme()
_ = infrav1.AddToScheme(scheme)
_ = admissionv1.AddToScheme(scheme)
}

var (
scheme *runtime.Scheme
)

func TestElfMachineMutation(t *testing.T) {
g := NewWithT(t)
tests := []testCase{}

elfMachine := fake.NewElfMachine(nil)
elfMachine.Annotations = nil
raw, err := marshal(elfMachine)
g.Expect(err).NotTo(HaveOccurred())
tests = append(tests, testCase{
name: "should set CAPE version",
admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
Kind: metav1.GroupVersionKind{Group: infrav1.GroupVersion.Group, Version: infrav1.GroupVersion.Version, Kind: "ElfMachine"},
Operation: admissionv1.Create,
Object: runtime.RawExtension{Raw: raw},
}},
expectRespAllowed: true,
expectPatchs: []jsonpatch.Operation{
{Operation: "add", Path: "/metadata/annotations", Value: map[string]interface{}{infrav1.CAPEVersionAnnotation: version.CAPEVersion()}},
},
})

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mutation := ElfMachineMutation{}
mutation.InjectDecoder(admission.NewDecoder(scheme))

resp := mutation.Handle(context.Background(), tc.admissionRequest)
g.Expect(resp.Allowed).Should(Equal(tc.expectRespAllowed))
g.Expect(resp.Patches).Should(Equal(tc.expectPatchs))
})
}
}

func marshal(obj client.Object) ([]byte, error) {
bs, err := json.Marshal(obj)
if err != nil {
return nil, err
}
return bs, nil
}

type testCase struct {
name string
admissionRequest admission.Request
expectRespAllowed bool
expectPatchs []jsonpatch.Operation
}
89 changes: 89 additions & 0 deletions webhooks/elfmachinetemplate_webhook_mutation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
Copyright 2024.

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.
*/

package webhooks

import (
goctx "context"
"encoding/json"
"net/http"

"github.com/go-logr/logr"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

infrav1 "github.com/smartxworks/cluster-api-provider-elf/api/v1beta1"
)

const (
defaultIPPoolAPIGroup = "ipam.metal3.io"
defaultIPPoolKind = "IPPool"
)

func (m *ElfMachineTemplateMutation) SetupWebhookWithManager(mgr ctrl.Manager) error {
if m.decoder == nil {
m.decoder = admission.NewDecoder(mgr.GetScheme())
}

Check warning on line 42 in webhooks/elfmachinetemplate_webhook_mutation.go

View check run for this annotation

Codecov / codecov/patch

webhooks/elfmachinetemplate_webhook_mutation.go#L39-L42

Added lines #L39 - L42 were not covered by tests

hookServer := mgr.GetWebhookServer()
hookServer.Register("/mutate-infrastructure-cluster-x-k8s-io-v1beta1-elfmachinetemplate", &webhook.Admission{Handler: m})
return ctrl.NewWebhookManagedBy(mgr).
For(&infrav1.ElfMachine{}).
Complete()

Check warning on line 48 in webhooks/elfmachinetemplate_webhook_mutation.go

View check run for this annotation

Codecov / codecov/patch

webhooks/elfmachinetemplate_webhook_mutation.go#L44-L48

Added lines #L44 - L48 were not covered by tests
}

//+kubebuilder:object:generate=false
//+kubebuilder:webhook:verbs=create;update,path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-elfmachinetemplate,mutating=true,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=elfmachinetemplates,versions=v1beta1,name=mutation.elfmachinetemplate.infrastructure.x-k8s.io,admissionReviewVersions=v1

type ElfMachineTemplateMutation struct {
client.Client
decoder *admission.Decoder
logr.Logger
}

func (m *ElfMachineTemplateMutation) Handle(ctx goctx.Context, request admission.Request) admission.Response {
var elfMachineTemplate infrav1.ElfMachineTemplate
if err := m.decoder.Decode(request, &elfMachineTemplate); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

Check warning on line 64 in webhooks/elfmachinetemplate_webhook_mutation.go

View check run for this annotation

Codecov / codecov/patch

webhooks/elfmachinetemplate_webhook_mutation.go#L63-L64

Added lines #L63 - L64 were not covered by tests

devices := elfMachineTemplate.Spec.Template.Spec.Network.Devices
for i := 0; i < len(devices); i++ {
for j := 0; j < len(devices[i].AddressesFromPools); j++ {
if devices[i].AddressesFromPools[j].APIGroup == nil || *devices[i].AddressesFromPools[j].APIGroup == "" {
devices[i].AddressesFromPools[j].APIGroup = pointer.String(defaultIPPoolAPIGroup)
}
if devices[i].AddressesFromPools[j].Kind == "" {
devices[i].AddressesFromPools[j].Kind = defaultIPPoolKind
}
}
}

if marshaledElfMachineTemplate, err := json.Marshal(elfMachineTemplate); err != nil {
return admission.Errored(http.StatusInternalServerError, err)

Check warning on line 79 in webhooks/elfmachinetemplate_webhook_mutation.go

View check run for this annotation

Codecov / codecov/patch

webhooks/elfmachinetemplate_webhook_mutation.go#L79

Added line #L79 was not covered by tests
} else {
return admission.PatchResponseFromRaw(request.Object.Raw, marshaledElfMachineTemplate)
}
}

// InjectDecoder injects the decoder.
func (m *ElfMachineTemplateMutation) InjectDecoder(d *admission.Decoder) error {
m.decoder = d
return nil
}
85 changes: 85 additions & 0 deletions webhooks/elfmachinetemplate_webhook_mutation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
Copyright 2024.

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.
*/

package webhooks

import (
"context"
"testing"

. "github.com/onsi/gomega"
"gomodules.xyz/jsonpatch/v2"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

infrav1 "github.com/smartxworks/cluster-api-provider-elf/api/v1beta1"
)

func TestElfMachineMutationTemplate(t *testing.T) {
g := NewWithT(t)
tests := []testCase{}

elfMachineTemplate := &infrav1.ElfMachineTemplate{
ObjectMeta: metav1.ObjectMeta{
Name: "elfmachinetemplate",
},
Spec: infrav1.ElfMachineTemplateSpec{
Template: infrav1.ElfMachineTemplateResource{
Spec: infrav1.ElfMachineSpec{},
},
},
}
elfMachineTemplate.Spec.Template.Spec.Network.Devices = []infrav1.NetworkDeviceSpec{
{AddressesFromPools: []corev1.TypedLocalObjectReference{{Name: "test"}}},
{AddressesFromPools: []corev1.TypedLocalObjectReference{{Name: "test", APIGroup: pointer.String("")}}},
{AddressesFromPools: []corev1.TypedLocalObjectReference{{Name: "test", APIGroup: pointer.String("apiGroup")}}},
{AddressesFromPools: []corev1.TypedLocalObjectReference{{Name: "test", APIGroup: pointer.String("apiGroup"), Kind: "kind"}}},
}
raw, err := marshal(elfMachineTemplate)
g.Expect(err).NotTo(HaveOccurred())
tests = append(tests, testCase{
name: "should set default values for network devices",
admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
Kind: metav1.GroupVersionKind{Group: infrav1.GroupVersion.Group, Version: infrav1.GroupVersion.Version, Kind: "ElfMachine"},
Operation: admissionv1.Create,
Object: runtime.RawExtension{Raw: raw},
}},
expectRespAllowed: true,
expectPatchs: []jsonpatch.Operation{
{Operation: "replace", Path: "/spec/template/spec/network/devices/0/addressesFromPools/0/apiGroup", Value: defaultIPPoolAPIGroup},
{Operation: "replace", Path: "/spec/template/spec/network/devices/0/addressesFromPools/0/kind", Value: defaultIPPoolKind},
{Operation: "replace", Path: "/spec/template/spec/network/devices/1/addressesFromPools/0/apiGroup", Value: defaultIPPoolAPIGroup},
{Operation: "replace", Path: "/spec/template/spec/network/devices/1/addressesFromPools/0/kind", Value: defaultIPPoolKind},
{Operation: "replace", Path: "/spec/template/spec/network/devices/2/addressesFromPools/0/kind", Value: defaultIPPoolKind},
},
})

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mutation := ElfMachineTemplateMutation{}
mutation.InjectDecoder(admission.NewDecoder(scheme))

resp := mutation.Handle(context.Background(), tc.admissionRequest)
g.Expect(resp.Allowed).Should(Equal(tc.expectRespAllowed))
g.Expect(resp.Patches).Should(HaveLen(len(tc.expectPatchs)))
g.Expect(resp.Patches).Should(ContainElements(tc.expectPatchs))
})
}
}
Loading