istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/config/kube/ingress/controller_test.go (about)

     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  
    15  package ingress
    16  
    17  import (
    18  	"testing"
    19  	"time"
    20  
    21  	corev1 "k8s.io/api/core/v1"
    22  	net "k8s.io/api/networking/v1"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  
    25  	meshconfig "istio.io/api/mesh/v1alpha1"
    26  	"istio.io/istio/pilot/pkg/model"
    27  	kubecontroller "istio.io/istio/pilot/pkg/serviceregistry/kube/controller"
    28  	"istio.io/istio/pkg/config"
    29  	"istio.io/istio/pkg/config/mesh"
    30  	"istio.io/istio/pkg/config/schema/gvk"
    31  	"istio.io/istio/pkg/kube"
    32  	"istio.io/istio/pkg/kube/kclient/clienttest"
    33  	"istio.io/istio/pkg/util/sets"
    34  )
    35  
    36  func newFakeController() (model.ConfigStoreController, kube.Client) {
    37  	meshHolder := mesh.NewTestWatcher(&meshconfig.MeshConfig{
    38  		IngressControllerMode: meshconfig.MeshConfig_DEFAULT,
    39  	})
    40  	fakeClient := kube.NewFakeClient()
    41  	return NewController(fakeClient, meshHolder, kubecontroller.Options{}), fakeClient
    42  }
    43  
    44  func TestIngressController(t *testing.T) {
    45  	ingress1 := net.Ingress{
    46  		ObjectMeta: metav1.ObjectMeta{
    47  			Namespace: "mock", // goes into backend full name
    48  			Name:      "test",
    49  		},
    50  		Spec: net.IngressSpec{
    51  			Rules: []net.IngressRule{
    52  				{
    53  					Host: "my.host.com",
    54  					IngressRuleValue: net.IngressRuleValue{
    55  						HTTP: &net.HTTPIngressRuleValue{
    56  							Paths: []net.HTTPIngressPath{
    57  								{
    58  									Path: "/test",
    59  									Backend: net.IngressBackend{
    60  										Service: &net.IngressServiceBackend{
    61  											Name: "foo",
    62  											Port: net.ServiceBackendPort{
    63  												Number: 8000,
    64  											},
    65  										},
    66  									},
    67  								},
    68  							},
    69  						},
    70  					},
    71  				},
    72  				{
    73  					Host: "my2.host.com",
    74  					IngressRuleValue: net.IngressRuleValue{
    75  						HTTP: &net.HTTPIngressRuleValue{
    76  							Paths: []net.HTTPIngressPath{
    77  								{
    78  									Path: "/test1.*",
    79  									Backend: net.IngressBackend{
    80  										Service: &net.IngressServiceBackend{
    81  											Name: "bar",
    82  											Port: net.ServiceBackendPort{
    83  												Number: 8000,
    84  											},
    85  										},
    86  									},
    87  								},
    88  							},
    89  						},
    90  					},
    91  				},
    92  				{
    93  					Host: "my3.host.com",
    94  					IngressRuleValue: net.IngressRuleValue{
    95  						HTTP: &net.HTTPIngressRuleValue{
    96  							Paths: []net.HTTPIngressPath{
    97  								{
    98  									Path: "/test/*",
    99  									Backend: net.IngressBackend{
   100  										Service: &net.IngressServiceBackend{
   101  											Name: "bar",
   102  											Port: net.ServiceBackendPort{
   103  												Number: 8000,
   104  											},
   105  										},
   106  									},
   107  								},
   108  							},
   109  						},
   110  					},
   111  				},
   112  			},
   113  		},
   114  	}
   115  
   116  	ingress2 := net.Ingress{
   117  		ObjectMeta: metav1.ObjectMeta{
   118  			Namespace: "mock",
   119  			Name:      "test",
   120  		},
   121  		Spec: net.IngressSpec{
   122  			Rules: []net.IngressRule{
   123  				{
   124  					Host: "my.host.com",
   125  					IngressRuleValue: net.IngressRuleValue{
   126  						HTTP: &net.HTTPIngressRuleValue{
   127  							Paths: []net.HTTPIngressPath{
   128  								{
   129  									Path: "/test2",
   130  									Backend: net.IngressBackend{
   131  										Service: &net.IngressServiceBackend{
   132  											Name: "foo",
   133  											Port: net.ServiceBackendPort{
   134  												Number: 8000,
   135  											},
   136  										},
   137  									},
   138  								},
   139  							},
   140  						},
   141  					},
   142  				},
   143  			},
   144  		},
   145  	}
   146  
   147  	controller, client := newFakeController()
   148  	ingress := clienttest.NewWriter[*net.Ingress](t, client)
   149  	configCh := make(chan config.Config)
   150  
   151  	configHandler := func(_, curr config.Config, event model.Event) {
   152  		configCh <- curr
   153  	}
   154  
   155  	wait := func() config.Config {
   156  		select {
   157  		case x := <-configCh:
   158  			return x
   159  		case <-time.After(time.Second * 10):
   160  			t.Fatalf("timed out waiting for config")
   161  		}
   162  		return config.Config{}
   163  	}
   164  
   165  	controller.RegisterEventHandler(gvk.VirtualService, configHandler)
   166  	stopCh := make(chan struct{})
   167  	go controller.Run(stopCh)
   168  	defer close(stopCh)
   169  
   170  	client.RunAndWait(stopCh)
   171  
   172  	ingress.Create(&ingress1)
   173  	vs := wait()
   174  	if vs.Name != ingress1.Name+"-"+"virtualservice" || vs.Namespace != ingress1.Namespace {
   175  		t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name)
   176  	}
   177  	ingress.Update(&ingress2)
   178  	vs = wait()
   179  	if vs.Name != ingress1.Name+"-"+"virtualservice" || vs.Namespace != ingress1.Namespace {
   180  		t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name)
   181  	}
   182  }
   183  
   184  func TestIngressControllerWithPortName(t *testing.T) {
   185  	ingressConfig := net.Ingress{
   186  		ObjectMeta: metav1.ObjectMeta{
   187  			Namespace: "mock",
   188  			Name:      "test",
   189  		},
   190  		Spec: net.IngressSpec{
   191  			Rules: []net.IngressRule{
   192  				{
   193  					Host: "my.host.com",
   194  					IngressRuleValue: net.IngressRuleValue{
   195  						HTTP: &net.HTTPIngressRuleValue{
   196  							Paths: []net.HTTPIngressPath{
   197  								{
   198  									Path: "/foo",
   199  									Backend: net.IngressBackend{
   200  										Service: &net.IngressServiceBackend{
   201  											Name: "foo",
   202  											Port: net.ServiceBackendPort{
   203  												Number: 8000,
   204  											},
   205  										},
   206  									},
   207  								},
   208  							},
   209  						},
   210  					},
   211  				},
   212  				{
   213  					Host: "my2.host.com",
   214  					IngressRuleValue: net.IngressRuleValue{
   215  						HTTP: &net.HTTPIngressRuleValue{
   216  							Paths: []net.HTTPIngressPath{
   217  								{
   218  									Path: "/bar",
   219  									Backend: net.IngressBackend{
   220  										Service: &net.IngressServiceBackend{
   221  											Name: "bar",
   222  											Port: net.ServiceBackendPort{
   223  												Name: "http",
   224  											},
   225  										},
   226  									},
   227  								},
   228  							},
   229  						},
   230  					},
   231  				},
   232  			},
   233  		},
   234  	}
   235  
   236  	serviceConfig := corev1.Service{
   237  		ObjectMeta: metav1.ObjectMeta{
   238  			Namespace: "mock",
   239  			Name:      "bar",
   240  		},
   241  		Spec: corev1.ServiceSpec{
   242  			Ports: []corev1.ServicePort{
   243  				{
   244  					Name: "http",
   245  					Port: 8080,
   246  				},
   247  			},
   248  		},
   249  	}
   250  
   251  	controller, client := newFakeController()
   252  	ingress := clienttest.NewWriter[*net.Ingress](t, client)
   253  	service := clienttest.NewWriter[*corev1.Service](t, client)
   254  	configCh := make(chan config.Config)
   255  
   256  	configHandler := func(_, curr config.Config, event model.Event) {
   257  		configCh <- curr
   258  	}
   259  
   260  	wait := func() config.Config {
   261  		select {
   262  		case x := <-configCh:
   263  			return x
   264  		case <-time.After(time.Second * 10):
   265  			t.Fatalf("timed out waiting for config")
   266  		}
   267  		return config.Config{}
   268  	}
   269  
   270  	controller.RegisterEventHandler(gvk.VirtualService, configHandler)
   271  	stopCh := make(chan struct{})
   272  	go controller.Run(stopCh)
   273  	defer close(stopCh)
   274  
   275  	client.RunAndWait(stopCh)
   276  
   277  	// First create ingress.
   278  	ingress.Create(&ingressConfig)
   279  	vs := wait()
   280  	if vs.Name != ingressConfig.Name+"-"+"virtualservice" || vs.Namespace != ingressConfig.Namespace {
   281  		t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name)
   282  	}
   283  
   284  	// Then we create service.
   285  	service.Create(&serviceConfig)
   286  	vs = wait()
   287  	if vs.Name != ingressConfig.Name+"-"+"virtualservice" || vs.Namespace != ingressConfig.Namespace {
   288  		t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name)
   289  	}
   290  
   291  	// We change service port number.
   292  	serviceConfig.Spec.Ports[0].Port = 8090
   293  	service.Update(&serviceConfig)
   294  	vs = wait()
   295  	if vs.Name != ingressConfig.Name+"-"+"virtualservice" || vs.Namespace != ingressConfig.Namespace {
   296  		t.Errorf("received unecpected config %v/%v", vs.Namespace, vs.Name)
   297  	}
   298  }
   299  
   300  func TestExtractServicesByPortNameType(t *testing.T) {
   301  	testCases := []struct {
   302  		name   string
   303  		input  net.Ingress
   304  		expect sets.String
   305  	}{
   306  		{
   307  			name: "has no port name",
   308  			input: net.Ingress{
   309  				ObjectMeta: metav1.ObjectMeta{
   310  					Namespace: "ingress",
   311  					Name:      "test",
   312  				},
   313  				Spec: net.IngressSpec{
   314  					Rules: []net.IngressRule{
   315  						{
   316  							Host: "my.host.com",
   317  							IngressRuleValue: net.IngressRuleValue{
   318  								HTTP: &net.HTTPIngressRuleValue{
   319  									Paths: []net.HTTPIngressPath{
   320  										{
   321  											Path: "/test",
   322  											Backend: net.IngressBackend{
   323  												Service: &net.IngressServiceBackend{
   324  													Name: "foo",
   325  													Port: net.ServiceBackendPort{
   326  														Number: 8000,
   327  													},
   328  												},
   329  											},
   330  										},
   331  									},
   332  								},
   333  							},
   334  						},
   335  					},
   336  				},
   337  			},
   338  			expect: nil,
   339  		},
   340  		{
   341  			name: "has no port name",
   342  			input: net.Ingress{
   343  				ObjectMeta: metav1.ObjectMeta{
   344  					Namespace: "ingress",
   345  					Name:      "test",
   346  				},
   347  				Spec: net.IngressSpec{
   348  					Rules: []net.IngressRule{
   349  						{
   350  							Host: "my.host.com",
   351  							IngressRuleValue: net.IngressRuleValue{
   352  								HTTP: &net.HTTPIngressRuleValue{
   353  									Paths: []net.HTTPIngressPath{
   354  										{
   355  											Path: "/test",
   356  											Backend: net.IngressBackend{
   357  												Service: &net.IngressServiceBackend{
   358  													Name: "foo",
   359  													Port: net.ServiceBackendPort{
   360  														Number: 8000,
   361  													},
   362  												},
   363  											},
   364  										},
   365  										{
   366  											Path: "/bar",
   367  											Backend: net.IngressBackend{
   368  												Service: &net.IngressServiceBackend{
   369  													Name: "bar",
   370  													Port: net.ServiceBackendPort{
   371  														Name: "http",
   372  													},
   373  												},
   374  											},
   375  										},
   376  									},
   377  								},
   378  							},
   379  						},
   380  						{
   381  							Host: "my1.host.com",
   382  							IngressRuleValue: net.IngressRuleValue{
   383  								HTTP: &net.HTTPIngressRuleValue{
   384  									Paths: []net.HTTPIngressPath{
   385  										{
   386  											Path: "/mock",
   387  											Backend: net.IngressBackend{
   388  												Service: &net.IngressServiceBackend{
   389  													Name: "mock",
   390  													Port: net.ServiceBackendPort{
   391  														Name: "grpc",
   392  													},
   393  												},
   394  											},
   395  										},
   396  									},
   397  								},
   398  							},
   399  						},
   400  					},
   401  				},
   402  			},
   403  			expect: sets.String{}.InsertAll("ingress/bar", "ingress/mock"),
   404  		},
   405  	}
   406  
   407  	for _, testCase := range testCases {
   408  		t.Run(testCase.name, func(t *testing.T) {
   409  			if !testCase.expect.Equals(extractServicesByPortNameType(&testCase.input)) {
   410  				t.Fatal("should be equal.")
   411  			}
   412  		})
   413  	}
   414  }
   415  
   416  func TestExtractPorts(t *testing.T) {
   417  	ports := []corev1.ServicePort{
   418  		{
   419  			Port: 80,
   420  		},
   421  		{
   422  			Name: "http",
   423  			Port: 8080,
   424  		},
   425  	}
   426  
   427  	expect := sets.New("80|", "8080|http")
   428  	if !expect.Equals(extractPorts(ports)) {
   429  		t.Fatal("should be equal")
   430  	}
   431  }