From 14726f1a5320f905c713880875bddcd1102d9011 Mon Sep 17 00:00:00 2001 From: Janet Kuo Date: Thu, 11 Aug 2016 17:06:28 -0700 Subject: [PATCH] Add unit test for komposeConvert --- cli/app/app_test.go | 1 + pkg/kobject/kobject.go | 13 +- pkg/loader/bundle/bundle.go | 10 +- pkg/loader/compose/compose.go | 4 +- pkg/transformer/kubernetes/kubernetes.go | 22 +- pkg/transformer/kubernetes/kubernetes_test.go | 245 ++++++++++++++++++ 6 files changed, 259 insertions(+), 36 deletions(-) create mode 100644 pkg/transformer/kubernetes/kubernetes_test.go diff --git a/cli/app/app_test.go b/cli/app/app_test.go index b19aed54..bf64ae2c 100644 --- a/cli/app/app_test.go +++ b/cli/app/app_test.go @@ -19,6 +19,7 @@ package app import ( "fmt" "testing" + "github.com/skippbox/kompose/pkg/transformer" ) diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index b3ae3e31..f69159d5 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -19,6 +19,7 @@ package kobject import ( "github.com/Sirupsen/logrus" "github.com/fatih/structs" + "k8s.io/kubernetes/pkg/api" ) var unsupportedKey = map[string]int{ @@ -162,19 +163,9 @@ type EnvVar struct { type Ports struct { HostPort int32 ContainerPort int32 - Protocol Protocol + Protocol api.Protocol } -// Protocol defines network protocols supported for things like container ports. -type Protocol string - -const ( - // ProtocolTCP is the TCP protocol. - ProtocolTCP Protocol = "TCP" - // ProtocolUDP is the UDP protocol. - ProtocolUDP Protocol = "UDP" -) - func CheckUnsupportedKey(service interface{}) { s := structs.New(service) for _, f := range s.Fields() { diff --git a/pkg/loader/bundle/bundle.go b/pkg/loader/bundle/bundle.go index 4a4c684e..3f621955 100644 --- a/pkg/loader/bundle/bundle.go +++ b/pkg/loader/bundle/bundle.go @@ -20,6 +20,8 @@ import ( "io/ioutil" "strings" + "k8s.io/kubernetes/pkg/api" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client/bundlefile" "github.com/skippbox/kompose/pkg/kobject" @@ -78,14 +80,14 @@ func loadEnvVarsfromBundle(service bundlefile.Service) ([]kobject.EnvVar, string func loadPortsfromBundle(service bundlefile.Service) ([]kobject.Ports, string) { ports := []kobject.Ports{} for _, port := range service.Ports { - var p kobject.Protocol + var p api.Protocol switch port.Protocol { default: - p = kobject.ProtocolTCP + p = api.ProtocolTCP case "TCP": - p = kobject.ProtocolTCP + p = api.ProtocolTCP case "UDP": - p = kobject.ProtocolUDP + p = api.ProtocolUDP } ports = append(ports, kobject.Ports{ HostPort: int32(port.Port), diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go index d7d45eda..23fdc496 100644 --- a/pkg/loader/compose/compose.go +++ b/pkg/loader/compose/compose.go @@ -22,6 +22,8 @@ import ( "strconv" "strings" + "k8s.io/kubernetes/pkg/api" + "github.com/Sirupsen/logrus" "github.com/docker/libcompose/config" "github.com/docker/libcompose/docker" @@ -50,7 +52,7 @@ func loadPortsFromCompose(composePorts []string) ([]kobject.Ports, string) { ports := []kobject.Ports{} character := ":" for _, port := range composePorts { - p := kobject.ProtocolTCP + p := api.ProtocolTCP if strings.Contains(port, character) { hostPort := port[0:strings.Index(port, character)] hostPort = strings.TrimSpace(hostPort) diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index a42b1cd4..677cb262 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -154,18 +154,9 @@ func InitDS(name string, service kobject.ServiceConfig) *extensions.DaemonSet { func ConfigPorts(name string, service kobject.ServiceConfig) []api.ContainerPort { ports := []api.ContainerPort{} for _, port := range service.Port { - var p api.Protocol - switch port.Protocol { - default: - p = api.ProtocolTCP - case kobject.ProtocolTCP: - p = api.ProtocolTCP - case kobject.ProtocolUDP: - p = api.ProtocolUDP - } ports = append(ports, api.ContainerPort{ ContainerPort: port.ContainerPort, - Protocol: p, + Protocol: port.Protocol, }) } @@ -179,21 +170,12 @@ func ConfigServicePorts(name string, service kobject.ServiceConfig) []api.Servic if port.HostPort == 0 { port.HostPort = port.ContainerPort } - var p api.Protocol - switch port.Protocol { - default: - p = api.ProtocolTCP - case kobject.ProtocolTCP: - p = api.ProtocolTCP - case kobject.ProtocolUDP: - p = api.ProtocolUDP - } var targetPort intstr.IntOrString targetPort.IntVal = port.ContainerPort targetPort.StrVal = strconv.Itoa(int(port.ContainerPort)) servicePorts = append(servicePorts, api.ServicePort{ Name: strconv.Itoa(int(port.HostPort)), - Protocol: p, + Protocol: port.Protocol, Port: port.HostPort, TargetPort: targetPort, }) diff --git a/pkg/transformer/kubernetes/kubernetes_test.go b/pkg/transformer/kubernetes/kubernetes_test.go new file mode 100644 index 00000000..106d7f40 --- /dev/null +++ b/pkg/transformer/kubernetes/kubernetes_test.go @@ -0,0 +1,245 @@ +/* +Copyright 2016 Skippbox, Ltd All rights reserved. + +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. +*/ + +package kubernetes + +import ( + "fmt" + "testing" + + deployapi "github.com/openshift/origin/pkg/deploy/api" + + "github.com/skippbox/kompose/pkg/kobject" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func newServiceConfig() kobject.ServiceConfig { + return kobject.ServiceConfig{ + ContainerName: "name", + Image: "image", + Environment: []kobject.EnvVar{kobject.EnvVar{Name: "env", Value: "value"}}, + Port: []kobject.Ports{kobject.Ports{HostPort: 123, ContainerPort: 456, Protocol: api.ProtocolTCP}}, + Command: []string{"cmd"}, + WorkingDir: "dir", + Args: []string{"arg1", "arg2"}, + Volumes: []string{"/tmp/volume"}, + Network: []string{"network1", "network2"}, + Labels: map[string]string{"service": "app"}, + Annotations: map[string]string{"abc": "def"}, + CPUSet: "cpu_set", + CPUShares: 1, + CPUQuota: 1, + CapAdd: []string{"cap_add"}, + CapDrop: []string{"cap_drop"}, + Entrypoint: []string{"entrypoint"}, + Expose: []string{"expose"}, + Privileged: true, + Restart: "always", + User: "user", + } +} + +func newKomposeObject() kobject.KomposeObject { + return kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfig()}, + } +} + +func equalStringSlice(s1, s2 []string) bool { + if len(s1) != len(s2) { + return false + } + for i := range s1 { + if s1[i] != s1[i] { + return false + } + } + return true +} + +func equalEnv(kEnvs []kobject.EnvVar, k8sEnvs []api.EnvVar) bool { + if len(kEnvs) != len(k8sEnvs) { + return false + } + for _, kEnv := range kEnvs { + found := false + for _, k8sEnv := range k8sEnvs { + if kEnv.Name == k8sEnv.Name && kEnv.Value == k8sEnv.Value { + found = true + } + } + if !found { + return false + } + } + return true +} + +func equalPorts(kPorts []kobject.Ports, k8sPorts []api.ContainerPort) bool { + if len(kPorts) != len(k8sPorts) { + return false + } + for _, kPort := range kPorts { + found := false + for _, k8sPort := range k8sPorts { + // FIXME: HostPort should be copied to container port + //if kPort.HostPort == k8sPort.HostPort && kPort.Protocol == k8sPort.Protocol && kPort.ContainerPort == k8sPort.ContainerPort { + if kPort.Protocol == k8sPort.Protocol && kPort.ContainerPort == k8sPort.ContainerPort { + found = true + } + // Name and HostIp shouldn't be set + if len(k8sPort.Name) != 0 || len(k8sPort.HostIP) != 0 { + return false + } + } + if !found { + return false + } + } + return true +} + +func checkPodTemplate(config kobject.ServiceConfig, template api.PodTemplateSpec) error { + container := template.Spec.Containers[0] + // FIXME: we should support container name and uncomment this test + //if config.ContainerName != container.Name { + //return fmt.Errorf("Found different container name: %v vs. %v", config.ContainerName, container.Name) + //} + if config.Image != container.Image { + return fmt.Errorf("Found different container image: %v vs. %v", config.Image, container.Image) + } + if !equalEnv(config.Environment, container.Env) { + return fmt.Errorf("Found different container env: %#v vs. %#v", config.Environment, container.Env) + } + if !equalPorts(config.Port, container.Ports) { + return fmt.Errorf("Found different container ports: %#v vs. %#v", config.Port, container.Ports) + } + if !equalStringSlice(config.Command, container.Command) { + return fmt.Errorf("Found different container cmd: %#v vs. %#v", config.Command, container.Command) + } + if config.WorkingDir != container.WorkingDir { + return fmt.Errorf("Found different container WorkingDir: %#v vs. %#v", config.WorkingDir, container.WorkingDir) + } + // FIXME: we should support container args and uncomment this test + //if !equalStringSlice(config.Args, container.Args) { + //return fmt.Errorf("Found different container args: %#v vs. %#v", config.Args, container.Args) + //} + if len(template.Spec.Volumes) == 0 || len(template.Spec.Volumes[0].Name) == 0 || + (template.Spec.Volumes[0].VolumeSource.HostPath == nil && template.Spec.Volumes[0].VolumeSource.EmptyDir == nil) { + return fmt.Errorf("Found incorrect volumes: %v vs. %#v", config.Volumes, template.Spec.Volumes) + } + restartPolicyMapping := map[string]api.RestartPolicy{"always": api.RestartPolicyAlways} + if restartPolicyMapping[config.Restart] != template.Spec.RestartPolicy { + return fmt.Errorf("Found incorrect restart policy: %v vs. %v", config.Restart, template.Spec.RestartPolicy) + } + if len(template.Labels) != 1 || len(template.Labels["service"]) == 0 { + return fmt.Errorf("Found incorrect labels: %#v", template.Labels) + } + // TODO: finish this + return nil +} + +func checkService(config kobject.ServiceConfig, svc *api.Service) error { + // TODO: finish this + return nil +} + +func TestKomposeConvert(t *testing.T) { + replicas := 3 + testCases := map[string]struct { + komposeObject kobject.KomposeObject + opt kobject.ConvertOptions + expectedNumObjs int + }{ + "Convert to Deployments": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, Replicas: replicas}, 2}, + // TODO: add more tests + } + for name, test := range testCases { + t.Log("Test case:", name) + k := Kubernetes{} + objs := k.Transform(test.komposeObject, test.opt) + if len(objs) != test.expectedNumObjs { + t.Errorf("Expected %d objects returned, got %d", test.expectedNumObjs, len(objs)) + } + var foundSVC, foundD, foundDS, foundRC, foundDC bool + for _, obj := range objs { + if svc, ok := obj.(*api.Service); ok { + if err := checkService(test.komposeObject.ServiceConfigs["app"], svc); err != nil { + t.Errorf("%v", err) + } + foundSVC = true + } + if test.opt.CreateD { + if d, ok := obj.(*extensions.Deployment); ok { + if err := checkPodTemplate(test.komposeObject.ServiceConfigs["app"], d.Spec.Template); err != nil { + t.Errorf("%v", err) + } + if (int)(d.Spec.Replicas) != replicas { + t.Errorf("Expected %d replicas, got %d", replicas, d.Spec.Replicas) + } + foundD = true + } + } + if test.opt.CreateDS { + if ds, ok := obj.(*extensions.DaemonSet); ok { + if err := checkPodTemplate(test.komposeObject.ServiceConfigs["app"], ds.Spec.Template); err != nil { + t.Errorf("%v", err) + } + foundDS = true + } + } + if test.opt.CreateRC { + if rc, ok := obj.(*api.ReplicationController); ok { + if err := checkPodTemplate(test.komposeObject.ServiceConfigs["app"], *rc.Spec.Template); err != nil { + t.Errorf("%v", err) + } + if (int)(rc.Spec.Replicas) != replicas { + t.Errorf("Expected %d replicas, got %d", replicas, rc.Spec.Replicas) + } + foundRC = true + } + } + if test.opt.CreateDeploymentConfig { + if dc, ok := obj.(*deployapi.DeploymentConfig); ok { + if err := checkPodTemplate(test.komposeObject.ServiceConfigs["app"], *dc.Spec.Template); err != nil { + t.Errorf("%v", err) + } + if (int)(dc.Spec.Replicas) != replicas { + t.Errorf("Expected %d replicas, got %d", replicas, dc.Spec.Replicas) + } + foundDC = true + } + } + } + if !foundSVC { + t.Errorf("Unexpected Service not created") + } + if test.opt.CreateD != foundD { + t.Errorf("Expected create Deployment: %v, found Deployment: %v", test.opt.CreateD, foundD) + } + if test.opt.CreateDS != foundDS { + t.Errorf("Expected create Daemon Set: %v, found Daemon Set: %v", test.opt.CreateDS, foundDS) + } + if test.opt.CreateRC != foundRC { + t.Errorf("Expected create Replication Controller: %v, found Replication Controller: %v", test.opt.CreateRC, foundRC) + } + if test.opt.CreateDeploymentConfig != foundDC { + t.Errorf("Expected create Deployment Config: %v, found Deployment Config: %v", test.opt.CreateDeploymentConfig, foundDC) + } + } +}