containerized-data-importer/pkg/apiserver/webhooks/datavolume-validate_test.go
Michael Henriksen e4fa647eea Clone permission refactoring (#906)
* allow user with 'create pod' permission in source namespace to clone PVCs also refactored Kubevirt interface to clone permission check

* make sure DataVolume spec is immutable also don't check perms when clone source namespace = dest namespace
2019-08-05 11:04:03 -04:00

538 lines
16 KiB
Go

/*
* This file is part of the CDI project
*
* 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.
*
* Copyright 2019 Red Hat, Inc.
*
*/
package webhooks
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/api/admission/v1beta1"
corev1 "k8s.io/api/core/v1"
k8sv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
cdicorev1alpha1 "kubevirt.io/containerized-data-importer/pkg/apis/core/v1alpha1"
)
var _ = Describe("Validating Webhook", func() {
Context("with DataVolume admission review", func() {
It("should accept DataVolume with HTTP source on create", func() {
dataVolume := newHTTPDataVolume("testDV", "http://www.example.com")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(true))
})
It("should accept DataVolume with Registry source on create", func() {
dataVolume := newRegistryDataVolume("testDV", "docker://registry:5000/test")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(true))
})
It("should accept DataVolume with PVC source on create", func() {
dataVolume := newPVCDataVolume("testDV", "testNamespace", "test")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(true))
})
It("should reject invalid DataVolume source PVC namespace on create", func() {
dataVolume := newPVCDataVolume("testDV", "", "test")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should reject invalid DataVolume source PVC name on create", func() {
dataVolume := newPVCDataVolume("testDV", "testNamespace", "")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should reject DataVolume with name length greater than 55 characters", func() {
dataVolume := newHTTPDataVolume(
"the-name-length-of-this-datavolume-is-greater-then-55cha",
"http://www.example.com")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should reject DataVolume source with invalid URL on create", func() {
dataVolume := newHTTPDataVolume("testDV", "invalidurl")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should reject DataVolume with multiple sources on create", func() {
dataVolume := newDataVolumeWithMultipleSources("testDV")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should reject DataVolume with empty PVC create", func() {
dataVolume := newDataVolumeWithEmptyPVCSpec("testDV", "http://www.example.com")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should reject DataVolume with PVC size 0", func() {
dataVolume := newDataVolumeWithPVCSizeZero("testDV", "http://www.example.com")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should accept DataVolume with Blank source and no content type", func() {
dataVolume := newBlankDataVolume("blank")
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(true))
})
It("should accept DataVolume with Blank source and kubevirt contentType", func() {
dataVolume := newBlankDataVolume("blank")
dataVolume.Spec.ContentType = cdicorev1alpha1.DataVolumeKubeVirt
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(true))
})
It("should reject DataVolume with Blank source and archive contentType", func() {
dataVolume := newBlankDataVolume("blank")
dataVolume.Spec.ContentType = cdicorev1alpha1.DataVolumeArchive
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should reject DataVolume with invalid contentType", func() {
dataVolume := newHTTPDataVolume("testDV", "http://www.example.com")
dataVolume.Spec.ContentType = "invalid"
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should accept DataVolume with archive contentType", func() {
dataVolume := newHTTPDataVolume("testDV", "http://www.example.com")
dataVolume.Spec.ContentType = cdicorev1alpha1.DataVolumeArchive
dvBytes, _ := json.Marshal(&dataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: dvBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(true))
})
It("should reject invalid DataVolume spec update", func() {
newDataVolume := newPVCDataVolume("testDV", "newNamespace", "testName")
newBytes, _ := json.Marshal(&newDataVolume)
oldDataVolume := newDataVolume.DeepCopy()
oldDataVolume.Spec.Source.PVC.Namespace = "oldNamespace"
oldBytes, _ := json.Marshal(oldDataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Operation: v1beta1.Update,
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: newBytes,
},
OldObject: runtime.RawExtension{
Raw: oldBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(false))
})
It("should accept object meta update", func() {
newDataVolume := newPVCDataVolume("testDV", "newNamespace", "testName")
newBytes, _ := json.Marshal(&newDataVolume)
oldDataVolume := newDataVolume.DeepCopy()
oldDataVolume.Annotations = map[string]string{"foo": "bar"}
oldBytes, _ := json.Marshal(oldDataVolume)
ar := &v1beta1.AdmissionReview{
Request: &v1beta1.AdmissionRequest{
Operation: v1beta1.Update,
Resource: metav1.GroupVersionResource{
Group: cdicorev1alpha1.SchemeGroupVersion.Group,
Version: cdicorev1alpha1.SchemeGroupVersion.Version,
Resource: "datavolumes",
},
Object: runtime.RawExtension{
Raw: newBytes,
},
OldObject: runtime.RawExtension{
Raw: oldBytes,
},
},
}
resp := validateDVs(ar)
Expect(resp.Allowed).To(Equal(true))
})
})
})
func newHTTPDataVolume(name, url string) *cdicorev1alpha1.DataVolume {
httpSource := cdicorev1alpha1.DataVolumeSource{
HTTP: &cdicorev1alpha1.DataVolumeSourceHTTP{URL: url},
}
pvc := newPVCSpec(5, "M")
return newDataVolume(name, httpSource, pvc)
}
func newRegistryDataVolume(name, url string) *cdicorev1alpha1.DataVolume {
registrySource := cdicorev1alpha1.DataVolumeSource{
Registry: &cdicorev1alpha1.DataVolumeSourceRegistry{URL: url},
}
pvc := newPVCSpec(5, "M")
return newDataVolume(name, registrySource, pvc)
}
func newBlankDataVolume(name string) *cdicorev1alpha1.DataVolume {
blankSource := cdicorev1alpha1.DataVolumeSource{
Blank: &cdicorev1alpha1.DataVolumeBlankImage{},
}
pvc := newPVCSpec(5, "M")
return newDataVolume(name, blankSource, pvc)
}
func newPVCDataVolume(name, pvcNamespace, pvcName string) *cdicorev1alpha1.DataVolume {
pvcSource := cdicorev1alpha1.DataVolumeSource{
PVC: &cdicorev1alpha1.DataVolumeSourcePVC{
Namespace: pvcNamespace,
Name: pvcName,
},
}
pvc := newPVCSpec(5, "M")
return newDataVolume(name, pvcSource, pvc)
}
func newDataVolumeWithEmptyPVCSpec(name, url string) *cdicorev1alpha1.DataVolume {
httpSource := cdicorev1alpha1.DataVolumeSource{
HTTP: &cdicorev1alpha1.DataVolumeSourceHTTP{URL: url},
}
return newDataVolume(name, httpSource, nil)
}
func newDataVolumeWithMultipleSources(name string) *cdicorev1alpha1.DataVolume {
source := cdicorev1alpha1.DataVolumeSource{
HTTP: &cdicorev1alpha1.DataVolumeSourceHTTP{URL: "http://www.example.com"},
S3: &cdicorev1alpha1.DataVolumeSourceS3{URL: "http://s3.examples3.com"},
}
pvc := newPVCSpec(5, "M")
return newDataVolume(name, source, pvc)
}
func newDataVolumeWithPVCSizeZero(name, url string) *cdicorev1alpha1.DataVolume {
httpSource := cdicorev1alpha1.DataVolumeSource{
HTTP: &cdicorev1alpha1.DataVolumeSourceHTTP{URL: url},
}
pvc := newPVCSpec(0, "M")
return newDataVolume(name, httpSource, pvc)
}
func newDataVolume(name string, source cdicorev1alpha1.DataVolumeSource, pvc *corev1.PersistentVolumeClaimSpec) *cdicorev1alpha1.DataVolume {
namespace := k8sv1.NamespaceDefault
dv := &cdicorev1alpha1.DataVolume{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
SelfLink: fmt.Sprintf("/apis/%s/namespaces/%s/datavolumes/%s", cdicorev1alpha1.SchemeGroupVersion.String(), namespace, name),
},
TypeMeta: metav1.TypeMeta{
APIVersion: cdicorev1alpha1.SchemeGroupVersion.String(),
Kind: "DataVolume",
},
Status: cdicorev1alpha1.DataVolumeStatus{},
Spec: cdicorev1alpha1.DataVolumeSpec{
Source: source,
PVC: pvc,
},
}
return dv
}
func newPVCSpec(sizeValue int64, sizeFormat resource.Format) *corev1.PersistentVolumeClaimSpec {
requests := make(map[corev1.ResourceName]resource.Quantity)
requests["storage"] = *resource.NewQuantity(sizeValue, sizeFormat)
pvc := &corev1.PersistentVolumeClaimSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{
corev1.ReadWriteOnce,
},
Resources: corev1.ResourceRequirements{
Requests: requests,
},
}
return pvc
}
func validateDVs(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
wh := NewDataVolumeValidatingWebhook(nil)
return serve(ar, wh)
}
func serve(ar *v1beta1.AdmissionReview, handler http.Handler) *v1beta1.AdmissionResponse {
reqBytes, _ := json.Marshal(ar)
req, err := http.NewRequest("POST", "/foobar", bytes.NewReader(reqBytes))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
var response v1beta1.AdmissionReview
err = json.NewDecoder(rr.Body).Decode(&response)
Expect(err).ToNot(HaveOccurred())
return response.Response
}