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
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