Skip to content

Commit

Permalink
Implement mutating webhook & unit test
Browse files Browse the repository at this point in the history
Signed-off-by: Itamar Holder <[email protected]>
  • Loading branch information
iholder101 committed Jul 3, 2022
1 parent 6046353 commit 760fa1a
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 1 deletion.
3 changes: 3 additions & 0 deletions pkg/virt-api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,9 @@ func (app *virtAPIApp) registerMutatingWebhook(informers *webhooks.Informers) {
http.HandleFunc(components.MigrationMutatePath, func(w http.ResponseWriter, r *http.Request) {
mutating_webhook.ServeMigrationCreate(w, r)
})
http.HandleFunc(components.VMCloneCreateMutatePath, func(w http.ResponseWriter, r *http.Request) {
mutating_webhook.ServeClones(w, r)
})
}

func (app *virtAPIApp) setupTLS(k8sCAManager webhooksutils.ClientCAManager, kubevirtCAManager webhooksutils.ClientCAManager) {
Expand Down
4 changes: 4 additions & 0 deletions pkg/virt-api/webhooks/mutating-webhook/mutating-webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,7 @@ func ServeVMIs(resp http.ResponseWriter, req *http.Request, clusterConfig *virtc
func ServeMigrationCreate(resp http.ResponseWriter, req *http.Request) {
serve(resp, req, &mutators.MigrationCreateMutator{})
}

func ServeClones(resp http.ResponseWriter, req *http.Request) {
serve(resp, req, &mutators.CloneCreateMutator{})
}
5 changes: 5 additions & 0 deletions pkg/virt-api/webhooks/mutating-webhook/mutators/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
],
)
Expand All @@ -54,11 +55,15 @@ go_test(
"//pkg/virt-config:go_default_library",
"//pkg/virt-handler/node-labeller/util:go_default_library",
"//pkg/virt-operator/resource/generate/rbac:go_default_library",
"//staging/src/kubevirt.io/api/clone:go_default_library",
"//staging/src/kubevirt.io/api/clone/v1alpha1:go_default_library",
"//staging/src/kubevirt.io/api/core:go_default_library",
"//staging/src/kubevirt.io/api/core/v1:go_default_library",
"//staging/src/kubevirt.io/api/flavor:go_default_library",
"//staging/src/kubevirt.io/client-go/api:go_default_library",
"//staging/src/kubevirt.io/client-go/kubecli:go_default_library",
"//staging/src/kubevirt.io/client-go/testutils:go_default_library",
"//tests/util:go_default_library",
"//vendor/github.com/onsi/ginkgo/v2:go_default_library",
"//vendor/github.com/onsi/gomega:go_default_library",
"//vendor/k8s.io/api/admission/v1:go_default_library",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import (
"encoding/json"
"fmt"

"kubevirt.io/client-go/log"

admissionv1 "k8s.io/api/admission/v1"
k8sv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/rand"

"kubevirt.io/api/clone"
clonev1alpha1 "kubevirt.io/api/clone/v1alpha1"
Expand All @@ -35,7 +39,7 @@ type CloneCreateMutator struct {
}

func (mutator *CloneCreateMutator) Mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
if !webhookutils.ValidateRequestResource(ar.Request.Resource, clone.GroupName, clone.ResourceVMClonePlural) {
if resource := ar.Request.Resource; resource.Group != clone.GroupName || resource.Resource != clone.ResourceVMClonePlural {
err := fmt.Errorf("expect resource to be '%s'", clone.ResourceVMClonePlural)
return webhookutils.ToAdmissionResponseError(err)
}
Expand Down Expand Up @@ -68,6 +72,8 @@ func (mutator *CloneCreateMutator) Mutate(ar *admissionv1.AdmissionReview) *admi
return webhookutils.ToAdmissionResponseError(err)
}

log.Log.Object(vmClone).V(4).Info(fmt.Sprintf("Mutating clone %s. Patch: %s", vmClone.Name, string(patchBytes)))

jsonPatchType := admissionv1.PatchTypeJSONPatch
return &admissionv1.AdmissionResponse{
Allowed: true,
Expand All @@ -77,4 +83,28 @@ func (mutator *CloneCreateMutator) Mutate(ar *admissionv1.AdmissionReview) *admi
}

func mutateClone(vmClone *clonev1alpha1.VirtualMachineClone) {
if vmClone.Spec.Target == nil {
vmClone.Spec.Target = generateDefaultTarget(&vmClone.Spec)
} else if vmClone.Spec.Target.Name == "" {
vmClone.Spec.Target.Name = generateTargetName(vmClone.Spec.Source.Name)
}
}

func generateTargetName(sourceName string) string {
const randomSuffixLength = 5
return fmt.Sprintf("clone-%s-%s", sourceName, rand.String(randomSuffixLength))
}

func generateDefaultTarget(cloneSpec *clonev1alpha1.VirtualMachineCloneSpec) (target *k8sv1.TypedLocalObjectReference) {
const defaultTargetKind = "VirtualMachine"

source := cloneSpec.Source

target = &k8sv1.TypedLocalObjectReference{
APIGroup: source.APIGroup,
Kind: defaultTargetKind,
Name: generateTargetName(source.Name),
}

return target
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,90 @@
package mutators

import (
"encoding/json"

"kubevirt.io/kubevirt/tests/util"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
admissionv1 "k8s.io/api/admission/v1"
k8sv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/pointer"

"kubevirt.io/api/clone"
clonev1alpha1 "kubevirt.io/api/clone/v1alpha1"
"kubevirt.io/client-go/kubecli"
utiltypes "kubevirt.io/kubevirt/pkg/util/types"
)

var _ = Describe("Clone mutating webhook", func() {

var vmClone *clonev1alpha1.VirtualMachineClone

BeforeEach(func() {
vmClone = kubecli.NewMinimalCloneWithNS("testclone", util.NamespaceTestDefault)
vmClone.Spec.Source = &k8sv1.TypedLocalObjectReference{
APIGroup: pointer.String(clone.GroupName),
Kind: "VirtualMachine",
Name: "test-source-vm",
}
})

It("Target should be auto generated if missing", func() {
cloneSpec := mutate(vmClone)
Expect(cloneSpec.Target).ShouldNot(BeNil())
Expect(cloneSpec.Target.Name).ShouldNot(BeEmpty())
})

It("Target name should be auto generated if missing", func() {
vmClone.Spec.Target = &k8sv1.TypedLocalObjectReference{
APIGroup: pointer.String(clone.GroupName),
Kind: "VirtualMachine",
Name: "",
}
cloneSpec := mutate(vmClone)
Expect(cloneSpec.Target).ShouldNot(BeNil())
Expect(cloneSpec.Target.Name).ShouldNot(BeEmpty())
})

})

func createCloneAdmissionReview(vmClone *clonev1alpha1.VirtualMachineClone) *admissionv1.AdmissionReview {
cloneBytes, _ := json.Marshal(vmClone)

ar := &admissionv1.AdmissionReview{
Request: &admissionv1.AdmissionRequest{
Operation: admissionv1.Create,
Resource: metav1.GroupVersionResource{
Group: clone.GroupName,
Resource: clone.ResourceVMClonePlural,
},
Object: runtime.RawExtension{
Raw: cloneBytes,
},
},
}

return ar
}

func mutate(vmClone *clonev1alpha1.VirtualMachineClone) *clonev1alpha1.VirtualMachineCloneSpec {
ar := createCloneAdmissionReview(vmClone)
mutator := CloneCreateMutator{}

resp := mutator.Mutate(ar)
Expect(resp.Allowed).Should(BeTrue())

cloneSpec := &clonev1alpha1.VirtualMachineCloneSpec{}
patch := []utiltypes.PatchOperation{
{Value: cloneSpec},
}

err := json.Unmarshal(resp.Patch, &patch)
Expect(err).ToNot(HaveOccurred())
Expect(patch).NotTo(BeEmpty())

return cloneSpec
}
1 change: 1 addition & 0 deletions pkg/virt-operator/resource/generate/components/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)

Expand Down
29 changes: 29 additions & 0 deletions pkg/virt-operator/resource/generate/components/webhooks.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package components

import (
"fmt"

admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"

"kubevirt.io/api/clone"
clonev1alpha1 "kubevirt.io/api/clone/v1alpha1"
Expand Down Expand Up @@ -232,6 +235,30 @@ func NewVirtAPIMutatingWebhookConfiguration(installNamespace string) *admissionr
},
},
},
{
Name: fmt.Sprintf("%s-mutator.kubevirt.io", clone.ResourceVMClonePlural),
AdmissionReviewVersions: []string{"v1", "v1beta1"},
SideEffects: &sideEffectNone,
FailurePolicy: &failurePolicy,
TimeoutSeconds: &defaultTimeoutSeconds,
Rules: []admissionregistrationv1.RuleWithOperations{{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{clone.GroupName},
APIVersions: clone.ApiSupportedWebhookVersions,
Resources: []string{clone.ResourceVMClonePlural},
},
}},
ClientConfig: admissionregistrationv1.WebhookClientConfig{
Service: &admissionregistrationv1.ServiceReference{
Namespace: installNamespace,
Name: VirtApiServiceName,
Path: pointer.String(VMCloneCreateMutatePath),
},
},
},
},
}

Expand Down Expand Up @@ -819,3 +846,5 @@ const LauncherEvictionValidatePath = "/launcher-eviction-validate"
const MigrationPolicyCreateValidatePath = "/migration-policy-validate-create"

const VMCloneCreateValidatePath = "/vm-clone-validate-create"

const VMCloneCreateMutatePath = "/vm-clone-mutate-create"
1 change: 1 addition & 0 deletions staging/src/kubevirt.io/client-go/kubecli/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ go_library(
importpath = "kubevirt.io/client-go/kubecli",
visibility = ["//visibility:public"],
deps = [
"//staging/src/kubevirt.io/api/clone/v1alpha1:go_default_library",
"//staging/src/kubevirt.io/api/core:go_default_library",
"//staging/src/kubevirt.io/api/core/v1:go_default_library",
"//staging/src/kubevirt.io/api/migrations/v1alpha1:go_default_library",
Expand Down
24 changes: 24 additions & 0 deletions staging/src/kubevirt.io/client-go/kubecli/kubevirt_test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package kubecli
import (
"errors"

clonev1alpha1 "kubevirt.io/api/clone/v1alpha1"
v1alpha12 "kubevirt.io/api/clone/v1alpha1"

"kubevirt.io/api/migrations/v1alpha1"

k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -85,3 +88,24 @@ func NewMinimalMigrationPolicy(name string) *v1alpha1.MigrationPolicy {
func NewMinimalMigrationPolicyList(policies ...v1alpha1.MigrationPolicy) *v1alpha1.MigrationPolicyList {
return &v1alpha1.MigrationPolicyList{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1alpha1.GroupVersion.String(), Kind: v1alpha1.MigrationPolicyListKind.Kind}, Items: policies}
}

func NewMinimalClone(name string) *v1alpha12.VirtualMachineClone {
return NewMinimalCloneWithNS(name, "")
}

func NewMinimalCloneWithNS(name, namespace string) *v1alpha12.VirtualMachineClone {
return &v1alpha12.VirtualMachineClone{
TypeMeta: k8smetav1.TypeMeta{APIVersion: clonev1alpha1.SchemeGroupVersion.String(), Kind: clonev1alpha1.VirtualMachineCloneKind.Kind},
ObjectMeta: k8smetav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
}

func NewMinimalCloneList(clones ...v1alpha12.VirtualMachineClone) *v1alpha12.VirtualMachineCloneList {
return &v1alpha12.VirtualMachineCloneList{
TypeMeta: k8smetav1.TypeMeta{APIVersion: clonev1alpha1.SchemeGroupVersion.String(), Kind: clonev1alpha1.VirtualMachineCloneListKind.Kind},
Items: clones,
}
}

0 comments on commit 760fa1a

Please sign in to comment.