package inject import ( "testing" "github.com/go-test/deep" l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2" "github.com/linkerd/linkerd2/pkg/k8s" "github.com/linkerd/linkerd2/pkg/util" "github.com/linkerd/linkerd2/pkg/version" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" k8sResource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" ) func TestGetOverriddenValues(t *testing.T) { // this test uses an annotated deployment and a expected Values object to verify // the GetOverriddenValues function. var ( proxyVersionOverride = "proxy-version-override" pullPolicy = "Always" ) testConfig, err := l5dcharts.NewValues() if err != nil { t.Fatalf("Unexpected error: %v", err) } var testCases = []struct { id string nsAnnotations map[string]string spec appsv1.DeploymentSpec expected func() *l5dcharts.Values }{ {id: "use overrides", nsAnnotations: make(map[string]string), spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ k8s.ProxyImageAnnotation: "cr.l5d.io/linkerd/proxy", k8s.ProxyImagePullPolicyAnnotation: pullPolicy, k8s.ProxyInitImageAnnotation: "cr.l5d.io/linkerd/proxy-init", k8s.ProxyControlPortAnnotation: "4000", k8s.ProxyInboundPortAnnotation: "5000", k8s.ProxyAdminPortAnnotation: "5001", k8s.ProxyOutboundPortAnnotation: "5002", k8s.ProxyPodInboundPortsAnnotation: "1234,5678", k8s.ProxyIgnoreInboundPortsAnnotation: "4222,6222", k8s.ProxyIgnoreOutboundPortsAnnotation: "8079,8080", k8s.ProxyCPURequestAnnotation: "0.15", k8s.ProxyMemoryRequestAnnotation: "120", k8s.ProxyEphemeralStorageRequestAnnotation: "10", k8s.ProxyCPULimitAnnotation: "1.5", k8s.ProxyMemoryLimitAnnotation: "256", k8s.ProxyEphemeralStorageLimitAnnotation: "50", k8s.ProxyUIDAnnotation: "8500", k8s.ProxyGIDAnnotation: "8500", k8s.ProxyLogLevelAnnotation: "debug,linkerd=debug", k8s.ProxyLogFormatAnnotation: "json", k8s.ProxyEnableExternalProfilesAnnotation: "false", k8s.ProxyVersionOverrideAnnotation: proxyVersionOverride, k8s.ProxyWaitBeforeExitSecondsAnnotation: "123", k8s.ProxyRequireIdentityOnInboundPortsAnnotation: "8888,9999", k8s.ProxyOutboundConnectTimeout: "6000ms", k8s.ProxyInboundConnectTimeout: "600ms", k8s.ProxyOpaquePortsAnnotation: "4320-4325,3306", k8s.ProxyAwait: "enabled", k8s.ProxySkipSubnetsAnnotation: "172.17.0.0/16", k8s.ProxyAccessLogAnnotation: "apache", k8s.ProxyShutdownGracePeriodAnnotation: "30s", k8s.ProxyOutboundDiscoveryCacheUnusedTimeout: "50000ms", k8s.ProxyInboundDiscoveryCacheUnusedTimeout: "900s", k8s.ProxyDisableOutboundProtocolDetectTimeout: "true", k8s.ProxyDisableInboundProtocolDetectTimeout: "true", k8s.ProxyEnableNativeSidecarAnnotation: "true", }, }, Spec: corev1.PodSpec{}, }, }, expected: func() *l5dcharts.Values { values, _ := l5dcharts.NewValues() values.Proxy.Cores = 2 values.Proxy.Image.Name = "cr.l5d.io/linkerd/proxy" values.Proxy.Image.PullPolicy = pullPolicy values.Proxy.Image.Version = proxyVersionOverride values.Proxy.PodInboundPorts = "1234,5678" values.Proxy.Ports.Control = 4000 values.Proxy.Ports.Inbound = 5000 values.Proxy.Ports.Admin = 5001 values.Proxy.Ports.Outbound = 5002 values.Proxy.WaitBeforeExitSeconds = 123 values.Proxy.LogLevel = "debug,linkerd=debug" values.Proxy.LogFormat = "json" values.Proxy.Resources = &l5dcharts.Resources{ CPU: l5dcharts.Constraints{ Limit: "1.5", Request: "0.15", }, Memory: l5dcharts.Constraints{ Limit: "256", Request: "120", }, EphemeralStorage: l5dcharts.Constraints{ Limit: "50", Request: "10", }, } values.Proxy.UID = 8500 values.Proxy.GID = 8500 values.ProxyInit.Image.Name = "cr.l5d.io/linkerd/proxy-init" values.ProxyInit.Image.PullPolicy = pullPolicy values.ProxyInit.Image.Version = version.ProxyInitVersion values.ProxyInit.IgnoreInboundPorts = "4222,6222" values.ProxyInit.IgnoreOutboundPorts = "8079,8080" values.ProxyInit.SkipSubnets = "172.17.0.0/16" values.Proxy.RequireIdentityOnInboundPorts = "8888,9999" values.Proxy.OutboundConnectTimeout = "6000ms" values.Proxy.InboundConnectTimeout = "600ms" values.Proxy.OpaquePorts = "4320-4325,3306" values.Proxy.Await = true values.Proxy.AccessLog = "apache" values.Proxy.ShutdownGracePeriod = "30000ms" values.Proxy.OutboundDiscoveryCacheUnusedTimeout = "50s" values.Proxy.InboundDiscoveryCacheUnusedTimeout = "900s" values.Proxy.DisableOutboundProtocolDetectTimeout = true values.Proxy.DisableInboundProtocolDetectTimeout = true values.Proxy.NativeSidecar = true return values }, }, {id: "use defaults", nsAnnotations: make(map[string]string), spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{}, Spec: corev1.PodSpec{}, }, }, expected: func() *l5dcharts.Values { values, _ := l5dcharts.NewValues() return values }, }, {id: "use namespace overrides", nsAnnotations: map[string]string{ k8s.ProxyImageAnnotation: "cr.l5d.io/linkerd/proxy", k8s.ProxyImagePullPolicyAnnotation: pullPolicy, k8s.ProxyInitImageAnnotation: "cr.l5d.io/linkerd/proxy-init", k8s.ProxyControlPortAnnotation: "4000", k8s.ProxyInboundPortAnnotation: "5000", k8s.ProxyAdminPortAnnotation: "5001", k8s.ProxyOutboundPortAnnotation: "5002", k8s.ProxyPodInboundPortsAnnotation: "1234,5678", k8s.ProxyIgnoreInboundPortsAnnotation: "4222,6222", k8s.ProxyIgnoreOutboundPortsAnnotation: "8079,8080", k8s.ProxyCPURequestAnnotation: "0.15", k8s.ProxyMemoryRequestAnnotation: "120", k8s.ProxyCPULimitAnnotation: "1.5", k8s.ProxyMemoryLimitAnnotation: "256", k8s.ProxyUIDAnnotation: "8500", k8s.ProxyGIDAnnotation: "8500", k8s.ProxyLogLevelAnnotation: "debug,linkerd=debug", k8s.ProxyLogFormatAnnotation: "json", k8s.ProxyEnableExternalProfilesAnnotation: "false", k8s.ProxyVersionOverrideAnnotation: proxyVersionOverride, k8s.ProxyWaitBeforeExitSecondsAnnotation: "123", k8s.ProxyOutboundConnectTimeout: "6000ms", k8s.ProxyInboundConnectTimeout: "600ms", k8s.ProxyOpaquePortsAnnotation: "4320-4325,3306", k8s.ProxyAwait: "enabled", k8s.ProxyAccessLogAnnotation: "apache", k8s.ProxyInjectAnnotation: "ingress", k8s.ProxyOutboundDiscoveryCacheUnusedTimeout: "50s", k8s.ProxyInboundDiscoveryCacheUnusedTimeout: "6000ms", k8s.ProxyDisableOutboundProtocolDetectTimeout: "true", k8s.ProxyDisableInboundProtocolDetectTimeout: "false", k8s.ProxyEnableNativeSidecarAnnotation: "true", }, spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ Spec: corev1.PodSpec{}, }, }, expected: func() *l5dcharts.Values { values, _ := l5dcharts.NewValues() values.Proxy.Cores = 2 values.Proxy.Image.Name = "cr.l5d.io/linkerd/proxy" values.Proxy.Image.PullPolicy = pullPolicy values.Proxy.Image.Version = proxyVersionOverride values.Proxy.PodInboundPorts = "1234,5678" values.Proxy.Ports.Control = 4000 values.Proxy.Ports.Inbound = 5000 values.Proxy.Ports.Admin = 5001 values.Proxy.Ports.Outbound = 5002 values.Proxy.WaitBeforeExitSeconds = 123 values.Proxy.LogLevel = "debug,linkerd=debug" values.Proxy.LogFormat = "json" values.Proxy.Resources = &l5dcharts.Resources{ CPU: l5dcharts.Constraints{ Limit: "1.5", Request: "0.15", }, Memory: l5dcharts.Constraints{ Limit: "256", Request: "120", }, } values.Proxy.UID = 8500 values.Proxy.GID = 8500 values.ProxyInit.Image.Name = "cr.l5d.io/linkerd/proxy-init" values.ProxyInit.Image.PullPolicy = pullPolicy values.ProxyInit.Image.Version = version.ProxyInitVersion values.ProxyInit.IgnoreInboundPorts = "4222,6222" values.ProxyInit.IgnoreOutboundPorts = "8079,8080" values.Proxy.OutboundConnectTimeout = "6000ms" values.Proxy.InboundConnectTimeout = "600ms" values.Proxy.OpaquePorts = "4320-4325,3306" values.Proxy.Await = true values.Proxy.AccessLog = "apache" values.Proxy.IsIngress = true values.Proxy.OutboundDiscoveryCacheUnusedTimeout = "50s" values.Proxy.InboundDiscoveryCacheUnusedTimeout = "6s" values.Proxy.DisableOutboundProtocolDetectTimeout = true values.Proxy.DisableInboundProtocolDetectTimeout = false values.Proxy.NativeSidecar = true return values }, }, {id: "use invalid duration for proxy timeouts", nsAnnotations: map[string]string{ k8s.ProxyOutboundConnectTimeout: "6000", k8s.ProxyInboundConnectTimeout: "600", k8s.ProxyOutboundDiscoveryCacheUnusedTimeout: "50", k8s.ProxyInboundDiscoveryCacheUnusedTimeout: "5000", k8s.ProxyDisableOutboundProtocolDetectTimeout: "9000", k8s.ProxyDisableInboundProtocolDetectTimeout: "9", }, spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{}, Spec: corev1.PodSpec{}, }, }, expected: func() *l5dcharts.Values { values, _ := l5dcharts.NewValues() return values }, }, {id: "use valid duration for proxy timeouts", nsAnnotations: map[string]string{ // Validate we're converting time values into ms for the proxy to parse correctly. k8s.ProxyOutboundConnectTimeout: "6s5ms", k8s.ProxyInboundConnectTimeout: "2s5ms", k8s.ProxyOutboundDiscoveryCacheUnusedTimeout: "6s5000ms", k8s.ProxyInboundDiscoveryCacheUnusedTimeout: "6s5000ms", k8s.ProxyDisableOutboundProtocolDetectTimeout: "false", k8s.ProxyDisableInboundProtocolDetectTimeout: "true", }, spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{}, Spec: corev1.PodSpec{}, }, }, expected: func() *l5dcharts.Values { values, _ := l5dcharts.NewValues() values.Proxy.OutboundConnectTimeout = "6005ms" values.Proxy.InboundConnectTimeout = "2005ms" values.Proxy.OutboundDiscoveryCacheUnusedTimeout = "11s" values.Proxy.InboundDiscoveryCacheUnusedTimeout = "11s" values.Proxy.DisableOutboundProtocolDetectTimeout = false values.Proxy.DisableInboundProtocolDetectTimeout = true return values }, }, {id: "use named port for opaque ports", nsAnnotations: make(map[string]string), spec: appsv1.DeploymentSpec{ Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ k8s.ProxyOpaquePortsAnnotation: "mysql", }, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Ports: []corev1.ContainerPort{ { Name: "mysql", ContainerPort: 3306, }, }, }, }, }, }, }, expected: func() *l5dcharts.Values { values, _ := l5dcharts.NewValues() values.Proxy.OpaquePorts = "3306" return values }, }, } for _, tc := range testCases { testCase := tc t.Run(testCase.id, func(t *testing.T) { data, err := yaml.Marshal(&appsv1.Deployment{Spec: testCase.spec}) if err != nil { t.Fatal(err) } resourceConfig := NewResourceConfig(testConfig, OriginUnknown, "linkerd"). WithKind("Deployment").WithNsAnnotations(testCase.nsAnnotations) if err := resourceConfig.parse(data); err != nil { t.Fatal(err) } AppendNamespaceAnnotations(resourceConfig.GetOverrideAnnotations(), resourceConfig.GetNsAnnotations(), resourceConfig.GetWorkloadAnnotations()) actual, err := GetOverriddenValues( resourceConfig.values, resourceConfig.getAnnotationOverrides(), util.GetNamedPorts(resourceConfig.pod.spec.Containers), ) if err != nil { t.Fatal(err) } expected := testCase.expected() if diff := deep.Equal(actual, expected); diff != nil { t.Errorf("%+v", diff) } }) } } func TestWholeCPUCores(t *testing.T) { for _, c := range []struct { v string n int }{ {v: "1", n: 1}, {v: "1m", n: 1}, {v: "1000m", n: 1}, {v: "1001m", n: 2}, } { q, err := k8sResource.ParseQuantity(c.v) if err != nil { t.Fatal(err) } n, err := ToWholeCPUCores(q) if err != nil { t.Fatal(err) } if n != int64(c.n) { t.Fatalf("Unexpected value: %v != %v", n, c.n) } } }