istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/network_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 controller
    16  
    17  import (
    18  	"fmt"
    19  	"sync"
    20  	"testing"
    21  	"time"
    22  
    23  	corev1 "k8s.io/api/core/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  	k8sv1 "sigs.k8s.io/gateway-api/apis/v1"
    27  	"sigs.k8s.io/gateway-api/apis/v1beta1"
    28  
    29  	"istio.io/api/label"
    30  	meshconfig "istio.io/api/mesh/v1alpha1"
    31  	"istio.io/istio/pilot/pkg/features"
    32  	"istio.io/istio/pilot/pkg/model"
    33  	"istio.io/istio/pkg/config/constants"
    34  	"istio.io/istio/pkg/config/mesh"
    35  	"istio.io/istio/pkg/config/schema/gvr"
    36  	"istio.io/istio/pkg/kube/controllers"
    37  	"istio.io/istio/pkg/kube/kclient"
    38  	"istio.io/istio/pkg/kube/kclient/clienttest"
    39  	"istio.io/istio/pkg/test"
    40  	"istio.io/istio/pkg/test/util/assert"
    41  	"istio.io/istio/pkg/test/util/retry"
    42  	"istio.io/istio/pkg/util/sets"
    43  )
    44  
    45  func TestNetworkUpdateTriggers(t *testing.T) {
    46  	test.SetForTest(t, &features.MultiNetworkGatewayAPI, true)
    47  	meshNetworks := mesh.NewFixedNetworksWatcher(nil)
    48  	c, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{
    49  		ClusterID:       constants.DefaultClusterName,
    50  		NetworksWatcher: meshNetworks,
    51  		DomainSuffix:    "cluster.local",
    52  		CRDs:            []schema.GroupVersionResource{gvr.KubernetesGateway},
    53  	})
    54  
    55  	if len(c.NetworkGateways()) != 0 {
    56  		t.Fatal("did not expect any gateways yet")
    57  	}
    58  
    59  	notifyCh := make(chan struct{}, 1)
    60  	var (
    61  		gwMu sync.Mutex
    62  		gws  []model.NetworkGateway
    63  	)
    64  	setGws := func(v []model.NetworkGateway) {
    65  		gwMu.Lock()
    66  		defer gwMu.Unlock()
    67  		gws = v
    68  	}
    69  	getGws := func() []model.NetworkGateway {
    70  		gwMu.Lock()
    71  		defer gwMu.Unlock()
    72  		return gws
    73  	}
    74  
    75  	c.AppendNetworkGatewayHandler(func() {
    76  		setGws(c.NetworkGateways())
    77  		notifyCh <- struct{}{}
    78  	})
    79  	expectGateways := func(t *testing.T, expectedGws int) {
    80  		// wait for a notification
    81  		assert.ChannelHasItem(t, notifyCh)
    82  		if n := len(getGws()); n != expectedGws {
    83  			t.Errorf("expected %d gateways but got %d", expectedGws, n)
    84  		}
    85  	}
    86  
    87  	t.Run("add meshnetworks", func(t *testing.T) {
    88  		addMeshNetworksFromRegistryGateway(t, c, meshNetworks)
    89  		expectGateways(t, 2)
    90  	})
    91  	t.Run("add labeled service", func(t *testing.T) {
    92  		addLabeledServiceGateway(t, c, "nw0")
    93  		expectGateways(t, 3)
    94  	})
    95  	t.Run("update labeled service network", func(t *testing.T) {
    96  		addLabeledServiceGateway(t, c, "nw1")
    97  		expectGateways(t, 3)
    98  	})
    99  	t.Run("add kubernetes gateway", func(t *testing.T) {
   100  		addOrUpdateGatewayResource(t, c, 35443)
   101  		expectGateways(t, 7)
   102  	})
   103  	t.Run("update kubernetes gateway", func(t *testing.T) {
   104  		addOrUpdateGatewayResource(t, c, 45443)
   105  		expectGateways(t, 7)
   106  	})
   107  	t.Run("remove kubernetes gateway", func(t *testing.T) {
   108  		removeGatewayResource(t, c)
   109  		expectGateways(t, 3)
   110  	})
   111  	t.Run("remove labeled service", func(t *testing.T) {
   112  		removeLabeledServiceGateway(t, c)
   113  		expectGateways(t, 2)
   114  	})
   115  	// gateways are created even with out service
   116  	t.Run("add kubernetes gateway", func(t *testing.T) {
   117  		addOrUpdateGatewayResource(t, c, 35443)
   118  		expectGateways(t, 6)
   119  	})
   120  	t.Run("remove kubernetes gateway", func(t *testing.T) {
   121  		removeGatewayResource(t, c)
   122  		expectGateways(t, 2)
   123  	})
   124  	t.Run("remove meshnetworks", func(t *testing.T) {
   125  		meshNetworks.SetNetworks(nil)
   126  		expectGateways(t, 0)
   127  	})
   128  }
   129  
   130  func addLabeledServiceGateway(t *testing.T, c *FakeController, nw string) {
   131  	svc := &corev1.Service{
   132  		ObjectMeta: metav1.ObjectMeta{Name: "istio-labeled-gw", Namespace: "arbitrary-ns", Labels: map[string]string{
   133  			label.TopologyNetwork.Name: nw,
   134  		}},
   135  		Spec: corev1.ServiceSpec{
   136  			Type:  corev1.ServiceTypeLoadBalancer,
   137  			Ports: []corev1.ServicePort{{Port: 15443, Protocol: corev1.ProtocolTCP}},
   138  		},
   139  		Status: corev1.ServiceStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{
   140  			IP:    "2.3.4.6",
   141  			Ports: []corev1.PortStatus{{Port: 15443, Protocol: corev1.ProtocolTCP}},
   142  		}}}},
   143  	}
   144  	clienttest.Wrap(t, c.services).CreateOrUpdate(svc)
   145  }
   146  
   147  func removeLabeledServiceGateway(t *testing.T, c *FakeController) {
   148  	clienttest.Wrap(t, c.services).Delete("istio-labeled-gw", "arbitrary-ns")
   149  }
   150  
   151  // creates a gateway that exposes 2 ports that are valid auto-passthrough ports
   152  // and it does so on an IP and a hostname
   153  func addOrUpdateGatewayResource(t *testing.T, c *FakeController, customPort int) {
   154  	passthroughMode := k8sv1.TLSModePassthrough
   155  	ipType := v1beta1.IPAddressType
   156  	hostnameType := v1beta1.HostnameAddressType
   157  	clienttest.Wrap(t, kclient.New[*v1beta1.Gateway](c.client)).CreateOrUpdate(&v1beta1.Gateway{
   158  		ObjectMeta: metav1.ObjectMeta{
   159  			Name:      "eastwest-gwapi",
   160  			Namespace: "istio-system",
   161  			Labels:    map[string]string{label.TopologyNetwork.Name: "nw2"},
   162  		},
   163  		Spec: v1beta1.GatewaySpec{
   164  			GatewayClassName: "istio",
   165  			Addresses: []v1beta1.GatewayAddress{
   166  				{Type: &ipType, Value: "1.2.3.4"},
   167  				{Type: &hostnameType, Value: "some hostname"},
   168  			},
   169  			Listeners: []v1beta1.Listener{
   170  				{
   171  					Name: "detected-by-options",
   172  					TLS: &v1beta1.GatewayTLSConfig{
   173  						Mode: &passthroughMode,
   174  						Options: map[v1beta1.AnnotationKey]v1beta1.AnnotationValue{
   175  							constants.ListenerModeOption: constants.ListenerModeAutoPassthrough,
   176  						},
   177  					},
   178  					Port: v1beta1.PortNumber(customPort),
   179  				},
   180  				{
   181  					Name: "detected-by-number",
   182  					TLS:  &v1beta1.GatewayTLSConfig{Mode: &passthroughMode},
   183  					Port: 15443,
   184  				},
   185  			},
   186  		},
   187  		Status: v1beta1.GatewayStatus{},
   188  	})
   189  }
   190  
   191  func removeGatewayResource(t *testing.T, c *FakeController) {
   192  	clienttest.Wrap(t, kclient.New[*v1beta1.Gateway](c.client)).Delete("eastwest-gwapi", "istio-system")
   193  }
   194  
   195  func addMeshNetworksFromRegistryGateway(t *testing.T, c *FakeController, watcher mesh.NetworksWatcher) {
   196  	clienttest.Wrap(t, c.services).Create(&corev1.Service{
   197  		ObjectMeta: metav1.ObjectMeta{Name: "istio-meshnetworks-gw", Namespace: "istio-system"},
   198  		Spec: corev1.ServiceSpec{
   199  			Type:  corev1.ServiceTypeLoadBalancer,
   200  			Ports: []corev1.ServicePort{{Port: 15443, Protocol: corev1.ProtocolTCP}},
   201  		},
   202  		Status: corev1.ServiceStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{
   203  			IP:    "1.2.3.4",
   204  			Ports: []corev1.PortStatus{{Port: 15443, Protocol: corev1.ProtocolTCP}},
   205  		}}}},
   206  	})
   207  	watcher.SetNetworks(&meshconfig.MeshNetworks{Networks: map[string]*meshconfig.Network{
   208  		"nw0": {
   209  			Endpoints: []*meshconfig.Network_NetworkEndpoints{{
   210  				Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{FromRegistry: "Kubernetes"},
   211  			}},
   212  			Gateways: []*meshconfig.Network_IstioNetworkGateway{{
   213  				Port: 15443,
   214  				Gw:   &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{RegistryServiceName: "istio-meshnetworks-gw.istio-system.svc.cluster.local"},
   215  			}},
   216  		},
   217  		"nw1": {
   218  			Endpoints: []*meshconfig.Network_NetworkEndpoints{{
   219  				Ne: &meshconfig.Network_NetworkEndpoints_FromRegistry{FromRegistry: "Kubernetes"},
   220  			}},
   221  			Gateways: []*meshconfig.Network_IstioNetworkGateway{{
   222  				Port: 15443,
   223  				Gw:   &meshconfig.Network_IstioNetworkGateway_RegistryServiceName{RegistryServiceName: "istio-meshnetworks-gw.istio-system.svc.cluster.local"},
   224  			}},
   225  		},
   226  	}})
   227  }
   228  
   229  func TestAmbientSystemNamespaceNetworkChange(t *testing.T) {
   230  	test.SetForTest(t, &features.EnableAmbient, true)
   231  	testNS := "test"
   232  	systemNS := "istio-system"
   233  
   234  	s, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{
   235  		SystemNamespace: systemNS,
   236  		NetworksWatcher: mesh.NewFixedNetworksWatcher(nil),
   237  	})
   238  
   239  	tracker := assert.NewTracker[string](t)
   240  
   241  	s.namespaces.AddEventHandler(controllers.ObjectHandler(func(o controllers.Object) {
   242  		tracker.Record(o.GetName())
   243  	}))
   244  
   245  	expectNetwork := func(t *testing.T, c *FakeController, network string) {
   246  		t.Helper()
   247  		retry.UntilSuccessOrFail(t, func() error {
   248  			t.Helper()
   249  			if c.networkFromSystemNamespace().String() != network {
   250  				return fmt.Errorf("no network system notify")
   251  			}
   252  			podNames := sets.New[string]("pod1", "pod2")
   253  			svcNames := sets.New[string]("svc1")
   254  			addresses := c.ambientIndex.All()
   255  			for _, addr := range addresses {
   256  				wl := addr.GetWorkload()
   257  				if wl != nil {
   258  					if !podNames.Contains(wl.Name) {
   259  						continue
   260  					}
   261  					if addr.GetWorkload().Network != network {
   262  						return fmt.Errorf("no network workload notify")
   263  					}
   264  				}
   265  				svc := addr.GetService()
   266  				if svc != nil {
   267  					if !svcNames.Contains(svc.Name) {
   268  						continue
   269  					}
   270  					for _, saddr := range svc.GetAddresses() {
   271  						if saddr.GetNetwork() != network {
   272  							return fmt.Errorf("no network service notify")
   273  						}
   274  					}
   275  				}
   276  			}
   277  			return nil
   278  		}, retry.Timeout(time.Second*5))
   279  	}
   280  
   281  	pc := clienttest.NewWriter[*corev1.Pod](t, s.client)
   282  	sc := clienttest.NewWriter[*corev1.Service](t, s.client)
   283  	pod1 := generatePod("127.0.0.1", "pod1", testNS, "sa1", "node1", map[string]string{"app": "a"}, nil)
   284  	pc.CreateOrUpdateStatus(pod1)
   285  	fx.WaitOrFail(t, "xds")
   286  
   287  	pod2 := generatePod("127.0.0.2", "pod2", testNS, "sa2", "node1", map[string]string{"app": "a"}, nil)
   288  	pc.CreateOrUpdateStatus(pod2)
   289  	fx.WaitOrFail(t, "xds")
   290  
   291  	sc.CreateOrUpdate(generateService("svc1", testNS, map[string]string{}, // labels
   292  		map[string]string{}, // annotations
   293  		[]int32{80},
   294  		map[string]string{"app": "a"}, // selector
   295  		"10.0.0.1",
   296  	))
   297  	fx.WaitOrFail(t, "xds")
   298  
   299  	createOrUpdateNamespace(t, s, testNS, "")
   300  	createOrUpdateNamespace(t, s, systemNS, "")
   301  
   302  	tracker.WaitOrdered(testNS, systemNS)
   303  
   304  	t.Run("change namespace network to nw1", func(t *testing.T) {
   305  		createOrUpdateNamespace(t, s, systemNS, "nw1")
   306  		tracker.WaitOrdered(systemNS)
   307  		expectNetwork(t, s, "nw1")
   308  	})
   309  
   310  	t.Run("change namespace network to nw2", func(t *testing.T) {
   311  		createOrUpdateNamespace(t, s, systemNS, "nw2")
   312  		tracker.WaitOrdered(systemNS)
   313  		expectNetwork(t, s, "nw2")
   314  	})
   315  
   316  	t.Run("manually change namespace network to nw3, and update meshNetworks", func(t *testing.T) {
   317  		s.setNetworkFromNamespace(&corev1.Namespace{
   318  			ObjectMeta: metav1.ObjectMeta{
   319  				Name: systemNS,
   320  				Labels: map[string]string{
   321  					label.TopologyNetwork.Name: "nw3",
   322  				},
   323  			},
   324  		})
   325  		createOrUpdateNamespace(t, s, systemNS, "nw3")
   326  		tracker.WaitOrdered(systemNS)
   327  		addMeshNetworksFromRegistryGateway(t, s, s.meshNetworksWatcher)
   328  		expectNetwork(t, s, "nw3")
   329  	})
   330  }
   331  
   332  func createOrUpdateNamespace(t *testing.T, c *FakeController, name, network string) {
   333  	namespace := &corev1.Namespace{
   334  		ObjectMeta: metav1.ObjectMeta{
   335  			Name: name,
   336  			Labels: map[string]string{
   337  				label.TopologyNetwork.Name: network,
   338  			},
   339  		},
   340  	}
   341  	clienttest.Wrap(t, c.namespaces).CreateOrUpdate(namespace)
   342  }