mirror of https://github.com/linkerd/linkerd2.git
371 lines
11 KiB
Go
371 lines
11 KiB
Go
package inject
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"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"
|
|
"github.com/linkerd/linkerd2/testutil"
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
//////////////////////
|
|
/// TEST SETUP ///
|
|
//////////////////////
|
|
|
|
var TestHelper *testutil.TestHelper
|
|
|
|
func TestMain(m *testing.M) {
|
|
TestHelper = testutil.NewTestHelper()
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
//////////////////////
|
|
/// TEST EXECUTION ///
|
|
//////////////////////
|
|
|
|
func TestInject(t *testing.T) {
|
|
cmd := []string{"inject",
|
|
"--manual",
|
|
"--linkerd-namespace=fake-ns",
|
|
"--disable-identity",
|
|
"--ignore-cluster",
|
|
"--proxy-version=proxy-version",
|
|
"--proxy-image=proxy-image",
|
|
"--init-image=init-image",
|
|
"testdata/inject_test.yaml",
|
|
}
|
|
out, stderr, err := TestHelper.LinkerdRun(cmd...)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v: %s", stderr, err)
|
|
}
|
|
|
|
err = validateInject(out, "injected_default.golden")
|
|
if err != nil {
|
|
t.Fatalf("Received unexpected output\n%s", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestInjectParams(t *testing.T) {
|
|
// TODO: test config.linkerd.io/proxy-version
|
|
cmd := []string{"inject",
|
|
"--manual",
|
|
"--linkerd-namespace=fake-ns",
|
|
"--disable-identity",
|
|
"--disable-tap",
|
|
"--ignore-cluster",
|
|
"--proxy-version=proxy-version",
|
|
"--proxy-image=proxy-image",
|
|
"--init-image=init-image",
|
|
"--image-pull-policy=Never",
|
|
"--control-port=123",
|
|
"--skip-inbound-ports=234,345",
|
|
"--skip-outbound-ports=456,567",
|
|
"--inbound-port=678",
|
|
"--admin-port=789",
|
|
"--outbound-port=890",
|
|
"--proxy-cpu-request=10m",
|
|
"--proxy-memory-request=10Mi",
|
|
"--proxy-cpu-limit=20m",
|
|
"--proxy-memory-limit=20Mi",
|
|
"--proxy-uid=1337",
|
|
"--proxy-log-level=warn",
|
|
"--enable-external-profiles",
|
|
"testdata/inject_test.yaml",
|
|
}
|
|
|
|
out, stderr, err := TestHelper.LinkerdRun(cmd...)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v: %s", stderr, err)
|
|
}
|
|
|
|
err = validateInject(out, "injected_params.golden")
|
|
if err != nil {
|
|
t.Fatalf("Received unexpected output\n%s", err.Error())
|
|
}
|
|
}
|
|
|
|
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 {
|
|
t.Fatalf("failed to read inject test file: %s", err)
|
|
}
|
|
|
|
injectNS := "inject-test"
|
|
deployName := "inject-test-terminus"
|
|
|
|
injectAnnotations := []string{"", k8s.ProxyInjectDisabled, k8s.ProxyInjectEnabled}
|
|
|
|
// deploy
|
|
for _, nsAnnotation := range injectAnnotations {
|
|
nsPrefix := injectNS
|
|
nsAnnotations := map[string]string{}
|
|
if nsAnnotation != "" {
|
|
nsAnnotations[k8s.ProxyInjectAnnotation] = nsAnnotation
|
|
nsPrefix = fmt.Sprintf("%s-%s", nsPrefix, nsAnnotation)
|
|
}
|
|
ns := TestHelper.GetTestNamespace(nsPrefix)
|
|
|
|
err = TestHelper.CreateNamespaceIfNotExists(ns, nsAnnotations)
|
|
if err != nil {
|
|
t.Fatalf("failed to create %s namespace with annotation %s: %s", ns, nsAnnotation, err)
|
|
}
|
|
|
|
for _, podAnnotation := range injectAnnotations {
|
|
// patch injectYAML with unique name and pod annotations
|
|
name := deployName
|
|
podAnnotations := map[string]string{}
|
|
if podAnnotation != "" {
|
|
podAnnotations[k8s.ProxyInjectAnnotation] = podAnnotation
|
|
name = fmt.Sprintf("%s-%s", name, podAnnotation)
|
|
}
|
|
|
|
patchedYAML, err := patchDeploy(injectYAML, name, podAnnotations)
|
|
if err != nil {
|
|
t.Fatalf("failed to patch inject test YAML in namespace %s for deploy/%s: %s", ns, name, 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", name, ns, err, o)
|
|
}
|
|
}
|
|
}
|
|
|
|
containerName := "bb-terminus"
|
|
|
|
// check for successful deploy
|
|
for _, nsAnnotation := range injectAnnotations {
|
|
nsPrefix := injectNS
|
|
if nsAnnotation != "" {
|
|
nsPrefix = fmt.Sprintf("%s-%s", nsPrefix, nsAnnotation)
|
|
}
|
|
ns := TestHelper.GetTestNamespace(nsPrefix)
|
|
|
|
for _, podAnnotation := range injectAnnotations {
|
|
name := deployName
|
|
if podAnnotation != "" {
|
|
name = fmt.Sprintf("%s-%s", name, podAnnotation)
|
|
}
|
|
|
|
o, err := TestHelper.Kubectl("", "--namespace", ns, "wait", "--for=condition=available", "--timeout=30s", "deploy/"+name)
|
|
if err != nil {
|
|
t.Fatalf("failed to wait for condition=available for deploy/%s in namespace %s: %s: %s", name, ns, err, o)
|
|
}
|
|
|
|
pods, err := TestHelper.GetPodsForDeployment(ns, name)
|
|
if err != nil {
|
|
t.Fatalf("failed to get pods for namespace %s: %s", ns, err)
|
|
}
|
|
|
|
if len(pods) != 1 {
|
|
t.Fatalf("expected 1 pod for namespace %s, got %d", ns, len(pods))
|
|
}
|
|
|
|
shouldBeInjected := false
|
|
switch nsAnnotation {
|
|
case "", k8s.ProxyInjectDisabled:
|
|
switch podAnnotation {
|
|
case k8s.ProxyInjectEnabled:
|
|
shouldBeInjected = true
|
|
}
|
|
case k8s.ProxyInjectEnabled:
|
|
switch podAnnotation {
|
|
case "", k8s.ProxyInjectEnabled:
|
|
shouldBeInjected = true
|
|
}
|
|
}
|
|
|
|
containers := pods[0].Spec.Containers
|
|
initContainers := pods[0].Spec.InitContainers
|
|
|
|
if shouldBeInjected {
|
|
if len(containers) != 2 {
|
|
t.Fatalf("expected 2 containers for pod %s/%s, got %d", ns, pods[0].GetName(), len(containers))
|
|
}
|
|
if containers[0].Name != containerName && containers[1].Name != containerName {
|
|
t.Fatalf("expected bb-terminus container in pod %s/%s, got %+v", ns, pods[0].GetName(), containers[0])
|
|
}
|
|
if containers[0].Name != k8s.ProxyContainerName && containers[1].Name != k8s.ProxyContainerName {
|
|
t.Fatalf("expected %s container in pod %s/%s, got %+v", ns, pods[0].GetName(), k8s.ProxyContainerName, containers[0])
|
|
}
|
|
if len(initContainers) != 1 {
|
|
t.Fatalf("expected 1 init container for pod %s/%s, got %d", ns, pods[0].GetName(), len(initContainers))
|
|
}
|
|
if initContainers[0].Name != k8s.InitContainerName {
|
|
t.Fatalf("expected %s init container in pod %s/%s, got %+v", ns, pods[0].GetName(), k8s.InitContainerName, initContainers[0])
|
|
}
|
|
} else {
|
|
if len(containers) != 1 {
|
|
t.Fatalf("expected 1 container for pod %s/%s, got %d", ns, pods[0].GetName(), len(containers))
|
|
}
|
|
if containers[0].Name != containerName {
|
|
t.Fatalf("expected bb-terminus container in pod %s/%s, got %s", ns, pods[0].GetName(), containers[0].Name)
|
|
}
|
|
if len(initContainers) != 0 {
|
|
t.Fatalf("expected 0 init containers for pod %s/%s, got %d", ns, pods[0].GetName(), len(initContainers))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func applyPatch(in string, patchJSON []byte) (string, error) {
|
|
patch, err := jsonpatch.DecodePatch(patchJSON)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
json, err := yaml.YAMLToJSON([]byte(in))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
patched, err := patch.Apply(json)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(patched), nil
|
|
}
|
|
|
|
func patchDeploy(in string, name string, annotations map[string]string) (string, error) {
|
|
ops := []string{
|
|
fmt.Sprintf(`{"op": "replace", "path": "/metadata/name", "value": "%s"}`, name),
|
|
fmt.Sprintf(`{"op": "replace", "path": "/spec/selector/matchLabels/app", "value": "%s"}`, name),
|
|
fmt.Sprintf(`{"op": "replace", "path": "/spec/template/metadata/labels/app", "value": "%s"}`, name),
|
|
}
|
|
|
|
if len(annotations) > 0 {
|
|
ops = append(ops, `{"op": "add", "path": "/spec/template/metadata/annotations", "value": {}}`)
|
|
for k, v := range annotations {
|
|
ops = append(ops,
|
|
fmt.Sprintf(`{"op": "add", "path": "/spec/template/metadata/annotations/%s", "value": "%s"}`, strings.Replace(k, "/", "~1", -1), v),
|
|
)
|
|
}
|
|
}
|
|
|
|
patchJSON := []byte(fmt.Sprintf("[%s]", strings.Join(ops, ",")))
|
|
|
|
return applyPatch(in, patchJSON)
|
|
}
|
|
|
|
func useTestImageTag(in string) (string, error) {
|
|
patchOps := []string{
|
|
fmt.Sprintf(`{"op": "replace", "path": "/spec/template/metadata/annotations/linkerd.io~1created-by", "value": "linkerd/cli %s"}`, TestHelper.GetVersion()),
|
|
fmt.Sprintf(`{"op": "replace", "path": "/spec/template/metadata/annotations/linkerd.io~1proxy-version", "value": "%s"}`, TestHelper.GetVersion()),
|
|
fmt.Sprintf(`{"op": "replace", "path": "/spec/template/spec/initContainers/0/image", "value": "init-image:%s"}`, version.ProxyInitVersion),
|
|
}
|
|
|
|
patchJSON := fmt.Sprintf("[%s]", strings.Join(patchOps, ","))
|
|
return applyPatch(in, []byte(patchJSON))
|
|
}
|
|
|
|
// validateInject is similar to `TestHelper.ValidateOutput`, but it pins the
|
|
// image tag used in some annotations and that of the proxy-init container,
|
|
// which vary from build to build.
|
|
func validateInject(actual, fixtureFile string) error {
|
|
actualPatched, err := useTestImageTag(actual)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fixture, err := testutil.ReadFile("testdata/" + fixtureFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fixturePatched, err := useTestImageTag(fixture)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if actualPatched != fixturePatched {
|
|
return fmt.Errorf(
|
|
"Expected:\n%s\nActual:\n%s", fixturePatched, actualPatched)
|
|
}
|
|
|
|
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
|
|
}
|