containerized-data-importer/pkg/controller/common/util_test.go
Edu Gómez Escandell eb6b76a399
Support IPv6 in controller GetMetricsURL (#3165)
Joining hostname+port with string concatenation leads to wrong URLs
when the hostame is an IPv6:

HOST    PORT   Sprintf    CORRECT
::1     8000   ::1:8000   [::1]:8000

To avoid this issue, it's best to use net.JoinHostPort. I added a test
that verifies this fix.

This type of issue can be discovered running the following command:

    golangci-lint run ./... --enable nosprintfhostport

Signed-off-by: Edu Gómez Escandell <egomez@redhat.com>
2024-04-08 14:43:52 +02:00

359 lines
11 KiB
Go

package common
import (
"context"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1"
sdkapi "kubevirt.io/controller-lifecycle-operator-sdk/api"
)
var _ = Describe("GetRequestedImageSize", func() {
It("Should return 1G if 1G provided", func() {
result, err := GetRequestedImageSize(CreatePvc("testPVC", "default", nil, nil))
Expect(err).ToNot(HaveOccurred())
Expect(result).To(Equal("1G"))
})
It("Should return error and blank if no size provided", func() {
result, err := GetRequestedImageSize(createPvcNoSize("testPVC", "default", nil, nil))
Expect(err).To(HaveOccurred())
Expect(result).To(Equal(""))
})
})
var _ = Describe("validateContentTypes", func() {
getContentType := func(contentType string) cdiv1.DataVolumeContentType {
if contentType == "" {
return cdiv1.DataVolumeKubeVirt
}
return cdiv1.DataVolumeContentType(contentType)
}
DescribeTable("should return", func(sourceContentType, targetContentType string, expectedResult bool) {
sourcePvc := CreatePvc("testPVC", "default", map[string]string{AnnContentType: sourceContentType}, nil)
dvSpec := &cdiv1.DataVolumeSpec{}
dvSpec.ContentType = cdiv1.DataVolumeContentType(targetContentType)
validated, sourceContent, targetContent := validateContentTypes(sourcePvc, dvSpec)
Expect(validated).To(Equal(expectedResult))
Expect(sourceContent).To(Equal(getContentType(sourceContentType)))
Expect(targetContent).To(Equal(getContentType(targetContentType)))
},
Entry("true when using archive in source and target", string(cdiv1.DataVolumeArchive), string(cdiv1.DataVolumeArchive), true),
Entry("false when using archive in source and KubeVirt in target", string(cdiv1.DataVolumeArchive), string(cdiv1.DataVolumeKubeVirt), false),
Entry("false when using KubeVirt in source and archive in target", string(cdiv1.DataVolumeKubeVirt), string(cdiv1.DataVolumeArchive), false),
Entry("true when using KubeVirt in source and target", string(cdiv1.DataVolumeKubeVirt), string(cdiv1.DataVolumeKubeVirt), true),
Entry("true when using default in source and target", "", "", true),
Entry("true when using default in source and KubeVirt (explicit) in target", "", string(cdiv1.DataVolumeKubeVirt), true),
Entry("true when using KubeVirt (explicit) in source and default in target", string(cdiv1.DataVolumeKubeVirt), "", true),
Entry("false when using default in source and archive in target", "", string(cdiv1.DataVolumeArchive), false),
Entry("false when using archive in source and default in target", string(cdiv1.DataVolumeArchive), "", false),
)
})
var _ = Describe("GetStorageClassByName", func() {
It("Should return the default storage class name", func() {
client := CreateClient(
CreateStorageClass("test-storage-class-1", nil),
CreateStorageClass("test-storage-class-2", map[string]string{
AnnDefaultStorageClass: "true",
}),
)
sc, _ := GetStorageClassByNameWithK8sFallback(context.Background(), client, nil)
Expect(sc.Name).To(Equal("test-storage-class-2"))
})
It("Should return nil if there's not default storage class", func() {
client := CreateClient(
CreateStorageClass("test-storage-class-1", nil),
CreateStorageClass("test-storage-class-2", nil),
)
sc, _ := GetStorageClassByNameWithK8sFallback(context.Background(), client, nil)
Expect(sc).To(BeNil())
})
It("Should return default virt class even if there's not default k8s storage class", func() {
client := CreateClient(
CreateStorageClass("test-storage-class-1", nil),
CreateStorageClass("test-storage-class-2", map[string]string{
AnnDefaultVirtStorageClass: "true",
}),
)
sc, _ := GetStorageClassByNameWithVirtFallback(context.Background(), client, nil, cdiv1.DataVolumeKubeVirt)
Expect(sc.Name).To(Equal("test-storage-class-2"))
})
DescribeTable("Should return newer default", func(annotation string) {
olderSc := CreateStorageClass("test-storage-class-new", map[string]string{
annotation: "true",
})
olderSc.SetCreationTimestamp(metav1.NewTime(time.Now().Add(-1 * time.Second)))
newerSc := CreateStorageClass("test-storage-class-old", map[string]string{
annotation: "true",
})
newerSc.SetCreationTimestamp(metav1.NewTime(time.Now()))
client := CreateClient(newerSc, olderSc)
sc, _ := GetStorageClassByNameWithVirtFallback(context.Background(), client, nil, cdiv1.DataVolumeKubeVirt)
Expect(sc.Name).To(Equal(newerSc.Name))
},
Entry("virt storage class", AnnDefaultVirtStorageClass),
Entry("k8s storage class", AnnDefaultStorageClass),
)
DescribeTable("Should fall back to lexicographic order when same timestamp", func(annotation string) {
firstSc := CreateStorageClass("test-storage-class-1", map[string]string{
annotation: "true",
})
firstSc.SetCreationTimestamp(metav1.NewTime(time.Now()))
secondSc := CreateStorageClass("test-storage-class-2", map[string]string{
annotation: "true",
})
secondSc.SetCreationTimestamp(metav1.NewTime(time.Now()))
client := CreateClient(firstSc, secondSc)
sc, _ := GetStorageClassByNameWithVirtFallback(context.Background(), client, nil, cdiv1.DataVolumeKubeVirt)
Expect(sc.Name).To(Equal(firstSc.Name))
},
Entry("virt storage class", AnnDefaultVirtStorageClass),
Entry("k8s storage class", AnnDefaultStorageClass),
)
})
var _ = Describe("Rebind", func() {
It("Should return error if PV doesn't exist", func() {
client := CreateClient()
pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "testPVC",
Namespace: "namespace",
},
Spec: v1.PersistentVolumeClaimSpec{
VolumeName: "testPV",
},
}
err := Rebind(context.Background(), client, pvc, pvc)
Expect(err).To(HaveOccurred())
Expect(errors.IsNotFound(err)).To(BeTrue())
})
It("Should return error if bound to unexpected claim", func() {
pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "testPVC",
Namespace: "namespace",
},
Spec: v1.PersistentVolumeClaimSpec{
VolumeName: "testPV",
},
}
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "testPV",
},
Spec: v1.PersistentVolumeSpec{
ClaimRef: &v1.ObjectReference{
Name: "anotherPVC",
Namespace: "namespace",
UID: "uid",
},
},
}
client := CreateClient(pv)
err := Rebind(context.Background(), client, pvc, pvc)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("PV testPV bound to unexpected claim anotherPVC"))
})
It("Should return nil if bound to target claim", func() {
pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "testPVC",
Namespace: "namespace",
},
Spec: v1.PersistentVolumeClaimSpec{
VolumeName: "testPV",
},
}
targetPVC := pvc.DeepCopy()
targetPVC.Name = "targetPVC"
targetPVC.UID = "uid"
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "testPV",
},
Spec: v1.PersistentVolumeSpec{
ClaimRef: &v1.ObjectReference{
Name: "targetPVC",
Namespace: "namespace",
UID: "uid",
},
},
}
client := CreateClient(pv)
err := Rebind(context.Background(), client, pvc, targetPVC)
Expect(err).ToNot(HaveOccurred())
})
It("Should rebind pv to target claim", func() {
pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "testPVC",
Namespace: "namespace",
},
Spec: v1.PersistentVolumeClaimSpec{
VolumeName: "testPV",
},
}
targetPVC := pvc.DeepCopy()
targetPVC.Name = "targetPVC"
pvc.UID = "uid"
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "testPV",
},
Spec: v1.PersistentVolumeSpec{
ClaimRef: &v1.ObjectReference{
Name: "testPVC",
Namespace: "namespace",
UID: "uid",
},
},
}
AddAnnotation(pv, "someAnno", "somevalue")
client := CreateClient(pv)
err := Rebind(context.Background(), client, pvc, targetPVC)
Expect(err).ToNot(HaveOccurred())
updatedPV := &v1.PersistentVolume{}
key := types.NamespacedName{Name: pv.Name, Namespace: pv.Namespace}
err = client.Get(context.TODO(), key, updatedPV)
Expect(err).ToNot(HaveOccurred())
Expect(updatedPV.Spec.ClaimRef.Name).To(Equal(targetPVC.Name))
//make sure annotations of pv from before rebind dont get deleted
Expect(pv.Annotations["someAnno"]).To(Equal("somevalue"))
})
Context("GetActiveCDI tests", func() {
createCDI := func(name string, phase sdkapi.Phase) *cdiv1.CDI {
return &cdiv1.CDI{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Status: cdiv1.CDIStatus{
Status: sdkapi.Status{
Phase: phase,
},
},
}
}
It("Should return nil if no CDI", func() {
client := CreateClient()
cdi, err := GetActiveCDI(context.Background(), client)
Expect(err).ToNot(HaveOccurred())
Expect(cdi).To(BeNil())
})
It("Should return single active", func() {
client := CreateClient(
createCDI("cdi1", sdkapi.PhaseDeployed),
)
cdi, err := GetActiveCDI(context.Background(), client)
Expect(err).ToNot(HaveOccurred())
Expect(cdi).ToNot(BeNil())
})
It("Should return success with single active one error", func() {
client := CreateClient(
createCDI("cdi1", sdkapi.PhaseDeployed),
createCDI("cdi2", sdkapi.PhaseError),
)
cdi, err := GetActiveCDI(context.Background(), client)
Expect(err).ToNot(HaveOccurred())
Expect(cdi).ToNot(BeNil())
Expect(cdi.Name).To(Equal("cdi1"))
})
It("Should return error if multiple CDIs are active", func() {
client := CreateClient(
createCDI("cdi1", sdkapi.PhaseDeployed),
createCDI("cdi2", sdkapi.PhaseDeployed),
)
cdi, err := GetActiveCDI(context.Background(), client)
Expect(err).To(HaveOccurred())
Expect(cdi).To(BeNil())
})
It("Should return error if multiple CDIs are error", func() {
client := CreateClient(
createCDI("cdi1", sdkapi.PhaseError),
createCDI("cdi2", sdkapi.PhaseError),
)
cdi, err := GetActiveCDI(context.Background(), client)
Expect(err).To(HaveOccurred())
Expect(cdi).To(BeNil())
})
})
})
var _ = Describe("GetMetricsURL", func() {
makePod := func(ip string, withMetrics bool) *v1.Pod {
pod := &v1.Pod{
Status: v1.PodStatus{
PodIP: ip,
},
}
if !withMetrics {
return pod
}
pod.Spec = v1.PodSpec{
Containers: []v1.Container{
{
Ports: []v1.ContainerPort{
{Name: "metrics", ContainerPort: 8080},
},
},
},
}
return pod
}
It("Should suceed with IPv4", func() {
pod := makePod("127.0.0.1", true)
url, err := GetMetricsURL(pod)
Expect(err).ToNot(HaveOccurred())
Expect(url).To(Equal("https://127.0.0.1:8080/metrics"))
})
It("Should suceed with IPv6", func() {
pod := makePod("::1", true)
url, err := GetMetricsURL(pod)
Expect(err).ToNot(HaveOccurred())
Expect(url).To(Equal("https://[::1]:8080/metrics"))
})
It("Should fail when there is no metrics port", func() {
pod := makePod("127.0.0.1", false)
_, err := GetMetricsURL(pod)
Expect(err).To(HaveOccurred())
})
})
func createPvcNoSize(name, ns string, annotations, labels map[string]string) *v1.PersistentVolumeClaim {
return &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns,
Annotations: annotations,
Labels: labels,
},
}
}