diff --git a/pkg/inject/inject.go b/pkg/inject/inject.go index 304c2dc44..e496828e0 100644 --- a/pkg/inject/inject.go +++ b/pkg/inject/inject.go @@ -537,7 +537,10 @@ func (conf *ResourceConfig) injectPodAnnotations(values *patch) { } func (conf *ResourceConfig) getOverride(annotation string) string { - return conf.pod.meta.Annotations[annotation] + if override := conf.pod.meta.Annotations[annotation]; override != "" { + return override + } + return conf.nsAnnotations[annotation] } func (conf *ResourceConfig) proxyImage() string { diff --git a/pkg/inject/inject_test.go b/pkg/inject/inject_test.go index c8e14d413..dc9c28068 100644 --- a/pkg/inject/inject_test.go +++ b/pkg/inject/inject_test.go @@ -75,9 +75,10 @@ func TestConfigAccessors(t *testing.T) { configs := &config.All{Global: globalConfig, Proxy: proxyConfig} var testCases = []struct { - id string - spec appsv1.DeploymentSpec - expected expectedProxyConfigs + id string + nsAnnotations map[string]string + spec appsv1.DeploymentSpec + expected expectedProxyConfigs }{ {id: "use overrides", spec: appsv1.DeploymentSpec{ @@ -168,6 +169,58 @@ func TestConfigAccessors(t *testing.T) { outboundSkipPorts: "9079", }, }, + {id: "use namespace overrides", + nsAnnotations: map[string]string{ + k8s.ProxyDisableIdentityAnnotation: "true", + k8s.ProxyImageAnnotation: "gcr.io/linkerd-io/proxy", + k8s.ProxyImagePullPolicyAnnotation: "Always", + k8s.ProxyInitImageAnnotation: "gcr.io/linkerd-io/proxy-init", + k8s.ProxyControlPortAnnotation: "4000", + k8s.ProxyInboundPortAnnotation: "5000", + k8s.ProxyAdminPortAnnotation: "5001", + k8s.ProxyOutboundPortAnnotation: "5002", + 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.ProxyLogLevelAnnotation: "debug,linkerd2_proxy=debug", + k8s.ProxyEnableExternalProfilesAnnotation: "false", + k8s.ProxyVersionOverrideAnnotation: proxyVersionOverride}, + spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{}, + }, + }, + expected: expectedProxyConfigs{ + image: "gcr.io/linkerd-io/proxy", + imagePullPolicy: "Always", + proxyVersion: proxyVersionOverride, + controlPort: int32(4000), + inboundPort: int32(5000), + adminPort: int32(5001), + outboundPort: int32(5002), + logLevel: "debug,linkerd2_proxy=debug", + resourceRequirements: &charts.Resources{ + CPU: charts.Constraints{ + Limit: "1500m", + Request: "150m", + }, + Memory: charts.Constraints{ + Limit: "256", + Request: "120", + }, + }, + proxyUID: int64(8500), + initImage: "gcr.io/linkerd-io/proxy-init", + initImagePullPolicy: "Always", + initVersion: version.ProxyInitVersion, + inboundSkipPorts: "4222,6222", + outboundSkipPorts: "8079,8080", + }, + }, } for _, tc := range testCases { @@ -178,7 +231,7 @@ func TestConfigAccessors(t *testing.T) { t.Fatal(err) } - resourceConfig := NewResourceConfig(configs, OriginUnknown).WithKind("Deployment") + resourceConfig := NewResourceConfig(configs, OriginUnknown).WithKind("Deployment").WithNsAnnotations(testCase.nsAnnotations) if err := resourceConfig.parse(data); err != nil { t.Fatal(err) } diff --git a/test/inject/inject_test.go b/test/inject/inject_test.go index fbbdfc678..c40c04537 100644 --- a/test/inject/inject_test.go +++ b/test/inject/inject_test.go @@ -6,6 +6,9 @@ import ( "strings" "testing" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + jsonpatch "github.com/evanphx/json-patch" "github.com/linkerd/linkerd2/pkg/k8s" "github.com/linkerd/linkerd2/pkg/version" @@ -89,6 +92,72 @@ func TestInjectParams(t *testing.T) { } } +func TestNamespaceOverrideAnnotations(t *testing.T) { + // Check for Namespace level override of proxy Configurations + injectYAML, err := testutil.ReadFile("testdata/inject_test.yaml") + if err != nil { + t.Fatalf("failed to read inject test file: %s", err) + } + + injectNS := "inject-namespace-override-test" + deployName := "inject-test-terminus" + nsProxyMemReq := "50Mi" + nsProxyCPUReq := "200m" + + // Namespace level proxy configuration override + nsAnnotations := map[string]string{ + k8s.ProxyInjectAnnotation: k8s.ProxyInjectEnabled, + k8s.ProxyCPURequestAnnotation: nsProxyCPUReq, + k8s.ProxyMemoryRequestAnnotation: nsProxyMemReq, + } + + ns := TestHelper.GetTestNamespace(injectNS) + err = TestHelper.CreateNamespaceIfNotExists(ns, nsAnnotations) + if err != nil { + t.Fatalf("failed to create %s namespace: %s", ns, err) + } + + // patch injectYAML with unique name and pod annotations + // Pod Level proxy configuration override + podProxyCPUReq := "600m" + podAnnotations := map[string]string{ + k8s.ProxyCPURequestAnnotation: podProxyCPUReq, + } + + patchedYAML, err := patchDeploy(injectYAML, deployName, podAnnotations) + if err != nil { + t.Fatalf("failed to patch inject test YAML in namespace %s for deploy/%s: %s", ns, deployName, err) + } + + o, err := TestHelper.Kubectl(patchedYAML, "--namespace", ns, "create", "-f", "-") + if err != nil { + t.Fatalf("failed to create deploy/%s in namespace %s for %s: %s", deployName, ns, err, o) + } + + o, err = TestHelper.Kubectl("", "--namespace", ns, "wait", "--for=condition=available", "--timeout=30s", "deploy/"+deployName) + if err != nil { + t.Fatalf("failed to wait for condition=available for deploy/%s in namespace %s: %s: %s", deployName, ns, err, o) + } + + pods, err := TestHelper.GetPodsForDeployment(ns, deployName) + if err != nil { + t.Fatalf("failed to get pods for namespace %s: %s", ns, err) + } + + containers := pods[0].Spec.Containers + proxyContainer := getProxyContainer(containers) + + // Match the pod configuration with the namespace level overrides + if proxyContainer.Resources.Requests["memory"] != resource.MustParse(nsProxyMemReq) { + t.Fatalf("proxy memory resource request falied to match with namespace level override") + } + + // Match with proxy level override + if proxyContainer.Resources.Requests["cpu"] != resource.MustParse(podProxyCPUReq) { + t.Fatalf("proxy cpu resource request falied to match with pod level override") + } +} + func TestAnnotationPermutations(t *testing.T) { injectYAML, err := testutil.ReadFile("testdata/inject_test.yaml") if err != nil { @@ -287,3 +356,15 @@ func validateInject(actual, fixtureFile string) error { return nil } + +// Get Proxy Container from Containers +func getProxyContainer(containers []v1.Container) *v1.Container { + for _, c := range containers { + container := c + if container.Name == k8s.ProxyContainerName { + return &container + } + } + + return nil +}