Skip to content

Commit

Permalink
Add unit tests to cover port-forward functionality in vmexport
Browse files Browse the repository at this point in the history
Signed-off-by: Alvaro Romero <alromero@redhat.com>
  • Loading branch information
alromeros committed Jul 27, 2023
1 parent c2cefd0 commit 015a3e9
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 10 deletions.
61 changes: 61 additions & 0 deletions pkg/virtctl/utils/test_utils.go
Expand Up @@ -143,6 +143,67 @@ func HandleSecretGet(k8sClient *fakek8sclient.Clientset, secretName string) {
})
}

func HandleServiceGet(k8sClient *fakek8sclient.Clientset, serviceName string, port int) {
service := &v1.Service{
ObjectMeta: k8smetav1.ObjectMeta{
Name: serviceName,
Namespace: k8smetav1.NamespaceDefault,
},
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{
{
Name: "export",
Port: int32(port),
},
},
},
}

k8sClient.Fake.PrependReactor("get", "services", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
get, ok := action.(testing.GetAction)
Expect(ok).To(BeTrue())
Expect(get.GetNamespace()).To(Equal(k8smetav1.NamespaceDefault))
Expect(get.GetName()).To(Equal(serviceName))
if service == nil {
return true, nil, errors.NewNotFound(v1.Resource("Service"), serviceName)
}
return true, service, nil
})
}

func HandlePodList(k8sClient *fakek8sclient.Clientset, podName string) {
podList := &v1.PodList{
ListMeta: k8smetav1.ListMeta{
ResourceVersion: "1",
},
Items: []v1.Pod{
{
ObjectMeta: k8smetav1.ObjectMeta{
Name: podName,
Namespace: k8smetav1.NamespaceDefault,
},
Spec: v1.PodSpec{},
},
},
}

k8sClient.Fake.PrependReactor("list", "pods", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
list, ok := action.(testing.ListAction)
Expect(ok).To(BeTrue())
Expect(list.GetNamespace()).To(Equal(k8smetav1.NamespaceDefault))
return true, podList, nil
})
}

func HandleVMExportDelete(client *kubevirtfake.Clientset, name string) {
client.Fake.PrependReactor("delete", "virtualmachineexports", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
delete, ok := action.(testing.DeleteAction)
Expect(ok).To(BeTrue())
Expect(delete.GetName()).To(Equal(name))
return true, nil, nil
})
}

func GetExportVolumeFormat(url string, format exportv1.ExportVolumeFormat) []exportv1.VirtualMachineExportVolumeFormat {
return []exportv1.VirtualMachineExportVolumeFormat{
{
Expand Down
24 changes: 23 additions & 1 deletion pkg/virtctl/vmexport/vmexport.go
Expand Up @@ -137,6 +137,8 @@ type exportFunc func(client kubecli.KubevirtClient, vmeInfo *VMExportInfo) error

type HTTPClientCreator func(*http.Transport, bool) *http.Client

type PortForwardFunc func(client kubecli.KubevirtClient, pod k8sv1.Pod, namespace string, ports []string, stopChan, readyChan chan struct{}) error

type exportCompleteFunc func(kubecli.KubevirtClient, *VMExportInfo, time.Duration, time.Duration) error

// ExportProcessingComplete is used to store the function to wait for the export object to be ready.
Expand Down Expand Up @@ -170,6 +172,8 @@ var exportFunction exportFunc

var httpClientCreatorFunc HTTPClientCreator

var startPortForward PortForwardFunc

// SetHTTPClientCreator allows overriding the default http client (useful for unit testing)
func SetHTTPClientCreator(f HTTPClientCreator) {
httpClientCreatorFunc = f
Expand All @@ -180,8 +184,19 @@ func SetDefaultHTTPClientCreator() {
httpClientCreatorFunc = getHTTPClient
}

// SetPortForwarder allows overriding the default port-forwarder (useful for unit testing)
func SetPortForwarder(f PortForwardFunc) {
startPortForward = f
}

// SetDefaultPortForwarder sets the port forwarder back to default
func SetDefaultPortForwarder() {
startPortForward = runPortForward
}

func init() {
SetDefaultHTTPClientCreator()
SetDefaultPortForwarder()
}

// usage provides several valid usage examples of vmexport
Expand Down Expand Up @@ -868,6 +883,13 @@ func handleDownloadFlags() error {
shouldCreate = true
}

if portForward != "" {
port, err := strconv.Atoi(portForward)
if err != nil || port < 0 || port > 65535 {
return fmt.Errorf(ErrInvalidValue, PORT_FORWARD_FLAG, "valid port numbers")
}
}

if exportManifest {
if volumeName != "" {
return fmt.Errorf(ErrIncompatibleFlag, VOLUME_FLAG, MANIFEST_FLAG)
Expand Down Expand Up @@ -1003,7 +1025,7 @@ func setupPortForward(client kubecli.KubevirtClient, vmeInfo *VMExportInfo) (cha

stopChan := make(chan struct{}, 1)
readyChan := make(chan struct{})
go runPortForward(client, podList.Items[0], vmeInfo.Namespace, ports, stopChan, readyChan)
go startPortForward(client, podList.Items[0], vmeInfo.Namespace, ports, stopChan, readyChan)

// Wait for the port forwarding to be ready
select {
Expand Down
97 changes: 88 additions & 9 deletions pkg/virtctl/vmexport/vmexport_test.go
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
k8sv1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -51,6 +52,7 @@ var _ = Describe("vmexport", func() {

kubeClient = fakek8sclient.NewSimpleClientset()
vmExportClient = kubevirtfake.NewSimpleClientset()

})

setflag := func(flag, parameter string) string {
Expand Down Expand Up @@ -91,10 +93,15 @@ var _ = Describe("vmexport", func() {
virtctlvmexport.SetHTTPClientCreator(func(*http.Transport, bool) *http.Client {
return server.Client()
})
virtctlvmexport.SetPortForwarder(func(client kubecli.KubevirtClient, pod k8sv1.Pod, namespace string, ports []string, stopChan, readyChan chan struct{}) error {
readyChan <- struct{}{}
return nil
})
}

testDone := func() {
virtctlvmexport.SetDefaultHTTPClientCreator()
virtctlvmexport.SetDefaultPortForwarder()
server.Close()
}

Expand Down Expand Up @@ -281,6 +288,7 @@ var _ = Describe("vmexport", func() {
Entry("Using 'manifest' with pvc flag", fmt.Sprintf(virtctlvmexport.ErrIncompatibleFlag, virtctlvmexport.PVC_FLAG, virtctlvmexport.MANIFEST_FLAG), virtctlvmexport.DOWNLOAD, vmexportName, virtctlvmexport.MANIFEST_FLAG, setflag(virtctlvmexport.PVC_FLAG, "test")),
Entry("Using 'manifest' with volume type", fmt.Sprintf(virtctlvmexport.ErrIncompatibleFlag, virtctlvmexport.VOLUME_FLAG, virtctlvmexport.MANIFEST_FLAG), virtctlvmexport.DOWNLOAD, vmexportName, virtctlvmexport.MANIFEST_FLAG, setflag(virtctlvmexport.VM_FLAG, "test"), setflag(virtctlvmexport.VOLUME_FLAG, "volume")),
Entry("Using 'manifest' with invalid output_format_flag", fmt.Sprintf(virtctlvmexport.ErrInvalidValue, virtctlvmexport.OUTPUT_FORMAT_FLAG, "json/yaml"), virtctlvmexport.DOWNLOAD, vmexportName, virtctlvmexport.MANIFEST_FLAG, setflag(virtctlvmexport.OUTPUT_FORMAT_FLAG, "invalid")),
Entry("Using 'port-forward' with invalid port", fmt.Sprintf(virtctlvmexport.ErrInvalidValue, virtctlvmexport.PORT_FORWARD_FLAG, "valid port numbers"), virtctlvmexport.DOWNLOAD, vmexportName, virtctlvmexport.PORT_FORWARD_FLAG, setflag(virtctlvmexport.PORT_FORWARD_FLAG, "test")),
)

AfterEach(func() {
Expand All @@ -303,7 +311,7 @@ var _ = Describe("vmexport", func() {

// Delete tests
It("VirtualMachineExport is deleted succesfully", func() {
handleVMExportDelete(vmExportClient, vmexportName)
utils.HandleVMExportDelete(vmExportClient, vmexportName)
cmd := clientcmd.NewRepeatableVirtctlCommand(commandName, virtctlvmexport.DELETE, vmexportName)
err := cmd()
Expect(err).ToNot(HaveOccurred())
Expand Down Expand Up @@ -642,13 +650,84 @@ var _ = Describe("vmexport", func() {
Expect(err).To(HaveOccurred())
})
})
})

func handleVMExportDelete(client *kubevirtfake.Clientset, name string) {
client.Fake.PrependReactor("delete", "virtualmachineexports", func(action testing.Action) (handled bool, obj runtime.Object, err error) {
delete, ok := action.(testing.DeleteAction)
Expect(ok).To(BeTrue())
Expect(delete.GetName()).To(Equal(name))
return true, nil, nil
Context("Port-forward", func() {
var (
orgHttpFunc virtctlvmexport.HandleHTTPRequestFunc
)

BeforeEach(func() {
orgHttpFunc = virtctlvmexport.HandleHTTPRequest
testInit(http.StatusOK)
})

AfterEach(func() {
virtctlvmexport.HandleHTTPRequest = orgHttpFunc
testDone()
})

It("VirtualMachineExport download fails when using port-forward with an invalid port", func() {
vme := utils.VMExportSpecPVC(vmexportName, metav1.NamespaceDefault, "test-pvc", secretName)
vme.Status = utils.GetVMEStatus([]exportv1.VirtualMachineExportVolume{
{
Name: "no-test-volume",
Formats: utils.GetExportVolumeFormat(server.URL, exportv1.KubeVirtRaw),
},
}, secretName)
utils.HandleSecretGet(kubeClient, secretName)
utils.HandleVMExportGet(vmExportClient, vme, vmexportName)
utils.HandleServiceGet(kubeClient, fmt.Sprintf("virt-export-%s", vme.Name), 321)
utils.HandlePodList(kubeClient, fmt.Sprintf("virt-export-pod-%s", vme.Name))

cmd := clientcmd.NewRepeatableVirtctlCommand(commandName, virtctlvmexport.DOWNLOAD, vmexportName, setflag(virtctlvmexport.PORT_FORWARD_FLAG, "5432"), setflag(virtctlvmexport.OUTPUT_FLAG, "disk.img"))
err := cmd()
Expect(err).To(HaveOccurred())
Expect(err.Error()).Should(Equal("Service virt-export-test-vme does not have a service port 443"))
})

It("VirtualMachineExport download with port-forward fails when the service doesn't have a valid pod ", func() {
vme := utils.VMExportSpecPVC(vmexportName, metav1.NamespaceDefault, "test-pvc", secretName)
vme.Status = utils.GetVMEStatus([]exportv1.VirtualMachineExportVolume{
{
Name: "no-test-volume",
Formats: utils.GetExportVolumeFormat(server.URL, exportv1.KubeVirtRaw),
},
}, secretName)
utils.HandleSecretGet(kubeClient, secretName)
utils.HandleVMExportGet(vmExportClient, vme, vmexportName)
utils.HandleServiceGet(kubeClient, fmt.Sprintf("virt-export-%s", vme.Name), 443)

cmd := clientcmd.NewRepeatableVirtctlCommand(commandName, virtctlvmexport.DOWNLOAD, vmexportName, setflag(virtctlvmexport.PORT_FORWARD_FLAG, "5432"), setflag(virtctlvmexport.OUTPUT_FLAG, "disk.img"))
err := cmd()
Expect(err).To(HaveOccurred())
Expect(err.Error()).Should(Equal("No pods found for the service virt-export-test-vme"))
})

It("VirtualMachineExport download with port-forward succeeds", func() {
virtctlvmexport.HandleHTTPRequest = func(client kubecli.KubevirtClient, vmexport *exportv1.VirtualMachineExport, downloadUrl string, insecure bool, exportURL string, headers map[string]string) (*http.Response, error) {
Expect(downloadUrl).To(Equal("https://127.0.0.1:5432"))
resp := http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader("data")),
}
return &resp, nil
}
vme := utils.VMExportSpecPVC(vmexportName, metav1.NamespaceDefault, "test-pvc", secretName)
vme.Status = utils.GetVMEStatus([]exportv1.VirtualMachineExportVolume{
{
Name: volumeName,
Formats: utils.GetExportVolumeFormat(server.URL, exportv1.KubeVirtRaw),
},
}, secretName)
vme.Status.Links.Internal = vme.Status.Links.External
utils.HandleSecretGet(kubeClient, secretName)
utils.HandleVMExportGet(vmExportClient, vme, vmexportName)
utils.HandleServiceGet(kubeClient, fmt.Sprintf("virt-export-%s", vme.Name), 443)
utils.HandlePodList(kubeClient, fmt.Sprintf("virt-export-pod-%s", vme.Name))

cmd := clientcmd.NewRepeatableVirtctlCommand(commandName, virtctlvmexport.DOWNLOAD, vmexportName, setflag(virtctlvmexport.VOLUME_FLAG, volumeName), setflag(virtctlvmexport.PORT_FORWARD_FLAG, "5432"), setflag(virtctlvmexport.OUTPUT_FLAG, "disk.img"))
err := cmd()
Expect(err).ToNot(HaveOccurred())
})
})
}
})

0 comments on commit 015a3e9

Please sign in to comment.