istio
562 строки · 15.1 Кб
1// Copyright Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package ambient
16
17import (
18"fmt"
19"net/netip"
20"testing"
21
22v1 "k8s.io/api/core/v1"
23metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24
25meshapi "istio.io/api/mesh/v1alpha1"
26networking "istio.io/api/networking/v1alpha3"
27networkingclient "istio.io/client-go/pkg/apis/networking/v1alpha3"
28securityclient "istio.io/client-go/pkg/apis/security/v1beta1"
29"istio.io/istio/pilot/pkg/model"
30"istio.io/istio/pkg/config/labels"
31"istio.io/istio/pkg/config/schema/kind"
32"istio.io/istio/pkg/kube/krt"
33"istio.io/istio/pkg/network"
34"istio.io/istio/pkg/slices"
35"istio.io/istio/pkg/test/util/assert"
36"istio.io/istio/pkg/workloadapi"
37)
38
39func TestPodWorkloads(t *testing.T) {
40cases := []struct {
41name string
42inputs []any
43pod *v1.Pod
44result *workloadapi.Workload
45}{
46{
47name: "simple pod not running and not have podIP",
48inputs: []any{},
49pod: &v1.Pod{
50TypeMeta: metav1.TypeMeta{},
51ObjectMeta: metav1.ObjectMeta{
52Name: "name",
53Namespace: "ns",
54},
55Spec: v1.PodSpec{},
56Status: v1.PodStatus{
57Phase: v1.PodPending,
58},
59},
60result: nil,
61},
62{
63name: "simple pod not running but have podIP",
64inputs: []any{},
65pod: &v1.Pod{
66TypeMeta: metav1.TypeMeta{},
67ObjectMeta: metav1.ObjectMeta{
68Name: "name",
69Namespace: "ns",
70},
71Spec: v1.PodSpec{},
72Status: v1.PodStatus{
73Phase: v1.PodPending,
74PodIP: "1.2.3.4",
75},
76},
77result: &workloadapi.Workload{
78Uid: "cluster0//Pod/ns/name",
79Name: "name",
80Namespace: "ns",
81Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()},
82Network: testNW,
83CanonicalName: "name",
84CanonicalRevision: "latest",
85WorkloadType: workloadapi.WorkloadType_POD,
86WorkloadName: "name",
87Status: workloadapi.WorkloadStatus_UNHEALTHY,
88ClusterId: testC,
89},
90},
91{
92name: "simple pod not ready",
93inputs: []any{},
94pod: &v1.Pod{
95TypeMeta: metav1.TypeMeta{},
96ObjectMeta: metav1.ObjectMeta{
97Name: "name",
98Namespace: "ns",
99},
100Spec: v1.PodSpec{},
101Status: v1.PodStatus{
102Phase: v1.PodRunning,
103PodIP: "1.2.3.4",
104},
105},
106result: &workloadapi.Workload{
107Uid: "cluster0//Pod/ns/name",
108Name: "name",
109Namespace: "ns",
110Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()},
111Network: testNW,
112CanonicalName: "name",
113CanonicalRevision: "latest",
114WorkloadType: workloadapi.WorkloadType_POD,
115WorkloadName: "name",
116Status: workloadapi.WorkloadStatus_UNHEALTHY,
117ClusterId: testC,
118},
119},
120{
121name: "pod with service",
122inputs: []any{
123model.ServiceInfo{
124Service: &workloadapi.Service{
125Name: "svc",
126Namespace: "ns",
127Hostname: "hostname",
128Ports: []*workloadapi.Port{{
129ServicePort: 80,
130TargetPort: 8080,
131}},
132},
133LabelSelector: model.NewSelector(map[string]string{"app": "foo"}),
134},
135},
136pod: &v1.Pod{
137TypeMeta: metav1.TypeMeta{},
138ObjectMeta: metav1.ObjectMeta{
139Name: "name",
140Namespace: "ns",
141Labels: map[string]string{
142"app": "foo",
143},
144},
145Spec: v1.PodSpec{},
146Status: v1.PodStatus{
147Phase: v1.PodRunning,
148Conditions: podReady,
149PodIP: "1.2.3.4",
150},
151},
152result: &workloadapi.Workload{
153Uid: "cluster0//Pod/ns/name",
154Name: "name",
155Namespace: "ns",
156Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()},
157Network: testNW,
158CanonicalName: "foo",
159CanonicalRevision: "latest",
160WorkloadType: workloadapi.WorkloadType_POD,
161WorkloadName: "name",
162Status: workloadapi.WorkloadStatus_HEALTHY,
163ClusterId: testC,
164Services: map[string]*workloadapi.PortList{
165"ns/hostname": {
166Ports: []*workloadapi.Port{{
167ServicePort: 80,
168TargetPort: 8080,
169}},
170},
171},
172},
173},
174{
175name: "pod with service named ports",
176inputs: []any{
177model.ServiceInfo{
178Service: &workloadapi.Service{
179Name: "svc",
180Namespace: "ns",
181Hostname: "hostname",
182Ports: []*workloadapi.Port{
183{
184ServicePort: 80,
185TargetPort: 8080,
186},
187{
188ServicePort: 81,
189TargetPort: 0,
190},
191{
192ServicePort: 82,
193TargetPort: 0,
194},
195},
196},
197PortNames: map[int32]model.ServicePortName{
198// Not a named port
19980: {PortName: "80"},
200// Named port found in pod
20181: {PortName: "81", TargetPortName: "81-target"},
202// Named port not found in pod
20382: {PortName: "82", TargetPortName: "82-target"},
204},
205LabelSelector: model.NewSelector(map[string]string{"app": "foo"}),
206},
207},
208pod: &v1.Pod{
209TypeMeta: metav1.TypeMeta{},
210ObjectMeta: metav1.ObjectMeta{
211Name: "name",
212Namespace: "ns",
213Labels: map[string]string{
214"app": "foo",
215},
216},
217Spec: v1.PodSpec{
218Containers: []v1.Container{{Ports: []v1.ContainerPort{
219{
220Name: "81-target",
221ContainerPort: 9090,
222Protocol: v1.ProtocolTCP,
223},
224}}},
225},
226Status: v1.PodStatus{
227Phase: v1.PodRunning,
228Conditions: podReady,
229PodIP: "1.2.3.4",
230},
231},
232result: &workloadapi.Workload{
233Uid: "cluster0//Pod/ns/name",
234Name: "name",
235Namespace: "ns",
236Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()},
237Network: testNW,
238CanonicalName: "foo",
239CanonicalRevision: "latest",
240WorkloadType: workloadapi.WorkloadType_POD,
241WorkloadName: "name",
242Status: workloadapi.WorkloadStatus_HEALTHY,
243ClusterId: testC,
244Services: map[string]*workloadapi.PortList{
245"ns/hostname": {
246Ports: []*workloadapi.Port{{
247ServicePort: 80,
248TargetPort: 8080,
249}, {
250ServicePort: 81,
251TargetPort: 9090,
252}},
253},
254},
255},
256},
257}
258for _, tt := range cases {
259t.Run(tt.name, func(t *testing.T) {
260inputs := tt.inputs
261a := newAmbientUnitTest()
262AuthorizationPolicies := krt.NewStaticCollection(extractType[model.WorkloadAuthorization](&inputs))
263PeerAuths := krt.NewStaticCollection(extractType[*securityclient.PeerAuthentication](&inputs))
264Waypoints := krt.NewStaticCollection(extractType[Waypoint](&inputs))
265WorkloadServices := krt.NewStaticCollection(extractType[model.ServiceInfo](&inputs))
266MeshConfig := krt.NewStatic(&MeshConfig{slices.First(extractType[meshapi.MeshConfig](&inputs))})
267Namespaces := krt.NewStaticCollection(extractType[*v1.Namespace](&inputs))
268assert.Equal(t, len(inputs), 0, fmt.Sprintf("some inputs were not consumed: %v", inputs))
269builder := a.podWorkloadBuilder(MeshConfig, AuthorizationPolicies, PeerAuths, Waypoints, WorkloadServices, Namespaces)
270wrapper := builder(krt.TestingDummyContext{}, tt.pod)
271var res *workloadapi.Workload
272if wrapper != nil {
273res = wrapper.Workload
274}
275assert.Equal(t, res, tt.result)
276})
277}
278}
279
280func TestWorkloadEntryWorkloads(t *testing.T) {
281cases := []struct {
282name string
283inputs []any
284we *networkingclient.WorkloadEntry
285result *workloadapi.Workload
286}{
287{
288name: "we with service",
289inputs: []any{
290model.ServiceInfo{
291Service: &workloadapi.Service{
292Name: "svc",
293Namespace: "ns",
294Hostname: "hostname",
295Ports: []*workloadapi.Port{{
296ServicePort: 80,
297TargetPort: 8080,
298}},
299},
300LabelSelector: model.NewSelector(map[string]string{"app": "foo"}),
301},
302},
303we: &networkingclient.WorkloadEntry{
304TypeMeta: metav1.TypeMeta{},
305ObjectMeta: metav1.ObjectMeta{
306Name: "name",
307Namespace: "ns",
308Labels: map[string]string{
309"app": "foo",
310},
311},
312Spec: networking.WorkloadEntry{
313Address: "1.2.3.4",
314},
315},
316result: &workloadapi.Workload{
317Uid: "cluster0/networking.istio.io/WorkloadEntry/ns/name",
318Name: "name",
319Namespace: "ns",
320Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()},
321Network: testNW,
322CanonicalName: "foo",
323CanonicalRevision: "latest",
324WorkloadType: workloadapi.WorkloadType_POD,
325WorkloadName: "name",
326Status: workloadapi.WorkloadStatus_HEALTHY,
327ClusterId: testC,
328Services: map[string]*workloadapi.PortList{
329"ns/hostname": {
330Ports: []*workloadapi.Port{{
331ServicePort: 80,
332TargetPort: 8080,
333}},
334},
335},
336},
337},
338{
339name: "pod with service named ports",
340inputs: []any{
341model.ServiceInfo{
342Service: &workloadapi.Service{
343Name: "svc",
344Namespace: "ns",
345Hostname: "hostname",
346Ports: []*workloadapi.Port{
347{
348ServicePort: 80,
349TargetPort: 8080,
350},
351{
352ServicePort: 81,
353TargetPort: 0,
354},
355{
356ServicePort: 82,
357TargetPort: 0,
358},
359{
360ServicePort: 83,
361TargetPort: 0,
362},
363},
364},
365PortNames: map[int32]model.ServicePortName{
366// Not a named port
36780: {PortName: "80"},
368// Named port found in WE
36981: {PortName: "81", TargetPortName: "81-target"},
370// Named port target found in WE
37182: {PortName: "82", TargetPortName: "82-target"},
372// Named port not found in WE
37383: {PortName: "83", TargetPortName: "83-target"},
374},
375LabelSelector: model.NewSelector(map[string]string{"app": "foo"}),
376Source: kind.Service,
377},
378},
379we: &networkingclient.WorkloadEntry{
380TypeMeta: metav1.TypeMeta{},
381ObjectMeta: metav1.ObjectMeta{
382Name: "name",
383Namespace: "ns",
384Labels: map[string]string{
385"app": "foo",
386},
387},
388Spec: networking.WorkloadEntry{
389Ports: map[string]uint32{
390"81": 8180,
391"82-target": 8280,
392},
393Address: "1.2.3.4",
394},
395},
396result: &workloadapi.Workload{
397Uid: "cluster0/networking.istio.io/WorkloadEntry/ns/name",
398Name: "name",
399Namespace: "ns",
400Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()},
401Network: testNW,
402CanonicalName: "foo",
403CanonicalRevision: "latest",
404WorkloadType: workloadapi.WorkloadType_POD,
405WorkloadName: "name",
406Status: workloadapi.WorkloadStatus_HEALTHY,
407ClusterId: testC,
408Services: map[string]*workloadapi.PortList{
409"ns/hostname": {
410Ports: []*workloadapi.Port{
411{
412ServicePort: 80,
413TargetPort: 8080,
414},
415{
416ServicePort: 82,
417TargetPort: 8280,
418},
419},
420},
421},
422},
423},
424{
425name: "pod with serviceentry named ports",
426inputs: []any{
427model.ServiceInfo{
428Service: &workloadapi.Service{
429Name: "svc",
430Namespace: "ns",
431Hostname: "hostname",
432Ports: []*workloadapi.Port{
433{
434ServicePort: 80,
435TargetPort: 8080,
436},
437{
438ServicePort: 81,
439TargetPort: 0,
440},
441{
442ServicePort: 82,
443TargetPort: 0,
444},
445},
446},
447PortNames: map[int32]model.ServicePortName{
448// TargetPort explicitly set
44980: {PortName: "80"},
450// Port name found
45181: {PortName: "81"},
452// Port name not found
45382: {PortName: "82"},
454},
455LabelSelector: model.NewSelector(map[string]string{"app": "foo"}),
456Source: kind.ServiceEntry,
457},
458},
459we: &networkingclient.WorkloadEntry{
460TypeMeta: metav1.TypeMeta{},
461ObjectMeta: metav1.ObjectMeta{
462Name: "name",
463Namespace: "ns",
464Labels: map[string]string{
465"app": "foo",
466},
467},
468Spec: networking.WorkloadEntry{
469Ports: map[string]uint32{
470"81": 8180,
471},
472Address: "1.2.3.4",
473},
474},
475result: &workloadapi.Workload{
476Uid: "cluster0/networking.istio.io/WorkloadEntry/ns/name",
477Name: "name",
478Namespace: "ns",
479Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()},
480Network: testNW,
481CanonicalName: "foo",
482CanonicalRevision: "latest",
483WorkloadType: workloadapi.WorkloadType_POD,
484WorkloadName: "name",
485Status: workloadapi.WorkloadStatus_HEALTHY,
486ClusterId: testC,
487Services: map[string]*workloadapi.PortList{
488"ns/hostname": {
489Ports: []*workloadapi.Port{
490{
491ServicePort: 80,
492TargetPort: 8080,
493},
494{
495ServicePort: 81,
496TargetPort: 8180,
497},
498{
499ServicePort: 82,
500TargetPort: 82,
501},
502},
503},
504},
505},
506},
507}
508for _, tt := range cases {
509t.Run(tt.name, func(t *testing.T) {
510inputs := tt.inputs
511a := newAmbientUnitTest()
512AuthorizationPolicies := krt.NewStaticCollection(extractType[model.WorkloadAuthorization](&inputs))
513PeerAuths := krt.NewStaticCollection(extractType[*securityclient.PeerAuthentication](&inputs))
514Waypoints := krt.NewStaticCollection(extractType[Waypoint](&inputs))
515WorkloadServices := krt.NewStaticCollection(extractType[model.ServiceInfo](&inputs))
516Namespaces := krt.NewStaticCollection(extractType[*v1.Namespace](&inputs))
517MeshConfig := krt.NewStatic(&MeshConfig{slices.First(extractType[meshapi.MeshConfig](&inputs))})
518assert.Equal(t, len(inputs), 0, fmt.Sprintf("some inputs were not consumed: %v", inputs))
519builder := a.workloadEntryWorkloadBuilder(MeshConfig, AuthorizationPolicies, PeerAuths, Waypoints, WorkloadServices, Namespaces)
520wrapper := builder(krt.TestingDummyContext{}, tt.we)
521var res *workloadapi.Workload
522if wrapper != nil {
523res = wrapper.Workload
524}
525assert.Equal(t, res, tt.result)
526})
527}
528}
529
530func newAmbientUnitTest() *index {
531return &index{
532networkUpdateTrigger: krt.NewRecomputeTrigger(),
533ClusterID: testC,
534Network: func(endpointIP string, labels labels.Instance) network.ID {
535return testNW
536},
537}
538}
539
540func extractType[T any](items *[]any) []T {
541var matched []T
542var unmatched []any
543arr := *items
544for _, val := range arr {
545if c, ok := val.(T); ok {
546matched = append(matched, c)
547} else {
548unmatched = append(unmatched, val)
549}
550}
551
552*items = unmatched
553return matched
554}
555
556var podReady = []v1.PodCondition{
557{
558Type: v1.PodReady,
559Status: v1.ConditionTrue,
560LastTransitionTime: metav1.Now(),
561},
562}
563