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 }