From dda62129d12da2e510e8e07dc1a93c74ed1e6dc7 Mon Sep 17 00:00:00 2001 From: Yifan Gu Date: Fri, 8 Jan 2016 13:21:17 -0800 Subject: [PATCH] rkt: Add unit tests for setApp. --- pkg/kubelet/rkt/rkt_test.go | 271 +++++++++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 4 deletions(-) diff --git a/pkg/kubelet/rkt/rkt_test.go b/pkg/kubelet/rkt/rkt_test.go index 3eca21e41bc..8822336387d 100644 --- a/pkg/kubelet/rkt/rkt_test.go +++ b/pkg/kubelet/rkt/rkt_test.go @@ -19,6 +19,7 @@ package rkt import ( "encoding/json" "fmt" + "sort" "testing" "time" @@ -26,6 +27,8 @@ import ( appctypes "github.com/appc/spec/schema/types" rktapi "github.com/coreos/rkt/api/v1alpha" "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" ) @@ -245,7 +248,7 @@ func TestCheckVersion(t *testing.T) { for i, tt := range tests { testCaseHint := fmt.Sprintf("test case #%d", i) err := r.checkVersion(tt.minimumRktBinVersion, tt.recommendedRktBinVersion, tt.minimumAppcVersion, tt.minimumRktApiVersion, tt.minimumSystemdVersion) - assert.Equal(t, err, tt.err, testCaseHint) + assert.Equal(t, tt.err, err, testCaseHint) if tt.calledGetInfo { assert.Equal(t, fr.called, []string{"GetInfo"}, testCaseHint) @@ -254,9 +257,9 @@ func TestCheckVersion(t *testing.T) { assert.Equal(t, fs.called, []string{"Version"}, testCaseHint) } if err == nil { - assert.Equal(t, r.binVersion.String(), fr.info.RktVersion, testCaseHint) - assert.Equal(t, r.appcVersion.String(), fr.info.AppcVersion, testCaseHint) - assert.Equal(t, r.apiVersion.String(), fr.info.ApiVersion, testCaseHint) + assert.Equal(t, fr.info.RktVersion, r.binVersion.String(), testCaseHint) + assert.Equal(t, fr.info.AppcVersion, r.appcVersion.String(), testCaseHint) + assert.Equal(t, fr.info.ApiVersion, r.apiVersion.String(), testCaseHint) } fr.CleanCalls() fs.CleanCalls() @@ -662,3 +665,263 @@ func TestGetPodStatus(t *testing.T) { fr.CleanCalls() } } + +func generateCapRetainIsolator(t *testing.T, caps ...string) appctypes.Isolator { + retain, err := appctypes.NewLinuxCapabilitiesRetainSet(caps...) + if err != nil { + t.Fatalf("Error generating cap retain isolator", err) + } + return retain.AsIsolator() +} + +func generateCapRevokeIsolator(t *testing.T, caps ...string) appctypes.Isolator { + revoke, err := appctypes.NewLinuxCapabilitiesRevokeSet(caps...) + if err != nil { + t.Fatalf("Error generating cap revoke isolator", err) + } + return revoke.AsIsolator() +} + +func generateCPUIsolator(t *testing.T, request, limit string) appctypes.Isolator { + cpu, err := appctypes.NewResourceCPUIsolator(request, limit) + if err != nil { + t.Fatalf("Error generating cpu resource isolator", err) + } + return cpu.AsIsolator() +} + +func generateMemoryIsolator(t *testing.T, request, limit string) appctypes.Isolator { + memory, err := appctypes.NewResourceMemoryIsolator(request, limit) + if err != nil { + t.Fatalf("Error generating memory resource isolator", err) + } + return memory.AsIsolator() +} + +func baseApp(t *testing.T) *appctypes.App { + return &appctypes.App{ + Exec: appctypes.Exec{"/bin/foo"}, + User: "0", + Group: "22", + SupplementaryGIDs: []int{4, 5, 6}, + WorkingDirectory: "/foo", + Environment: []appctypes.EnvironmentVariable{ + {"env-foo", "bar"}, + }, + MountPoints: []appctypes.MountPoint{ + {Name: *appctypes.MustACName("mnt-foo"), Path: "/mnt-foo", ReadOnly: false}, + }, + Ports: []appctypes.Port{ + {Name: *appctypes.MustACName("port-foo"), Protocol: "TCP", Port: 4242}, + }, + Isolators: []appctypes.Isolator{ + generateCapRetainIsolator(t, "CAP_SYS_ADMIN"), + generateCapRevokeIsolator(t, "CAP_NET_ADMIN"), + generateCPUIsolator(t, "100m", "200m"), + generateMemoryIsolator(t, "10M", "20M"), + }, + } +} + +type envByName []appctypes.EnvironmentVariable + +func (s envByName) Len() int { return len(s) } +func (s envByName) Less(i, j int) bool { return s[i].Name < s[j].Name } +func (s envByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type mountsByName []appctypes.MountPoint + +func (s mountsByName) Len() int { return len(s) } +func (s mountsByName) Less(i, j int) bool { return s[i].Name < s[j].Name } +func (s mountsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type portsByName []appctypes.Port + +func (s portsByName) Len() int { return len(s) } +func (s portsByName) Less(i, j int) bool { return s[i].Name < s[j].Name } +func (s portsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +type isolatorsByName []appctypes.Isolator + +func (s isolatorsByName) Len() int { return len(s) } +func (s isolatorsByName) Less(i, j int) bool { return s[i].Name < s[j].Name } +func (s isolatorsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func sortAppFields(app *appctypes.App) { + sort.Sort(envByName(app.Environment)) + sort.Sort(mountsByName(app.MountPoints)) + sort.Sort(portsByName(app.Ports)) + sort.Sort(isolatorsByName(app.Isolators)) +} + +func TestSetApp(t *testing.T) { + rootUser := int64(0) + nonRootUser := int64(42) + runAsNonRootTrue := true + fsgid := int64(3) + + tests := []struct { + container *api.Container + opts *kubecontainer.RunContainerOptions + ctx *api.SecurityContext + podCtx *api.PodSecurityContext + expect *appctypes.App + err error + }{ + // Nothing should change. + { + container: &api.Container{}, + opts: &kubecontainer.RunContainerOptions{}, + ctx: nil, + podCtx: nil, + expect: baseApp(t), + err: nil, + }, + + // error verifying non-root. + { + container: &api.Container{}, + opts: &kubecontainer.RunContainerOptions{}, + ctx: &api.SecurityContext{ + RunAsNonRoot: &runAsNonRootTrue, + RunAsUser: &rootUser, + }, + podCtx: nil, + expect: nil, + err: fmt.Errorf("container has no runAsUser and image will run as root"), + }, + + // app should be changed. + { + container: &api.Container{ + Command: []string{"/bin/bar"}, + Args: []string{"hello", "world"}, + WorkingDir: "/tmp", + Resources: api.ResourceRequirements{ + Limits: api.ResourceList{"cpu": resource.MustParse("50m"), "memory": resource.MustParse("50M")}, + Requests: api.ResourceList{"cpu": resource.MustParse("5m"), "memory": resource.MustParse("5M")}, + }, + }, + opts: &kubecontainer.RunContainerOptions{ + Envs: []kubecontainer.EnvVar{ + {Name: "env-bar", Value: "foo"}, + }, + Mounts: []kubecontainer.Mount{ + {Name: "mnt-bar", ContainerPath: "/mnt-bar", ReadOnly: true}, + }, + PortMappings: []kubecontainer.PortMapping{ + {Name: "port-bar", Protocol: api.ProtocolTCP, ContainerPort: 1234}, + }, + }, + ctx: &api.SecurityContext{ + Capabilities: &api.Capabilities{ + Add: []api.Capability{"CAP_SYS_CHROOT", "CAP_SYS_BOOT"}, + Drop: []api.Capability{"CAP_SETUID", "CAP_SETGID"}, + }, + RunAsUser: &nonRootUser, + RunAsNonRoot: &runAsNonRootTrue, + }, + podCtx: &api.PodSecurityContext{ + SupplementalGroups: []int64{1, 2}, + FSGroup: &fsgid, + }, + expect: &appctypes.App{ + Exec: appctypes.Exec{"/bin/bar", "hello", "world"}, + User: "42", + Group: "22", + SupplementaryGIDs: []int{1, 2, 3}, + WorkingDirectory: "/tmp", + Environment: []appctypes.EnvironmentVariable{ + {"env-foo", "bar"}, + {"env-bar", "foo"}, + }, + MountPoints: []appctypes.MountPoint{ + {Name: *appctypes.MustACName("mnt-foo"), Path: "/mnt-foo", ReadOnly: false}, + {Name: *appctypes.MustACName("mnt-bar"), Path: "/mnt-bar", ReadOnly: true}, + }, + Ports: []appctypes.Port{ + {Name: *appctypes.MustACName("port-foo"), Protocol: "TCP", Port: 4242}, + {Name: *appctypes.MustACName("port-bar"), Protocol: "TCP", Port: 1234}, + }, + Isolators: []appctypes.Isolator{ + generateCapRetainIsolator(t, "CAP_SYS_CHROOT", "CAP_SYS_BOOT"), + generateCapRevokeIsolator(t, "CAP_SETUID", "CAP_SETGID"), + generateCPUIsolator(t, "5m", "50m"), + generateMemoryIsolator(t, "5M", "50M"), + }, + }, + }, + + // app should be changed. (env, mounts, ports, are overrided). + { + container: &api.Container{ + Command: []string{"/bin/bar"}, + Args: []string{"hello", "world"}, + WorkingDir: "/tmp", + Resources: api.ResourceRequirements{ + Limits: api.ResourceList{"cpu": resource.MustParse("50m"), "memory": resource.MustParse("50M")}, + Requests: api.ResourceList{"cpu": resource.MustParse("5m"), "memory": resource.MustParse("5M")}, + }, + }, + opts: &kubecontainer.RunContainerOptions{ + Envs: []kubecontainer.EnvVar{ + {Name: "env-foo", Value: "foo"}, + }, + Mounts: []kubecontainer.Mount{ + {Name: "mnt-foo", ContainerPath: "/mnt-bar", ReadOnly: true}, + }, + PortMappings: []kubecontainer.PortMapping{ + {Name: "port-foo", Protocol: api.ProtocolTCP, ContainerPort: 1234}, + }, + }, + ctx: &api.SecurityContext{ + Capabilities: &api.Capabilities{ + Add: []api.Capability{"CAP_SYS_CHROOT", "CAP_SYS_BOOT"}, + Drop: []api.Capability{"CAP_SETUID", "CAP_SETGID"}, + }, + RunAsUser: &nonRootUser, + RunAsNonRoot: &runAsNonRootTrue, + }, + podCtx: &api.PodSecurityContext{ + SupplementalGroups: []int64{1, 2}, + FSGroup: &fsgid, + }, + expect: &appctypes.App{ + Exec: appctypes.Exec{"/bin/bar", "hello", "world"}, + User: "42", + Group: "22", + SupplementaryGIDs: []int{1, 2, 3}, + WorkingDirectory: "/tmp", + Environment: []appctypes.EnvironmentVariable{ + {"env-foo", "foo"}, + }, + MountPoints: []appctypes.MountPoint{ + {Name: *appctypes.MustACName("mnt-foo"), Path: "/mnt-bar", ReadOnly: true}, + }, + Ports: []appctypes.Port{ + {Name: *appctypes.MustACName("port-foo"), Protocol: "TCP", Port: 1234}, + }, + Isolators: []appctypes.Isolator{ + generateCapRetainIsolator(t, "CAP_SYS_CHROOT", "CAP_SYS_BOOT"), + generateCapRevokeIsolator(t, "CAP_SETUID", "CAP_SETGID"), + generateCPUIsolator(t, "5m", "50m"), + generateMemoryIsolator(t, "5M", "50M"), + }, + }, + }, + } + + for i, tt := range tests { + testCaseHint := fmt.Sprintf("test case #%d", i) + app := baseApp(t) + err := setApp(app, tt.container, tt.opts, tt.ctx, tt.podCtx) + if err == nil && tt.err != nil || err != nil && tt.err == nil { + t.Errorf("%s: expect %v, saw %v", testCaseHint, tt.err, err) + } + if err == nil { + sortAppFields(tt.expect) + sortAppFields(app) + assert.Equal(t, tt.expect, app, testCaseHint) + } + } +}