...

Source file src/edge-infra.dev/pkg/edge/linkerd/k8s/controllers/workloadinjection/workloads/integration/integration_test.go

Documentation: edge-infra.dev/pkg/edge/linkerd/k8s/controllers/workloadinjection/workloads/integration

     1  package integration
     2  
     3  import (
     4  	"context"
     5  	"crypto/x509"
     6  	"os"
     7  	"reflect"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/linkerd/linkerd2/pkg/k8s"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	appsv1 "k8s.io/api/apps/v1"
    15  	corev1 "k8s.io/api/core/v1"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/types"
    18  
    19  	statusk8s "edge-infra.dev/pkg/edge/k8objectsutils/status"
    20  
    21  	linkerd "edge-infra.dev/pkg/edge/linkerd"
    22  	l5dv1alpha1 "edge-infra.dev/pkg/edge/linkerd/k8s/apis/linkerd/v1alpha1"
    23  	"edge-infra.dev/pkg/edge/linkerd/k8s/controllers/workloadinjection/workloads"
    24  	"edge-infra.dev/test/f2"
    25  	"edge-infra.dev/test/f2/x/ktest"
    26  	chart "edge-infra.dev/third_party/k8s/linkerd/helm"
    27  )
    28  
    29  var f f2.Framework
    30  
    31  var (
    32  	disabledAnnotationPod = createPod("disabled", "default", false, false, false, false)
    33  	terminatingPod        = createPod("terminating", "default", false, true, true, false)
    34  	hostNetworkPod        = createPod("host", "default", true, true, false, false)
    35  	normalPod             = createPod("normal", "default", false, true, false, false)
    36  	clboPod               = createPod("clbo", "default", false, true, false, true)
    37  	l5d                   = l5dv1alpha1.Linkerd{}
    38  )
    39  
    40  func TestMain(m *testing.M) {
    41  	f = f2.New(context.Background(),
    42  		f2.WithExtensions(
    43  			ktest.New(),
    44  		)).
    45  		Setup(func(ctx f2.Context) (f2.Context, error) {
    46  			k, err := ktest.FromContext(ctx)
    47  			if err != nil {
    48  				return ctx, err
    49  			}
    50  			// Override timeouts if we aren't using a live cluster
    51  			if !*k.Env.UseExistingCluster {
    52  				k.Timeout = 5 * time.Second
    53  				k.Tick = 10 * time.Millisecond
    54  			}
    55  			return ctx, nil
    56  		}).Teardown()
    57  	os.Exit(f.Run(m))
    58  }
    59  
    60  func TestRestartStates(t *testing.T) {
    61  	feature := f2.NewFeature("RestartStates").
    62  		Setup("create namespaces", func(ctx f2.Context, t *testing.T) f2.Context {
    63  			k := ktest.FromContextT(ctx, t)
    64  			ns1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test1"}}
    65  			ns2 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test2"}}
    66  			ns3 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test3"}}
    67  			require.NoError(t, k.Client.Create(ctx, ns1))
    68  			require.NoError(t, k.Client.Create(ctx, ns2))
    69  			require.NoError(t, k.Client.Create(ctx, ns3))
    70  			return ctx
    71  		}).
    72  		Setup("create deployments", func(ctx f2.Context, t *testing.T) f2.Context {
    73  			k := ktest.FromContextT(ctx, t)
    74  			deployment1 := createDeployment("deployment1", "test1", "123")
    75  			deployment2 := createDeployment("deployment2", "test2", "456")
    76  			deployment3 := createDeployment("deployment3", "test3", "789")
    77  			require.NoError(t, k.Client.Create(ctx, &deployment1))
    78  			require.NoError(t, k.Client.Create(ctx, &deployment2))
    79  			require.NoError(t, k.Client.Create(ctx, &deployment3))
    80  			return ctx
    81  		}).
    82  		Setup("create pods", func(ctx f2.Context, t *testing.T) f2.Context {
    83  			k := ktest.FromContextT(ctx, t)
    84  			pod1 := createPod("pod1", "test1", false, true, false, false)
    85  			pod1.OwnerReferences = append(pod1.OwnerReferences, metav1.OwnerReference{
    86  				Kind: reflect.TypeOf(appsv1.Deployment{}).Name(), Name: "deployment1", APIVersion: "apps/v1", UID: "123"})
    87  			pod2 := createPod("pod2", "test2", false, true, false, false)
    88  			pod2.OwnerReferences = append(pod2.OwnerReferences, metav1.OwnerReference{
    89  				Kind: reflect.TypeOf(appsv1.Deployment{}).Name(), Name: "deployment2", APIVersion: "apps/v1", UID: "456"})
    90  			pod3 := createPod("pod3", "test3", false, true, false, false)
    91  			pod3.OwnerReferences = append(pod3.OwnerReferences, metav1.OwnerReference{
    92  				Kind: reflect.TypeOf(appsv1.Deployment{}).Name(), Name: "deployment3", APIVersion: "apps/v1", UID: "789"})
    93  			require.NoError(t, k.Client.Create(ctx, &pod1))
    94  			require.NoError(t, k.Client.Create(ctx, &pod2))
    95  			require.NoError(t, k.Client.Create(ctx, &pod3))
    96  			return ctx
    97  		}).
    98  		Test("no namespaces defined on workloadinjection CR", func(ctx f2.Context, t *testing.T) f2.Context {
    99  			k := ktest.FromContextT(ctx, t)
   100  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{
   101  				ObjectMeta: metav1.ObjectMeta{Name: "linkerd", Namespace: linkerd.Namespace},
   102  				Spec: l5dv1alpha1.LinkerdWorkloadInjectionSpec{
   103  					Namespaces: []string{},
   104  				},
   105  			}
   106  			workloads, err := workloads.RestartStates(ctx, k.Client, &l5d, &workloadinjection)
   107  			assert.NoError(t, err)
   108  			assert.Equal(t, 3, len(workloads))
   109  			return ctx
   110  		}).
   111  		Test("specified namespaces define on workloadinjection CR", func(ctx f2.Context, t *testing.T) f2.Context {
   112  			k := ktest.FromContextT(ctx, t)
   113  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{
   114  				ObjectMeta: metav1.ObjectMeta{Name: "linkerd", Namespace: linkerd.Namespace},
   115  				Spec: l5dv1alpha1.LinkerdWorkloadInjectionSpec{
   116  					Namespaces: []string{"test1"},
   117  				},
   118  			}
   119  			workloads, err := workloads.RestartStates(ctx, k.Client, &l5d, &workloadinjection)
   120  			assert.NoError(t, err)
   121  			assert.Equal(t, 1, len(workloads))
   122  			return ctx
   123  		}).Feature()
   124  	f.Test(t, feature)
   125  }
   126  
   127  func TestPodRestartReason(t *testing.T) {
   128  	feature := f2.NewFeature("PodRestartReason").
   129  		Test("do not restart pod with linkerd disabled annotation", func(ctx f2.Context, t *testing.T) f2.Context {
   130  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{}
   131  			assert.Equal(t, workloads.DoNotRestart, workloads.PodRestartReason(ctx, disabledAnnotationPod, &l5d, &workloadinjection, []*x509.Certificate{}))
   132  			return ctx
   133  		}).
   134  		Test("do not restart terminating pod", func(ctx f2.Context, t *testing.T) f2.Context {
   135  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{}
   136  			assert.Equal(t, workloads.DoNotRestart, workloads.PodRestartReason(ctx, terminatingPod, &l5d, &workloadinjection, []*x509.Certificate{}))
   137  			return ctx
   138  		}).
   139  		Test("do not restart pod on host network", func(ctx f2.Context, t *testing.T) f2.Context {
   140  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{}
   141  			assert.Equal(t, workloads.DoNotRestart, workloads.PodRestartReason(ctx, hostNetworkPod, &l5d, &workloadinjection, []*x509.Certificate{}))
   142  			return ctx
   143  		}).
   144  		Test("force restart pod when force is set", func(ctx f2.Context, t *testing.T) f2.Context {
   145  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{Spec: l5dv1alpha1.LinkerdWorkloadInjectionSpec{Force: true}}
   146  			assert.Equal(t, workloads.ForceRestart, workloads.PodRestartReason(ctx, normalPod, &l5d, &workloadinjection, []*x509.Certificate{}))
   147  			return ctx
   148  		}).
   149  		Test("restart pod if linkerd proxy is missing", func(ctx f2.Context, t *testing.T) f2.Context {
   150  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{}
   151  			assert.Equal(t, workloads.ProxyMissing, workloads.PodRestartReason(ctx, normalPod, &l5d, &workloadinjection, []*x509.Certificate{}))
   152  			return ctx
   153  		}).
   154  		Test("restart pod if linkerd proxy is outdated", func(ctx f2.Context, t *testing.T) f2.Context {
   155  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{}
   156  			outdatedPod := createPod("outdated", "default", false, true, false, false)
   157  			outdatedPod.Spec.Containers = append(outdatedPod.Spec.Containers, corev1.Container{Name: k8s.ProxyContainerName, Image: "container"})
   158  			assert.Equal(t, workloads.ProxyOutdated, workloads.PodRestartReason(ctx, outdatedPod, &l5d, &workloadinjection, []*x509.Certificate{}))
   159  			return ctx
   160  		}).
   161  		Test("restart pod if identity certificate has expired", func(ctx f2.Context, t *testing.T) f2.Context {
   162  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{}
   163  			meshedPod := createPod("meshed", "default", false, true, false, false)
   164  			meshedPod.Spec.Containers = append(meshedPod.Spec.Containers, corev1.Container{Name: k8s.ProxyContainerName, Image: "container"})
   165  			meshedPod.Annotations = map[string]string{linkerd.ProxyVersionAnnotation: chart.Version}
   166  			assert.Equal(t, workloads.CertExpired, workloads.PodRestartReason(ctx, meshedPod, &l5d, &workloadinjection, []*x509.Certificate{{NotAfter: time.Now().Add(time.Minute * -1)}}))
   167  			return ctx
   168  		}).
   169  		Test("do not restart pod", func(ctx f2.Context, t *testing.T) f2.Context {
   170  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{}
   171  			meshedPod := createPod("meshed", "default", false, true, false, false)
   172  			meshedPod.Spec.Containers = append(meshedPod.Spec.Containers, corev1.Container{Name: k8s.ProxyContainerName, Image: "container"})
   173  			meshedPod.Annotations = map[string]string{linkerd.ProxyVersionAnnotation: chart.Version}
   174  			assert.Equal(t, workloads.DoNotRestart, workloads.PodRestartReason(ctx, meshedPod, &l5d, &workloadinjection, []*x509.Certificate{}))
   175  			return ctx
   176  		}).
   177  		Test("restart pod if in crashloopbackoff", func(ctx f2.Context, t *testing.T) f2.Context {
   178  			workloadinjection := l5dv1alpha1.LinkerdWorkloadInjection{}
   179  			pod := clboPod
   180  			pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{Name: k8s.ProxyContainerName, Image: "container"})
   181  			pod.Annotations = map[string]string{linkerd.ProxyVersionAnnotation: chart.Version}
   182  			assert.Equal(t, workloads.ProxyCLBO, workloads.PodRestartReason(ctx, pod, &l5d, &workloadinjection, []*x509.Certificate{}))
   183  			return ctx
   184  		}).
   185  		Feature()
   186  	f.Test(t, feature)
   187  }
   188  
   189  func TestOwningWorkload(t *testing.T) {
   190  	var k *ktest.K8s
   191  	feature := f2.NewFeature("OwningWorkload").
   192  		Test("owner reference empty", func(ctx f2.Context, t *testing.T) f2.Context {
   193  			k = ktest.FromContextT(ctx, t)
   194  			normalPod.OwnerReferences = []metav1.OwnerReference{}
   195  			workload, err := workloads.OwningWorkload(ctx, k.Client, normalPod)
   196  			assert.NoError(t, err)
   197  			assert.Nil(t, workload)
   198  			return ctx
   199  		}).
   200  		Test("owner is a replica set which doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
   201  			normalPod.OwnerReferences = []metav1.OwnerReference{
   202  				{Kind: reflect.TypeOf(appsv1.ReplicaSet{}).Name(), Name: "replica-set"},
   203  			}
   204  			workload, err := workloads.OwningWorkload(ctx, k.Client, normalPod)
   205  			assert.EqualError(t, err, "failed to get owning replicaset: replicasets.apps \"replica-set\" not found")
   206  			assert.Nil(t, workload)
   207  			return ctx
   208  		}).
   209  		Test("owner is a replica set with no owning workload", func(ctx f2.Context, t *testing.T) f2.Context {
   210  			replicaSet := createReplicaSet()
   211  			require.NoError(t, k.Client.Create(ctx, &replicaSet))
   212  			workload, err := workloads.OwningWorkload(ctx, k.Client, normalPod)
   213  			assert.EqualError(t, err, "failed to get owner ref for replicaset")
   214  			assert.Nil(t, workload)
   215  			return ctx
   216  		}).
   217  		Test("cannot find owning workload", func(ctx f2.Context, t *testing.T) f2.Context {
   218  			replicaSet := createReplicaSet()
   219  			replicaSet.OwnerReferences = []metav1.OwnerReference{
   220  				{Kind: reflect.TypeOf(appsv1.Deployment{}).Name(), Name: "deployment", APIVersion: "apps/v1", UID: "12345"},
   221  			}
   222  			require.NoError(t, k.Client.Update(ctx, &replicaSet))
   223  			workload, err := workloads.OwningWorkload(ctx, k.Client, normalPod)
   224  			assert.ErrorContains(t, err, "failed to get owning workload")
   225  			assert.Nil(t, workload)
   226  			return ctx
   227  		}).
   228  		Test("successfully return owning workload", func(ctx f2.Context, t *testing.T) f2.Context {
   229  			deployment := createDeployment("deployment", "default", "12345")
   230  			require.NoError(t, k.Client.Create(ctx, &deployment))
   231  			workload, err := workloads.OwningWorkload(ctx, k.Client, normalPod)
   232  			assert.NoError(t, err)
   233  			assert.NotNil(t, workload)
   234  			assert.Equal(t, "deployment", workload.GetName())
   235  			return ctx
   236  		}).Feature()
   237  	f.Test(t, feature)
   238  }
   239  
   240  func TestIsProxyOutdated(t *testing.T) {
   241  	feature := f2.NewFeature("IsProxyOutdated").
   242  		Test("proxy version annotation doesnt exist", func(ctx f2.Context, t *testing.T) f2.Context {
   243  			annotations := map[string]string{}
   244  			assert.True(t, workloads.IsProxyOutdated(ctx, annotations))
   245  			return ctx
   246  		}).
   247  		Test("version not equal to chart version", func(ctx f2.Context, t *testing.T) f2.Context {
   248  			annotations := map[string]string{linkerd.ProxyVersionAnnotation: "0.0.0"}
   249  			assert.True(t, workloads.IsProxyOutdated(ctx, annotations))
   250  			return ctx
   251  		}).
   252  		Test("version equal to chart version", func(ctx f2.Context, t *testing.T) f2.Context {
   253  			annotations := map[string]string{linkerd.ProxyVersionAnnotation: chart.Version}
   254  			assert.False(t, workloads.IsProxyOutdated(ctx, annotations))
   255  			return ctx
   256  		}).Feature()
   257  	f.Test(t, feature)
   258  }
   259  
   260  func TestProxyExists(t *testing.T) {
   261  	feature := f2.NewFeature("ProxyExists").
   262  		Test("proxy doesn't exist", func(ctx f2.Context, t *testing.T) f2.Context {
   263  			pod := createPod("no-proxy", "default", false, true, false, false)
   264  			assert.False(t, linkerd.ProxyExists(&pod))
   265  			return ctx
   266  		}).
   267  		Test("proxy does exist (injected)", func(ctx f2.Context, t *testing.T) f2.Context {
   268  			pod := createPod("proxy", "default", false, true, false, false)
   269  			pod.Spec.Containers = append(pod.Spec.Containers, corev1.Container{Name: k8s.ProxyContainerName, Image: "container"})
   270  			assert.True(t, linkerd.ProxyExists(&pod))
   271  			return ctx
   272  		}).
   273  		Test("proxy does exist (native)", func(ctx f2.Context, t *testing.T) f2.Context {
   274  			pod := createPod("proxy", "default", false, true, false, false)
   275  			pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{Name: k8s.ProxyContainerName, Image: "container"})
   276  			assert.True(t, linkerd.ProxyExists(&pod))
   277  			return ctx
   278  		}).Feature()
   279  	f.Test(t, feature)
   280  }
   281  
   282  func createPod(name, namespace string, hostNetwork, enabled, terminating, clbo bool) corev1.Pod {
   283  	proxy := "enabled"
   284  	deletionTimestamp := &metav1.Time{Time: time.Now()}
   285  	if !enabled {
   286  		proxy = "disabled"
   287  	}
   288  	if !terminating {
   289  		deletionTimestamp = nil
   290  	}
   291  	pod := corev1.Pod{
   292  		TypeMeta: metav1.TypeMeta{
   293  			APIVersion: "v1",
   294  			Kind:       "Pod",
   295  		},
   296  		ObjectMeta: metav1.ObjectMeta{
   297  			Name:              name,
   298  			Namespace:         namespace,
   299  			Labels:            map[string]string{},
   300  			Annotations:       map[string]string{linkerd.InjectionAnnotation: proxy},
   301  			DeletionTimestamp: deletionTimestamp,
   302  		},
   303  		Spec: corev1.PodSpec{
   304  			HostNetwork: hostNetwork,
   305  			Containers:  []corev1.Container{{Name: "dummy", Image: "container"}},
   306  		},
   307  	}
   308  	if clbo {
   309  		pod.Status.InitContainerStatuses = []corev1.ContainerStatus{
   310  			{
   311  				Name: k8s.ProxyContainerName,
   312  				State: corev1.ContainerState{
   313  					Waiting: &corev1.ContainerStateWaiting{
   314  						Reason: statusk8s.CrashLoopBackOff,
   315  					},
   316  				},
   317  				RestartCount: 1000,
   318  			},
   319  		}
   320  	}
   321  	return pod
   322  }
   323  
   324  func createReplicaSet() appsv1.ReplicaSet {
   325  	return appsv1.ReplicaSet{
   326  		ObjectMeta: metav1.ObjectMeta{
   327  			Name:      "replica-set",
   328  			Namespace: "default",
   329  		},
   330  		Spec: appsv1.ReplicaSetSpec{
   331  			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"hello": "world"}},
   332  			Template: corev1.PodTemplateSpec{
   333  				ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"hello": "world"}},
   334  				Spec: corev1.PodSpec{
   335  					Containers: []corev1.Container{{Name: "dummy", Image: "container"}},
   336  				},
   337  			},
   338  		},
   339  	}
   340  }
   341  
   342  func createDeployment(name, namespace, uid string) appsv1.Deployment {
   343  	return appsv1.Deployment{
   344  		ObjectMeta: metav1.ObjectMeta{
   345  			Name:      name,
   346  			Namespace: namespace,
   347  			UID:       types.UID(uid),
   348  		},
   349  		Spec: appsv1.DeploymentSpec{
   350  			Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"hello": "world"}},
   351  			Template: corev1.PodTemplateSpec{
   352  				ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"hello": "world"}},
   353  				Spec: corev1.PodSpec{
   354  					Containers: []corev1.Container{{Name: "dummy", Image: "container"}},
   355  				},
   356  			},
   357  		},
   358  	}
   359  }
   360  

View as plain text