From 858314e06b659ae851aa7405cf554ed53410f6ef Mon Sep 17 00:00:00 2001 From: Charlie Drage Date: Wed, 22 Feb 2017 08:19:27 -0500 Subject: [PATCH] Adds mem_limit support for conversion This commit adds mem_limit support. Taking the value from docker-compose.yaml and converting it to it's associative value in Kubernetes artifacts. Closes (half) of https://github.com/kubernetes-incubator/kompose/issues/267 --- pkg/kobject/kobject.go | 56 +++++++------ pkg/loader/compose/compose.go | 2 +- pkg/transformer/kubernetes/k8sutils.go | 12 +++ pkg/transformer/kubernetes/k8sutils_test.go | 48 +++++++++++ script/test/cmd/tests.sh | 10 +++ .../fixtures/mem-limit/docker-compose-mb.yml | 11 +++ .../fixtures/mem-limit/docker-compose.yml | 11 +++ .../test/fixtures/mem-limit/output-k8s.json | 83 +++++++++++++++++++ .../fixtures/mem-limit/output-mb-k8s.json | 83 +++++++++++++++++++ 9 files changed, 289 insertions(+), 27 deletions(-) create mode 100644 script/test/fixtures/mem-limit/docker-compose-mb.yml create mode 100644 script/test/fixtures/mem-limit/docker-compose.yml create mode 100644 script/test/fixtures/mem-limit/output-k8s.json create mode 100644 script/test/fixtures/mem-limit/output-mb-k8s.json diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 4f5bed01..951867e2 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -16,7 +16,10 @@ limitations under the License. package kobject -import "k8s.io/kubernetes/pkg/api" +import ( + "github.com/docker/libcompose/yaml" + "k8s.io/kubernetes/pkg/api" +) // KomposeObject holds the generic struct of Kompose transformation type KomposeObject struct { @@ -54,31 +57,32 @@ type ConvertOptions struct { type ServiceConfig struct { // use tags to mark from what element this value comes ContainerName string - Image string `compose:"image",bundle:"Image"` - Environment []EnvVar `compose:"environment",bundle:"Env"` - Port []Ports `compose:"ports",bundle:"Ports"` - Command []string `compose:"command",bundle:"Command"` - WorkingDir string `compose:"",bundle:"WorkingDir"` - Args []string `compose:"args",bundle:"Args"` - Volumes []string `compose:"volumes",bundle:"Volumes"` - Network []string `compose:"network",bundle:"Networks"` - Labels map[string]string `compose:"labels",bundle:"Labels"` - Annotations map[string]string `compose:"",bundle:""` - CPUSet string `compose:"cpuset",bundle:""` - CPUShares int64 `compose:"cpu_shares",bundle:""` - CPUQuota int64 `compose:"cpu_quota",bundle:""` - CapAdd []string `compose:"cap_add",bundle:""` - CapDrop []string `compose:"cap_drop",bundle:""` - Expose []string `compose:"expose",bundle:""` - Privileged bool `compose:"privileged",bundle:""` - Restart string `compose:"restart",bundle:""` - User string `compose:"user",bundle:"User"` - VolumesFrom []string `compose:"volumes_from",bundle:""` - ServiceType string `compose:"kompose.service.type",bundle:""` - Build string `compose:"build",bundle:""` - ExposeService string `compose:"kompose.service.expose",bundle:""` - Stdin bool `compose:"stdin_open",bundle:""` - Tty bool `compose:"tty",bundle:""` + Image string `compose:"image",bundle:"Image"` + Environment []EnvVar `compose:"environment",bundle:"Env"` + Port []Ports `compose:"ports",bundle:"Ports"` + Command []string `compose:"command",bundle:"Command"` + WorkingDir string `compose:"",bundle:"WorkingDir"` + Args []string `compose:"args",bundle:"Args"` + Volumes []string `compose:"volumes",bundle:"Volumes"` + Network []string `compose:"network",bundle:"Networks"` + Labels map[string]string `compose:"labels",bundle:"Labels"` + Annotations map[string]string `compose:"",bundle:""` + CPUSet string `compose:"cpuset",bundle:""` + CPUShares int64 `compose:"cpu_shares",bundle:""` + CPUQuota int64 `compose:"cpu_quota",bundle:""` + CapAdd []string `compose:"cap_add",bundle:""` + CapDrop []string `compose:"cap_drop",bundle:""` + Expose []string `compose:"expose",bundle:""` + Privileged bool `compose:"privileged",bundle:""` + Restart string `compose:"restart",bundle:""` + User string `compose:"user",bundle:"User"` + VolumesFrom []string `compose:"volumes_from",bundle:""` + ServiceType string `compose:"kompose.service.type",bundle:""` + Build string `compose:"build",bundle:""` + ExposeService string `compose:"kompose.service.expose",bundle:""` + Stdin bool `compose:"stdin_open",bundle:""` + Tty bool `compose:"tty",bundle:""` + MemLimit yaml.MemStringorInt `compose:"mem_limit",bundle:""` } // EnvVar holds the environment variable struct of a container diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go index e6cf9125..89c5fe5b 100644 --- a/pkg/loader/compose/compose.go +++ b/pkg/loader/compose/compose.go @@ -64,7 +64,6 @@ func checkUnsupportedKey(composeProject *project.Project) []string { "Ipc": false, "Logging": false, "MacAddress": false, - "MemLimit": false, "MemSwapLimit": false, "NetworkMode": false, "Pid": false, @@ -366,6 +365,7 @@ func (c *Compose) LoadFile(files []string) kobject.KomposeObject { serviceConfig.VolumesFrom = composeServiceConfig.VolumesFrom serviceConfig.Stdin = composeServiceConfig.StdinOpen serviceConfig.Tty = composeServiceConfig.Tty + serviceConfig.MemLimit = composeServiceConfig.MemLimit komposeObject.ServiceConfigs[name] = serviceConfig } diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index efd11530..ea87f6be 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -39,6 +39,7 @@ import ( "k8s.io/kubernetes/pkg/runtime" deployapi "github.com/openshift/origin/pkg/deploy/api" + "k8s.io/kubernetes/pkg/api/resource" ) /** @@ -343,6 +344,15 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic template.Spec.Containers[0].TTY = service.Tty template.Spec.Volumes = volumes + // Configure the resource limits + if service.MemLimit != 0 { + memoryResourceList := api.ResourceList{ + api.ResourceMemory: *resource.NewQuantity( + int64(service.MemLimit), "RandomStringForFormat")} + template.Spec.Containers[0].Resources.Limits = memoryResourceList + } + + // Setup security context securityContext := &api.SecurityContext{} if service.Privileged == true { securityContext.Privileged = &service.Privileged @@ -356,6 +366,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic } } + // update template only if securityContext is not empty if *securityContext != (api.SecurityContext{}) { template.Spec.Containers[0].SecurityContext = securityContext @@ -363,6 +374,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic template.Spec.Containers[0].Ports = ports template.ObjectMeta.Labels = transformer.ConfigLabels(name) + // Configure the container restart policy. switch service.Restart { case "", "always": diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index 2a10cd84..a7fa152a 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -67,11 +67,59 @@ func TestCreateService(t *testing.T) { // Test the creation of the service svc := k.CreateService("foo", service, objects) + if svc.Spec.Ports[0].Port != 123 { t.Errorf("Expected port 123 upon conversion, actual %d", svc.Spec.Ports[0].Port) } } +/* + Test the creation of a service with a memory limit +*/ +func TestCreateServiceWithMemLimit(t *testing.T) { + + // An example service + service := 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"}, // not supported + Labels: nil, + Annotations: map[string]string{"abc": "def"}, + CPUSet: "cpu_set", // not supported + CPUShares: 1, // not supported + CPUQuota: 1, // not supported + CapAdd: []string{"cap_add"}, // not supported + CapDrop: []string{"cap_drop"}, // not supported + Expose: []string{"expose"}, // not supported + Privileged: true, + Restart: "always", + MemLimit: 1337, + } + + // An example object generated via k8s runtime.Objects() + komposeObject := kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, + } + k := Kubernetes{} + objects := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 3}) + + // Retrieve the deployment object and test that it matches the MemLimit value + for _, obj := range objects { + if deploy, ok := obj.(*extensions.Deployment); ok { + memTest, _ := deploy.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().AsInt64() + if memTest != 1337 { + t.Errorf("Expected 1337 for mem_limit check, got %v", memTest) + } + } + } +} + /* Test the creation of a service with a specified user. The expected result is that Kompose will set user in PodSpec diff --git a/script/test/cmd/tests.sh b/script/test/cmd/tests.sh index 1ff7dcfe..0cc3f9d5 100755 --- a/script/test/cmd/tests.sh +++ b/script/test/cmd/tests.sh @@ -55,6 +55,16 @@ convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/entrypoin convert::expect_success "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/entrypoint-command/docker-compose.yml convert --stdout -j" "$KOMPOSE_ROOT/script/test/fixtures/entrypoint-command/output-os.json" +###### +# Tests related to docker-compose file in /script/test/fixtures/mem-limit +# kubernetes test + +# Test the "memory limit" conversion +convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/mem-limit/docker-compose.yml convert --stdout -j" "$KOMPOSE_ROOT/script/test/fixtures/mem-limit/output-k8s.json" + +# Test the "memory limit" conversion with "Mb" tagged on +convert::expect_success "kompose -f $KOMPOSE_ROOT/script/test/fixtures/mem-limit/docker-compose-mb.yml convert --stdout -j" "$KOMPOSE_ROOT/script/test/fixtures/mem-limit/output-mb-k8s.json" + ###### # Tests related to docker-compose file in /script/test/fixtures/ports-with-proto # kubernetes test diff --git a/script/test/fixtures/mem-limit/docker-compose-mb.yml b/script/test/fixtures/mem-limit/docker-compose-mb.yml new file mode 100644 index 00000000..846d6cf8 --- /dev/null +++ b/script/test/fixtures/mem-limit/docker-compose-mb.yml @@ -0,0 +1,11 @@ +version: "2" + +services: + redis: + image: redis:3.0 + networks: + - default + ports: + - "6379/tcp" + - "1234:1235/udp" + mem_limit: 10000Mb diff --git a/script/test/fixtures/mem-limit/docker-compose.yml b/script/test/fixtures/mem-limit/docker-compose.yml new file mode 100644 index 00000000..eb1268a6 --- /dev/null +++ b/script/test/fixtures/mem-limit/docker-compose.yml @@ -0,0 +1,11 @@ +version: "2" + +services: + redis: + image: redis:3.0 + networks: + - default + ports: + - "6379/tcp" + - "1234:1235/udp" + mem_limit: 10000 diff --git a/script/test/fixtures/mem-limit/output-k8s.json b/script/test/fixtures/mem-limit/output-k8s.json new file mode 100644 index 00000000..7bac65ad --- /dev/null +++ b/script/test/fixtures/mem-limit/output-k8s.json @@ -0,0 +1,83 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "redis", + "creationTimestamp": null, + "labels": { + "service": "redis" + } + }, + "spec": { + "ports": [ + { + "name": "6379", + "port": 6379, + "targetPort": 6379 + }, + { + "name": "1234", + "protocol": "UDP", + "port": 1234, + "targetPort": 1235 + } + ], + "selector": { + "service": "redis" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "redis", + "creationTimestamp": null + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "service": "redis" + } + }, + "spec": { + "containers": [ + { + "name": "redis", + "image": "redis:3.0", + "ports": [ + { + "containerPort": 6379 + }, + { + "containerPort": 1235, + "protocol": "UDP" + } + ], + "resources": { + "limits": { + "memory": "10e3" + } + } + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + } + ] +} diff --git a/script/test/fixtures/mem-limit/output-mb-k8s.json b/script/test/fixtures/mem-limit/output-mb-k8s.json new file mode 100644 index 00000000..7796091c --- /dev/null +++ b/script/test/fixtures/mem-limit/output-mb-k8s.json @@ -0,0 +1,83 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "redis", + "creationTimestamp": null, + "labels": { + "service": "redis" + } + }, + "spec": { + "ports": [ + { + "name": "6379", + "port": 6379, + "targetPort": 6379 + }, + { + "name": "1234", + "protocol": "UDP", + "port": 1234, + "targetPort": 1235 + } + ], + "selector": { + "service": "redis" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "redis", + "creationTimestamp": null + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "service": "redis" + } + }, + "spec": { + "containers": [ + { + "name": "redis", + "image": "redis:3.0", + "ports": [ + { + "containerPort": 6379 + }, + { + "containerPort": 1235, + "protocol": "UDP" + } + ], + "resources": { + "limits": { + "memory": "10485760e3" + } + } + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + } + ] +}