24
"github.com/google/go-cmp/cmp"
25
corev1 "k8s.io/api/core/v1"
26
knetworking "k8s.io/api/networking/v1"
27
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
"k8s.io/apimachinery/pkg/runtime"
29
"k8s.io/apimachinery/pkg/util/intstr"
30
"k8s.io/client-go/kubernetes/scheme"
33
meshconfig "istio.io/api/mesh/v1alpha1"
34
networking "istio.io/api/networking/v1alpha3"
35
"istio.io/istio/pilot/pkg/config/kube/crd"
36
"istio.io/istio/pilot/test/util"
37
"istio.io/istio/pkg/config"
38
"istio.io/istio/pkg/config/mesh"
39
"istio.io/istio/pkg/kube"
40
"istio.io/istio/pkg/kube/kclient"
41
"istio.io/istio/pkg/test"
44
func TestGoldenConversion(t *testing.T) {
45
cases := []string{"simple", "tls", "overlay", "tls-no-secret"}
46
for _, tt := range cases {
47
t.Run(tt, func(t *testing.T) {
48
input, err := readConfig(t, fmt.Sprintf("testdata/%s.yaml", tt))
52
serviceLister := createFakeClient(t)
53
cfgs := map[string]*config.Config{}
54
for _, obj := range input {
55
ingress := obj.(*knetworking.Ingress)
56
ConvertIngressVirtualService(*ingress, "mydomain", cfgs, serviceLister)
58
ordered := []config.Config{}
59
for _, v := range cfgs {
60
ordered = append(ordered, *v)
62
for _, obj := range input {
63
ingress := obj.(*knetworking.Ingress)
64
m := mesh.DefaultMeshConfig()
65
gws := ConvertIngressV1alpha3(*ingress, m, "mydomain")
66
ordered = append(ordered, gws)
69
sort.Slice(ordered, func(i, j int) bool {
70
return ordered[i].Name < ordered[j].Name
72
output := marshalYaml(t, ordered)
73
goldenFile := fmt.Sprintf("testdata/%s.yaml.golden", tt)
75
if err := os.WriteFile(goldenFile, output, 0o644); err != nil {
79
expected, err := os.ReadFile(goldenFile)
83
if diff := cmp.Diff(expected, output); diff != "" {
84
t.Fatalf("Diff:\n%s", diff)
91
func marshalYaml(t *testing.T, cl []config.Config) []byte {
94
separator := []byte("---\n")
95
for _, config := range cl {
96
obj, err := crd.ConvertConfig(config)
98
t.Fatalf("Could not decode %v: %v", config.Name, err)
100
bytes, err := yaml.Marshal(obj)
102
t.Fatalf("Could not convert %v to YAML: %v", config, err)
104
result = append(result, bytes...)
105
result = append(result, separator...)
110
func readConfig(t *testing.T, filename string) ([]runtime.Object, error) {
113
data, err := os.ReadFile(filename)
115
t.Fatalf("failed to read input yaml file: %v", err)
117
var varr []runtime.Object
118
for _, yml := range strings.Split(string(data), "\n---") {
119
obj, _, err := scheme.Codecs.UniversalDeserializer().Decode([]byte(yml), nil, nil)
123
varr = append(varr, obj)
129
func TestConversion(t *testing.T) {
130
prefix := knetworking.PathTypePrefix
131
exact := knetworking.PathTypeExact
133
ingress := knetworking.Ingress{
134
ObjectMeta: metav1.ObjectMeta{
137
Spec: knetworking.IngressSpec{
138
Rules: []knetworking.IngressRule{
141
IngressRuleValue: knetworking.IngressRuleValue{
142
HTTP: &knetworking.HTTPIngressRuleValue{
143
Paths: []knetworking.HTTPIngressPath{
146
Backend: knetworking.IngressBackend{
147
Service: &knetworking.IngressServiceBackend{
149
Port: knetworking.ServiceBackendPort{Number: 8000},
156
Backend: knetworking.IngressBackend{
157
Service: &knetworking.IngressServiceBackend{
159
Port: knetworking.ServiceBackendPort{Number: 8000},
168
Host: "my2.host.com",
169
IngressRuleValue: knetworking.IngressRuleValue{
170
HTTP: &knetworking.HTTPIngressRuleValue{
171
Paths: []knetworking.HTTPIngressPath{
174
Backend: knetworking.IngressBackend{
175
Service: &knetworking.IngressServiceBackend{
177
Port: knetworking.ServiceBackendPort{Number: 8000},
186
Host: "my3.host.com",
187
IngressRuleValue: knetworking.IngressRuleValue{
188
HTTP: &knetworking.HTTPIngressRuleValue{
189
Paths: []knetworking.HTTPIngressPath{
192
Backend: knetworking.IngressBackend{
193
Service: &knetworking.IngressServiceBackend{
195
Port: knetworking.ServiceBackendPort{Number: 8000},
204
Host: "my4.host.com",
205
IngressRuleValue: knetworking.IngressRuleValue{
206
HTTP: &knetworking.HTTPIngressRuleValue{
207
Paths: []knetworking.HTTPIngressPath{
210
Backend: knetworking.IngressBackend{
211
Service: &knetworking.IngressServiceBackend{
213
Port: knetworking.ServiceBackendPort{Number: 8000},
224
ingress2 := knetworking.Ingress{
225
ObjectMeta: metav1.ObjectMeta{
228
Spec: knetworking.IngressSpec{
229
Rules: []knetworking.IngressRule{
232
IngressRuleValue: knetworking.IngressRuleValue{
233
HTTP: &knetworking.HTTPIngressRuleValue{
234
Paths: []knetworking.HTTPIngressPath{
237
Backend: knetworking.IngressBackend{
238
Service: &knetworking.IngressServiceBackend{
240
Port: knetworking.ServiceBackendPort{Number: 8000},
245
Path: "/test/foo/bar",
247
Backend: knetworking.IngressBackend{
248
Service: &knetworking.IngressServiceBackend{
250
Port: knetworking.ServiceBackendPort{Number: 8000},
255
Path: "/test/foo/bar",
257
Backend: knetworking.IngressBackend{
258
Service: &knetworking.IngressServiceBackend{
260
Port: knetworking.ServiceBackendPort{Number: 8000},
271
serviceLister := createFakeClient(t)
272
cfgs := map[string]*config.Config{}
273
ConvertIngressVirtualService(ingress, "mydomain", cfgs, serviceLister)
274
ConvertIngressVirtualService(ingress2, "mydomain", cfgs, serviceLister)
277
t.Error("VirtualServices, expected 4 got ", len(cfgs))
280
expectedLength := [5]int{13, 13, 9, 6, 5}
281
expectedExact := [5]bool{true, false, false, true, true}
283
for n, cfg := range cfgs {
284
vs := cfg.Spec.(*networking.VirtualService)
286
if n == "my.host.com" {
287
if vs.Hosts[0] != "my.host.com" {
288
t.Error("Unexpected host", vs)
290
if len(vs.Http) != 5 {
291
t.Error("Unexpected rules", vs.Http)
293
for i, route := range vs.Http {
294
length, exact := getMatchURILength(route.Match[0])
295
if length != expectedLength[i] || exact != expectedExact[i] {
296
t.Errorf("Unexpected rule at idx:%d, want {length:%d, exact:%v}, got {length:%d, exact: %v}",
297
i, expectedLength[i], expectedExact[i], length, exact)
300
} else if n == "my4.host.com" {
301
if vs.Hosts[0] != "my4.host.com" {
302
t.Error("Unexpected host", vs)
304
if len(vs.Http) != 1 {
305
t.Error("Unexpected rules", vs.Http)
307
if vs.Http[0].Match != nil {
308
t.Error("Expected HTTPMatchRequest to be nil, got {}")
314
func TestDecodeIngressRuleName(t *testing.T) {
322
{"my-ingress", 1, 2},
323
{"my-cool-ingress", 1, 2},
326
for _, c := range cases {
327
encoded := EncodeIngressRuleName(c.ingressName, c.ruleNum, c.pathNum)
328
ingressName, ruleNum, pathNum, err := decodeIngressRuleName(encoded)
330
t.Errorf("decodeIngressRuleName(%q) => error %v", encoded, err)
332
if ingressName != c.ingressName || ruleNum != c.ruleNum || pathNum != c.pathNum {
333
t.Errorf("decodeIngressRuleName(%q) => (%q, %d, %d), want (%q, %d, %d)",
335
ingressName, ruleNum, pathNum,
336
c.ingressName, c.ruleNum, c.pathNum,
342
func TestEncoding(t *testing.T) {
343
if got := EncodeIngressRuleName("name", 3, 5); got != "name-3-5" {
344
t.Errorf("unexpected ingress encoding %q", got)
352
for _, code := range cases {
353
if _, _, _, err := decodeIngressRuleName(code); err == nil {
354
t.Errorf("expected error on decoding %q", code)
359
func TestIngressClass(t *testing.T) {
360
istio := mesh.DefaultMeshConfig().IngressClass
361
ingressClassIstio := &knetworking.IngressClass{
362
ObjectMeta: metav1.ObjectMeta{
365
Spec: knetworking.IngressClassSpec{
366
Controller: IstioIngressController,
369
ingressClassOther := &knetworking.IngressClass{
370
ObjectMeta: metav1.ObjectMeta{
373
Spec: knetworking.IngressClassSpec{
379
ingressClass *knetworking.IngressClass
380
ingressMode meshconfig.MeshConfig_IngressControllerMode
384
{ingressMode: meshconfig.MeshConfig_DEFAULT, annotation: "nginx", shouldProcess: false},
385
{ingressMode: meshconfig.MeshConfig_STRICT, annotation: "nginx", shouldProcess: false},
386
{ingressMode: meshconfig.MeshConfig_OFF, annotation: istio, shouldProcess: false},
387
{ingressMode: meshconfig.MeshConfig_DEFAULT, annotation: istio, shouldProcess: true},
388
{ingressMode: meshconfig.MeshConfig_STRICT, annotation: istio, shouldProcess: true},
389
{ingressMode: meshconfig.MeshConfig_DEFAULT, annotation: "", shouldProcess: true},
390
{ingressMode: meshconfig.MeshConfig_STRICT, annotation: "", shouldProcess: false},
393
{ingressMode: meshconfig.MeshConfig_DEFAULT, ingressClass: ingressClassOther, shouldProcess: false},
394
{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: ingressClassOther, shouldProcess: false},
395
{ingressMode: meshconfig.MeshConfig_DEFAULT, ingressClass: ingressClassIstio, shouldProcess: true},
396
{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: ingressClassIstio, shouldProcess: true},
397
{ingressMode: meshconfig.MeshConfig_DEFAULT, ingressClass: nil, shouldProcess: true},
398
{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: nil, shouldProcess: false},
403
{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: ingressClassIstio, annotation: "nginx", shouldProcess: false},
404
{ingressMode: meshconfig.MeshConfig_STRICT, ingressClass: ingressClassOther, annotation: istio, shouldProcess: true},
405
{ingressMode: -1, shouldProcess: false},
408
for i, c := range cases {
410
if c.ingressClass != nil {
411
className = c.ingressClass.Name
413
t.Run(fmt.Sprintf("%d %s %s %s", i, c.ingressMode, c.annotation, className), func(t *testing.T) {
414
ing := knetworking.Ingress{
415
ObjectMeta: metav1.ObjectMeta{
416
Name: "test-ingress",
417
Namespace: "default",
418
Annotations: make(map[string]string),
420
Spec: knetworking.IngressSpec{
421
DefaultBackend: &knetworking.IngressBackend{
422
Service: &knetworking.IngressServiceBackend{
423
Name: "default-http-backend",
424
Port: knetworking.ServiceBackendPort{Number: 8000},
430
mesh := mesh.DefaultMeshConfig()
431
mesh.IngressControllerMode = c.ingressMode
433
if c.annotation != "" {
434
ing.Annotations["kubernetes.io/ingress.class"] = c.annotation
437
if c.shouldProcess != shouldProcessIngressWithClass(mesh, &ing, c.ingressClass) {
438
t.Errorf("got %v, want %v",
439
!c.shouldProcess, c.shouldProcess)
445
func TestNamedPortIngressConversion(t *testing.T) {
446
ingress := knetworking.Ingress{
447
ObjectMeta: metav1.ObjectMeta{
450
Spec: knetworking.IngressSpec{
451
Rules: []knetworking.IngressRule{
454
IngressRuleValue: knetworking.IngressRuleValue{
455
HTTP: &knetworking.HTTPIngressRuleValue{
456
Paths: []knetworking.HTTPIngressPath{
459
Backend: knetworking.IngressBackend{
460
Service: &knetworking.IngressServiceBackend{
462
Port: knetworking.ServiceBackendPort{Name: "test-svc-port"},
473
service := &corev1.Service{
474
ObjectMeta: metav1.ObjectMeta{
478
Spec: corev1.ServiceSpec{
479
Ports: []corev1.ServicePort{
481
Name: "test-svc-port",
484
TargetPort: intstr.IntOrString{
490
Selector: map[string]string{
495
serviceLister := createFakeClient(t, service)
496
cfgs := map[string]*config.Config{}
497
ConvertIngressVirtualService(ingress, "mydomain", cfgs, serviceLister)
499
t.Error("VirtualServices, expected 1 got ", len(cfgs))
501
if cfgs["host.com"] == nil {
502
t.Error("Host, found nil")
504
vs := cfgs["host.com"].Spec.(*networking.VirtualService)
505
if len(vs.Http) != 1 {
506
t.Error("HttpSpec, expected 1 got ", len(vs.Http))
509
if len(http.Route) != 1 {
510
t.Error("Route, expected 1 got ", len(http.Route))
512
route := http.Route[0]
513
if route.Destination.Port.Number != 8888 {
514
t.Error("PortNumer, expected 8888 got ", route.Destination.Port.Number)
518
func createFakeClient(t test.Failer, objects ...runtime.Object) kclient.Client[*corev1.Service] {
519
kc := kube.NewFakeClient(objects...)
520
stop := test.NewStop(t)
521
services := kclient.New[*corev1.Service](kc)
523
kube.WaitForCacheSync("test", stop, services.HasSynced)