Skip to content

Commit

Permalink
refactoring and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mhenriks committed Oct 22, 2018
1 parent f167234 commit 91917a0
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package uploadimage
package imageupload

import (
"crypto/tls"
Expand All @@ -8,21 +8,17 @@ import (
"os"
"time"

"k8s.io/apimachinery/pkg/util/wait"

"github.com/spf13/cobra"

"k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"

uploadcdiv1 "kubevirt.io/containerized-data-importer/pkg/apis/uploadcontroller/v1alpha1"
cdiClientset "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned"

"kubevirt.io/kubevirt/pkg/kubecli"
"kubevirt.io/kubevirt/pkg/virtctl/templates"
)
Expand All @@ -49,6 +45,26 @@ var (
noCreate = false
)

// HTTPClientCreator is a function that creates http clients
type HTTPClientCreator func(bool) *http.Client

var httpClientCreatorFunc HTTPClientCreator

// SetHTTPClientCreator allows overriding the default http client
// useful for unit tests
func SetHTTPClientCreator(f HTTPClientCreator) {
httpClientCreatorFunc = f
}

// SetDefaultHTTPClientCreator sets the http client creator back to default
func SetDefaultHTTPClientCreator() {
httpClientCreatorFunc = getHTTPClient
}

func init() {
SetDefaultHTTPClientCreator()
}

// NewImageUploadCommand returns a comra.Command for handling the the uploading of VM images
func NewImageUploadCommand(clientConfig clientcmd.ClientConfig) *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -140,9 +156,7 @@ func (c *command) run(cmd *cobra.Command, args []string) error {
return nil
}

func uploadData(uploadProxyURL, token string, reader io.Reader, insecure bool) error {
url := uploadProxyURL + uploadProxyURI
req, _ := http.NewRequest("POST", url, reader)
func getHTTPClient(insecure bool) *http.Client {
client := &http.Client{}

if insecure {
Expand All @@ -151,6 +165,14 @@ func uploadData(uploadProxyURL, token string, reader io.Reader, insecure bool) e
}
}

return client
}

func uploadData(uploadProxyURL, token string, reader io.Reader, insecure bool) error {
url := uploadProxyURL + uploadProxyURI
req, _ := http.NewRequest("POST", url, reader)
client := httpClientCreatorFunc(insecure)

req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("Content-Type", "application/octet-stream")

Expand Down Expand Up @@ -218,7 +240,8 @@ func createUploadPVC(client kubernetes.Interface, namespace, name, size, storage

pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Name: name,
Namespace: namespace,
Annotations: map[string]string{
uploadRequestAnnotation: "",
},
Expand Down
13 changes: 13 additions & 0 deletions pkg/virtctl/imageupload/imageupload_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package imageupload_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestImageUpload(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "ImageUpload Suite")
}
244 changes: 244 additions & 0 deletions pkg/virtctl/imageupload/imageupload_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package imageupload_test

import (
"fmt"
"net/http"
"net/http/httptest"
"time"

"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
fakek8sclient "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/testing"

fakecdiclient "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned/fake"
"kubevirt.io/kubevirt/pkg/kubecli"
"kubevirt.io/kubevirt/pkg/virtctl/imageupload"
"kubevirt.io/kubevirt/tests"
)

const (
commandName = "image-upload"
uploadRequestAnnotation = "cdi.kubevirt.io/storage.upload.target"
podPhaseAnnotation = "cdi.kubevirt.io/storage.pod.phase"
)

var _ = Describe("ImageUpload", func() {

const (
pvcNamespace = "default"
pvcName = "test-pvc"
pvcSize = "500Mi"
imagePath = "../../../vendor/kubevirt.io/containerized-data-importer/tests/images/cirros-qcow2.img"
)

var (
ctrl *gomock.Controller
kubeClient *fakek8sclient.Clientset
cdiClient *fakecdiclient.Clientset
server *httptest.Server

createCalled bool
updateCalled bool
)

BeforeEach(func() {
ctrl = gomock.NewController(GinkgoT())
kubecli.GetKubevirtClientFromClientConfig = kubecli.GetMockKubevirtClientFromClientConfig
kubecli.MockKubevirtClientInstance = kubecli.NewMockKubevirtClient(ctrl)
})

AfterEach(func() {
ctrl.Finish()
})

addPodPhaseAnnotation := func() {
time.Sleep(10 * time.Millisecond)
pvc, err := kubeClient.CoreV1().PersistentVolumeClaims(pvcNamespace).Get(pvcName, metav1.GetOptions{})
Expect(err).To(BeNil())
pvc.Annotations[podPhaseAnnotation] = "Running"
pvc, err = kubeClient.CoreV1().PersistentVolumeClaims(pvcNamespace).Update(pvc)
if err != nil {
fmt.Fprintf(GinkgoWriter, "Error: %v\n", err)
}
Expect(err).To(BeNil())
}

addReactors := func() {
kubeClient.Fake.PrependReactor("create", "persistentvolumeclaims", func(action testing.Action) (bool, runtime.Object, error) {
create, ok := action.(testing.CreateAction)
Expect(ok).To(BeTrue())

pvc, ok := create.GetObject().(*v1.PersistentVolumeClaim)
Expect(ok).To(BeTrue())
Expect(pvc.Name).To(Equal(pvcName))

Expect(createCalled).To(BeFalse())
createCalled = true

go addPodPhaseAnnotation()

return false, nil, nil
})

kubeClient.Fake.PrependReactor("update", "persistentvolumeclaims", func(action testing.Action) (bool, runtime.Object, error) {
update, ok := action.(testing.UpdateAction)
Expect(ok).To(BeTrue())

pvc, ok := update.GetObject().(*v1.PersistentVolumeClaim)
Expect(ok).To(BeTrue())
Expect(pvc.Name).To(Equal(pvcName))

if !createCalled && !updateCalled {
go addPodPhaseAnnotation()
}

updateCalled = true

return false, nil, nil
})
}

validatePVC := func() {
pvc, err := kubeClient.CoreV1().PersistentVolumeClaims(pvcNamespace).Get(pvcName, metav1.GetOptions{})
Expect(err).To(BeNil())

resource, ok := pvc.Spec.Resources.Requests[v1.ResourceStorage]
Expect(ok).To(BeTrue())
Expect(resource.String()).To(Equal(pvcSize))

_, ok = pvc.Annotations[uploadRequestAnnotation]
Expect(ok).To(BeTrue())
}

testInit := func(statusCode int, kubeobjects ...runtime.Object) {
createCalled = false
updateCalled = false

kubeClient = fakek8sclient.NewSimpleClientset(kubeobjects...)
cdiClient = fakecdiclient.NewSimpleClientset()

kubecli.MockKubevirtClientInstance.EXPECT().CoreV1().Return(kubeClient.CoreV1()).AnyTimes()
kubecli.MockKubevirtClientInstance.EXPECT().CdiClient().Return(cdiClient).AnyTimes()

addReactors()

server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode)
}))
imageupload.SetHTTPClientCreator(func(bool) *http.Client {
return server.Client()
})
}

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

pvcSpec := func() *v1.PersistentVolumeClaim {
quantity, _ := resource.ParseQuantity(pvcSize)

pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: pvcName,
Namespace: "default",
Annotations: map[string]string{},
},
Spec: v1.PersistentVolumeClaimSpec{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: quantity,
},
},
},
}

return pvc
}

pvcSpecWithUploadAnnotation := func() *v1.PersistentVolumeClaim {
spec := pvcSpec()
spec.Annotations = map[string]string{
uploadRequestAnnotation: "",
podPhaseAnnotation: "Running",
}
return spec
}

pvcSpecWithUploadSucceeded := func() *v1.PersistentVolumeClaim {
spec := pvcSpec()
spec.Annotations = map[string]string{
uploadRequestAnnotation: "",
podPhaseAnnotation: "Succeeded",
}
return spec
}

Context("Successful upload to PVC", func() {
It("PVC does not exist", func() {
testInit(http.StatusOK)
cmd := tests.NewRepeatableVirtctlCommand(commandName, "--pvc-name", pvcName, "--pvc-size", pvcSize,
"--uploadproxy-url", server.URL, "--insecure", "--image-path", imagePath)
Expect(cmd()).To(BeNil())
Expect(createCalled).To(BeTrue())
validatePVC()
})

DescribeTable("PVC does exist", func(pvc *v1.PersistentVolumeClaim) {
testInit(http.StatusOK, pvc)
cmd := tests.NewRepeatableVirtctlCommand(commandName, "--no-create", "--pvc-name", pvcName,
"--uploadproxy-url", server.URL, "--insecure", "--image-path", imagePath)
Expect(cmd()).To(BeNil())
Expect(createCalled).To(BeFalse())
validatePVC()
},
Entry("PVC with upload annotation", pvcSpecWithUploadAnnotation()),
Entry("PVC without upload annotation", pvcSpec()),
)

AfterEach(func() {
testDone()
})
})

Context("Upload fails", func() {
It("PVC already uploaded", func() {
testInit(http.StatusOK, pvcSpecWithUploadSucceeded())
cmd := tests.NewRepeatableVirtctlCommand(commandName, "--pvc-name", pvcName, "--pvc-size", pvcSize,
"--uploadproxy-url", server.URL, "--insecure", "--image-path", imagePath)
Expect(cmd()).NotTo(BeNil())
})

It("Upload fails", func() {
testInit(http.StatusInternalServerError)
cmd := tests.NewRepeatableVirtctlCommand(commandName, "--pvc-name", pvcName, "--pvc-size", pvcSize,
"--uploadproxy-url", server.URL, "--insecure", "--image-path", imagePath)
Expect(cmd()).NotTo(BeNil())
})

DescribeTable("Bad args", func(args []string) {
testInit(http.StatusOK)
args = append([]string{commandName}, args...)
cmd := tests.NewRepeatableVirtctlCommand(args...)
Expect(cmd()).NotTo(BeNil())
},
Entry("No args", []string{}),
Entry("No args", []string{"--pvc-name", pvcName, "--pvc-size", pvcSize, "--uploadproxy-url", "https://doesnotexist", "--insecure", "--image-path", imagePath}),
Entry("No name", []string{"--pvc-size", pvcSize, "--uploadproxy-url", "https://doesnotexist", "--insecure", "--image-path", imagePath}),
Entry("No size", []string{"--pvc-name", pvcName, "--uploadproxy-url", "https://doesnotexist", "--insecure", "--image-path", imagePath}),
Entry("No url", []string{"--pvc-name", pvcName, "--pvc-size", pvcSize, "--insecure", "--image-path", imagePath}),
Entry("No image path", []string{"--pvc-name", pvcName, "--pvc-size", pvcSize, "--uploadproxy-url", "https://doesnotexist", "--insecure"}),
)

AfterEach(func() {
testDone()
})
})
})
4 changes: 2 additions & 2 deletions pkg/virtctl/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
"kubevirt.io/kubevirt/pkg/log"
"kubevirt.io/kubevirt/pkg/virtctl/console"
"kubevirt.io/kubevirt/pkg/virtctl/expose"
"kubevirt.io/kubevirt/pkg/virtctl/imageupload"
"kubevirt.io/kubevirt/pkg/virtctl/templates"
"kubevirt.io/kubevirt/pkg/virtctl/uploadimage"
"kubevirt.io/kubevirt/pkg/virtctl/version"
"kubevirt.io/kubevirt/pkg/virtctl/vm"
"kubevirt.io/kubevirt/pkg/virtctl/vnc"
Expand Down Expand Up @@ -48,7 +48,7 @@ func NewVirtctlCommand() *cobra.Command {
vm.NewStopCommand(clientConfig),
expose.NewExposeCommand(clientConfig),
version.VersionCommand(clientConfig),
uploadimage.NewImageUploadCommand(clientConfig),
imageupload.NewImageUploadCommand(clientConfig),
optionsCmd,
)
return rootCmd
Expand Down

0 comments on commit 91917a0

Please sign in to comment.