istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/dns/server/name_table_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 server_test 16 17 import ( 18 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "google.golang.org/protobuf/testing/protocmp" 22 23 meshconfig "istio.io/api/mesh/v1alpha1" 24 "istio.io/istio/pilot/pkg/model" 25 "istio.io/istio/pilot/pkg/serviceregistry/provider" 26 "istio.io/istio/pkg/config/constants" 27 "istio.io/istio/pkg/config/host" 28 "istio.io/istio/pkg/config/protocol" 29 dnsProto "istio.io/istio/pkg/dns/proto" 30 dnsServer "istio.io/istio/pkg/dns/server" 31 ) 32 33 // nolint 34 func makeServiceInstances(proxy *model.Proxy, service *model.Service, hostname, subdomain string) map[int][]*model.IstioEndpoint { 35 instances := make(map[int][]*model.IstioEndpoint) 36 for _, port := range service.Ports { 37 instances[port.Port] = makeInstances(proxy, service, port.Port, port.Port) 38 instances[port.Port][0].HostName = hostname 39 instances[port.Port][0].SubDomain = subdomain 40 instances[port.Port][0].Network = proxy.Metadata.Network 41 instances[port.Port][0].Locality.ClusterID = proxy.Metadata.ClusterID 42 } 43 return instances 44 } 45 46 func TestNameTable(t *testing.T) { 47 mesh := &meshconfig.MeshConfig{RootNamespace: "istio-system"} 48 proxy := &model.Proxy{ 49 IPAddresses: []string{"9.9.9.9"}, 50 Metadata: &model.NodeMetadata{}, 51 Type: model.SidecarProxy, 52 DNSDomain: "testns.svc.cluster.local", 53 } 54 nw1proxy := &model.Proxy{ 55 IPAddresses: []string{"9.9.9.9"}, 56 Metadata: &model.NodeMetadata{Network: "nw1"}, 57 Type: model.SidecarProxy, 58 DNSDomain: "testns.svc.cluster.local", 59 } 60 cl1proxy := &model.Proxy{ 61 IPAddresses: []string{"9.9.9.9"}, 62 Metadata: &model.NodeMetadata{ClusterID: "cl1"}, 63 Type: model.SidecarProxy, 64 DNSDomain: "testns.svc.cluster.local", 65 } 66 67 pod1 := &model.Proxy{ 68 IPAddresses: []string{"1.2.3.4"}, 69 Metadata: &model.NodeMetadata{}, 70 Type: model.SidecarProxy, 71 DNSDomain: "testns.svc.cluster.local", 72 } 73 pod2 := &model.Proxy{ 74 IPAddresses: []string{"9.6.7.8"}, 75 Metadata: &model.NodeMetadata{Network: "nw2", ClusterID: "cl2"}, 76 Type: model.SidecarProxy, 77 DNSDomain: "testns.svc.cluster.local", 78 } 79 pod3 := &model.Proxy{ 80 IPAddresses: []string{"19.6.7.8"}, 81 Metadata: &model.NodeMetadata{Network: "nw1"}, 82 Type: model.SidecarProxy, 83 DNSDomain: "testns.svc.cluster.local", 84 } 85 pod4 := &model.Proxy{ 86 IPAddresses: []string{"9.16.7.8"}, 87 Metadata: &model.NodeMetadata{ClusterID: "cl1"}, 88 Type: model.SidecarProxy, 89 DNSDomain: "testns.svc.cluster.local", 90 } 91 92 headlessService := &model.Service{ 93 Hostname: host.Name("headless-svc.testns.svc.cluster.local"), 94 DefaultAddress: constants.UnspecifiedIP, 95 Ports: model.PortList{&model.Port{ 96 Name: "tcp-port", 97 Port: 9000, 98 Protocol: protocol.TCP, 99 }}, 100 Resolution: model.Passthrough, 101 Attributes: model.ServiceAttributes{ 102 Name: "headless-svc", 103 Namespace: "testns", 104 ServiceRegistry: provider.Kubernetes, 105 }, 106 } 107 108 headlessServiceForServiceEntry := &model.Service{ 109 Hostname: host.Name("foo.bar.com"), 110 DefaultAddress: constants.UnspecifiedIP, 111 Ports: model.PortList{&model.Port{ 112 Name: "tcp-port", 113 Port: 9000, 114 Protocol: protocol.TCP, 115 }}, 116 Resolution: model.Passthrough, 117 Attributes: model.ServiceAttributes{ 118 Name: "foo.bar.com", 119 Namespace: "testns", 120 ServiceRegistry: provider.External, 121 LabelSelectors: map[string]string{"wl": "headless-foobar"}, 122 }, 123 } 124 125 wildcardService := &model.Service{ 126 Hostname: host.Name("*.testns.svc.cluster.local"), 127 DefaultAddress: "172.10.10.10", 128 Ports: model.PortList{ 129 &model.Port{ 130 Name: "tcp-port", 131 Port: 9000, 132 Protocol: protocol.TCP, 133 }, 134 &model.Port{ 135 Name: "http-port", 136 Port: 8000, 137 Protocol: protocol.HTTP, 138 }, 139 }, 140 Resolution: model.ClientSideLB, 141 Attributes: model.ServiceAttributes{ 142 Name: "wildcard-svc", 143 Namespace: "testns", 144 ServiceRegistry: provider.Kubernetes, 145 }, 146 } 147 148 cidrService := &model.Service{ 149 Hostname: host.Name("*.testns.svc.cluster.local"), 150 DefaultAddress: "172.217.0.0/16", 151 Ports: model.PortList{ 152 &model.Port{ 153 Name: "tcp-port", 154 Port: 9000, 155 Protocol: protocol.TCP, 156 }, 157 &model.Port{ 158 Name: "http-port", 159 Port: 8000, 160 Protocol: protocol.HTTP, 161 }, 162 }, 163 Resolution: model.ClientSideLB, 164 Attributes: model.ServiceAttributes{ 165 Name: "cidr-svc", 166 Namespace: "testns", 167 ServiceRegistry: provider.Kubernetes, 168 }, 169 } 170 171 serviceWithVIP1 := &model.Service{ 172 Hostname: host.Name("mysql.foo.bar"), 173 DefaultAddress: "10.0.0.5", 174 Ports: model.PortList{ 175 &model.Port{ 176 Name: "tcp", 177 Port: 3306, 178 Protocol: protocol.TCP, 179 }, 180 }, 181 Resolution: model.Passthrough, 182 Attributes: model.ServiceAttributes{ 183 Name: "mysql-svc", 184 Namespace: "testns", 185 ServiceRegistry: provider.External, 186 }, 187 } 188 serviceWithVIP2 := serviceWithVIP1.DeepCopy() 189 serviceWithVIP2.DefaultAddress = "10.0.0.6" 190 191 decoratedService := serviceWithVIP1.DeepCopy() 192 decoratedService.DefaultAddress = "10.0.0.7" 193 decoratedService.Attributes.ServiceRegistry = provider.Kubernetes 194 195 push := model.NewPushContext() 196 push.Mesh = mesh 197 push.AddPublicServices([]*model.Service{headlessService}) 198 push.AddServiceInstances(headlessService, 199 makeServiceInstances(pod1, headlessService, "pod1", "headless-svc")) 200 push.AddServiceInstances(headlessService, 201 makeServiceInstances(pod2, headlessService, "pod2", "headless-svc")) 202 push.AddServiceInstances(headlessService, 203 makeServiceInstances(pod3, headlessService, "pod3", "headless-svc")) 204 push.AddServiceInstances(headlessService, 205 makeServiceInstances(pod4, headlessService, "pod4", "headless-svc")) 206 207 wpush := model.NewPushContext() 208 wpush.Mesh = mesh 209 wpush.AddPublicServices([]*model.Service{wildcardService}) 210 211 cpush := model.NewPushContext() 212 cpush.Mesh = mesh 213 cpush.AddPublicServices([]*model.Service{cidrService}) 214 215 sepush := model.NewPushContext() 216 sepush.Mesh = mesh 217 sepush.AddPublicServices([]*model.Service{headlessServiceForServiceEntry}) 218 sepush.AddServiceInstances(headlessServiceForServiceEntry, 219 makeServiceInstances(pod1, headlessServiceForServiceEntry, "", "")) 220 sepush.AddServiceInstances(headlessServiceForServiceEntry, 221 makeServiceInstances(pod2, headlessServiceForServiceEntry, "", "")) 222 sepush.AddServiceInstances(headlessServiceForServiceEntry, 223 makeServiceInstances(pod3, headlessServiceForServiceEntry, "", "")) 224 sepush.AddServiceInstances(headlessServiceForServiceEntry, 225 makeServiceInstances(pod4, headlessServiceForServiceEntry, "", "")) 226 227 cases := []struct { 228 name string 229 proxy *model.Proxy 230 push *model.PushContext 231 enableMultiClusterHeadless bool 232 expectedNameTable *dnsProto.NameTable 233 }{ 234 { 235 name: "headless service pods", 236 proxy: proxy, 237 push: push, 238 expectedNameTable: &dnsProto.NameTable{ 239 Table: map[string]*dnsProto.NameTable_NameInfo{ 240 "pod1.headless-svc.testns.svc.cluster.local": { 241 Ips: []string{"1.2.3.4"}, 242 Registry: "Kubernetes", 243 Shortname: "pod1.headless-svc", 244 Namespace: "testns", 245 }, 246 "pod2.headless-svc.testns.svc.cluster.local": { 247 Ips: []string{"9.6.7.8"}, 248 Registry: "Kubernetes", 249 Shortname: "pod2.headless-svc", 250 Namespace: "testns", 251 }, 252 "pod3.headless-svc.testns.svc.cluster.local": { 253 Ips: []string{"19.6.7.8"}, 254 Registry: "Kubernetes", 255 Shortname: "pod3.headless-svc", 256 Namespace: "testns", 257 }, 258 "pod4.headless-svc.testns.svc.cluster.local": { 259 Ips: []string{"9.16.7.8"}, 260 Registry: "Kubernetes", 261 Shortname: "pod4.headless-svc", 262 Namespace: "testns", 263 }, 264 "headless-svc.testns.svc.cluster.local": { 265 Ips: []string{"1.2.3.4", "9.6.7.8", "19.6.7.8", "9.16.7.8"}, 266 Registry: "Kubernetes", 267 Shortname: "headless-svc", 268 Namespace: "testns", 269 }, 270 }, 271 }, 272 }, 273 { 274 name: "headless service pods with network isolation", 275 proxy: nw1proxy, 276 push: push, 277 expectedNameTable: &dnsProto.NameTable{ 278 Table: map[string]*dnsProto.NameTable_NameInfo{ 279 "pod1.headless-svc.testns.svc.cluster.local": { 280 Ips: []string{"1.2.3.4"}, 281 Registry: "Kubernetes", 282 Shortname: "pod1.headless-svc", 283 Namespace: "testns", 284 }, 285 "pod3.headless-svc.testns.svc.cluster.local": { 286 Ips: []string{"19.6.7.8"}, 287 Registry: "Kubernetes", 288 Shortname: "pod3.headless-svc", 289 Namespace: "testns", 290 }, 291 "pod4.headless-svc.testns.svc.cluster.local": { 292 Ips: []string{"9.16.7.8"}, 293 Registry: "Kubernetes", 294 Shortname: "pod4.headless-svc", 295 Namespace: "testns", 296 }, 297 "headless-svc.testns.svc.cluster.local": { 298 Ips: []string{"1.2.3.4", "19.6.7.8", "9.16.7.8"}, 299 Registry: "Kubernetes", 300 Shortname: "headless-svc", 301 Namespace: "testns", 302 }, 303 }, 304 }, 305 }, 306 { 307 name: "multi cluster headless service pods", 308 proxy: cl1proxy, 309 push: push, 310 expectedNameTable: &dnsProto.NameTable{ 311 Table: map[string]*dnsProto.NameTable_NameInfo{ 312 "pod1.headless-svc.testns.svc.cluster.local": { 313 Ips: []string{"1.2.3.4"}, 314 Registry: "Kubernetes", 315 Shortname: "pod1.headless-svc", 316 Namespace: "testns", 317 }, 318 "pod2.headless-svc.testns.svc.cluster.local": { 319 Ips: []string{"9.6.7.8"}, 320 Registry: "Kubernetes", 321 Shortname: "pod2.headless-svc", 322 Namespace: "testns", 323 }, 324 "pod3.headless-svc.testns.svc.cluster.local": { 325 Ips: []string{"19.6.7.8"}, 326 Registry: "Kubernetes", 327 Shortname: "pod3.headless-svc", 328 Namespace: "testns", 329 }, 330 "pod4.headless-svc.testns.svc.cluster.local": { 331 Ips: []string{"9.16.7.8"}, 332 Registry: "Kubernetes", 333 Shortname: "pod4.headless-svc", 334 Namespace: "testns", 335 }, 336 "headless-svc.testns.svc.cluster.local": { 337 Ips: []string{"1.2.3.4", "19.6.7.8", "9.16.7.8"}, 338 Registry: "Kubernetes", 339 Shortname: "headless-svc", 340 Namespace: "testns", 341 }, 342 }, 343 }, 344 }, 345 { 346 name: "multi cluster headless service pods with multi cluster enabled", 347 proxy: cl1proxy, 348 push: push, 349 enableMultiClusterHeadless: true, 350 expectedNameTable: &dnsProto.NameTable{ 351 Table: map[string]*dnsProto.NameTable_NameInfo{ 352 "pod1.headless-svc.testns.svc.cluster.local": { 353 Ips: []string{"1.2.3.4"}, 354 Registry: "Kubernetes", 355 Shortname: "pod1.headless-svc", 356 Namespace: "testns", 357 }, 358 "pod2.headless-svc.testns.svc.cluster.local": { 359 Ips: []string{"9.6.7.8"}, 360 Registry: "Kubernetes", 361 Shortname: "pod2.headless-svc", 362 Namespace: "testns", 363 }, 364 "pod3.headless-svc.testns.svc.cluster.local": { 365 Ips: []string{"19.6.7.8"}, 366 Registry: "Kubernetes", 367 Shortname: "pod3.headless-svc", 368 Namespace: "testns", 369 }, 370 "pod4.headless-svc.testns.svc.cluster.local": { 371 Ips: []string{"9.16.7.8"}, 372 Registry: "Kubernetes", 373 Shortname: "pod4.headless-svc", 374 Namespace: "testns", 375 }, 376 "headless-svc.testns.svc.cluster.local": { 377 Ips: []string{"1.2.3.4", "9.6.7.8", "19.6.7.8", "9.16.7.8"}, 378 Registry: "Kubernetes", 379 Shortname: "headless-svc", 380 Namespace: "testns", 381 }, 382 }, 383 }, 384 }, 385 { 386 name: "wildcard service pods", 387 proxy: proxy, 388 push: wpush, 389 expectedNameTable: &dnsProto.NameTable{ 390 Table: map[string]*dnsProto.NameTable_NameInfo{ 391 "*.testns.svc.cluster.local": { 392 Ips: []string{"172.10.10.10"}, 393 Registry: "Kubernetes", 394 Shortname: "wildcard-svc", 395 Namespace: "testns", 396 }, 397 }, 398 }, 399 }, 400 { 401 name: "cidr service", 402 proxy: proxy, 403 push: cpush, 404 expectedNameTable: &dnsProto.NameTable{ 405 Table: map[string]*dnsProto.NameTable_NameInfo{}, 406 }, 407 }, 408 { 409 name: "service entry with resolution = NONE", 410 proxy: proxy, 411 push: sepush, 412 expectedNameTable: &dnsProto.NameTable{ 413 Table: map[string]*dnsProto.NameTable_NameInfo{ 414 "foo.bar.com": { 415 Ips: []string{"1.2.3.4", "9.6.7.8", "19.6.7.8", "9.16.7.8"}, 416 Registry: "External", 417 }, 418 }, 419 }, 420 }, 421 { 422 name: "service entry with resolution = NONE with network isolation", 423 proxy: nw1proxy, 424 push: sepush, 425 expectedNameTable: &dnsProto.NameTable{ 426 Table: map[string]*dnsProto.NameTable_NameInfo{ 427 "foo.bar.com": { 428 Ips: []string{"1.2.3.4", "19.6.7.8", "9.16.7.8"}, 429 Registry: "External", 430 }, 431 }, 432 }, 433 }, 434 { 435 name: "multi cluster service entry with resolution = NONE", 436 proxy: cl1proxy, 437 push: sepush, 438 expectedNameTable: &dnsProto.NameTable{ 439 Table: map[string]*dnsProto.NameTable_NameInfo{ 440 "foo.bar.com": { 441 Ips: []string{"1.2.3.4", "19.6.7.8", "9.16.7.8"}, 442 Registry: "External", 443 }, 444 }, 445 }, 446 }, 447 { 448 name: "service entry with multiple VIPs", 449 proxy: proxy, 450 push: func() *model.PushContext { 451 push := model.NewPushContext() 452 push.Mesh = mesh 453 push.AddPublicServices([]*model.Service{serviceWithVIP1, serviceWithVIP2}) 454 return push 455 }(), 456 expectedNameTable: &dnsProto.NameTable{ 457 Table: map[string]*dnsProto.NameTable_NameInfo{ 458 serviceWithVIP1.Hostname.String(): { 459 Ips: []string{serviceWithVIP1.DefaultAddress}, 460 Registry: provider.External.String(), 461 }, 462 }, 463 }, 464 }, 465 { 466 name: "service entry as a decorator(created before k8s service)", 467 proxy: proxy, 468 push: func() *model.PushContext { 469 push := model.NewPushContext() 470 push.Mesh = mesh 471 push.AddPublicServices([]*model.Service{serviceWithVIP1, decoratedService}) 472 return push 473 }(), 474 expectedNameTable: &dnsProto.NameTable{ 475 Table: map[string]*dnsProto.NameTable_NameInfo{ 476 serviceWithVIP1.Hostname.String(): { 477 Ips: []string{decoratedService.DefaultAddress}, 478 Registry: provider.Kubernetes.String(), 479 Shortname: decoratedService.Attributes.Name, 480 Namespace: decoratedService.Attributes.Namespace, 481 }, 482 }, 483 }, 484 }, 485 { 486 name: "service entry as a decorator(created after k8s service)", 487 proxy: proxy, 488 push: func() *model.PushContext { 489 push := model.NewPushContext() 490 push.Mesh = mesh 491 push.AddPublicServices([]*model.Service{decoratedService, serviceWithVIP2}) 492 return push 493 }(), 494 expectedNameTable: &dnsProto.NameTable{ 495 Table: map[string]*dnsProto.NameTable_NameInfo{ 496 serviceWithVIP1.Hostname.String(): { 497 Ips: []string{decoratedService.DefaultAddress}, 498 Registry: provider.Kubernetes.String(), 499 Shortname: decoratedService.Attributes.Name, 500 Namespace: decoratedService.Attributes.Namespace, 501 }, 502 }, 503 }, 504 }, 505 } 506 for _, tt := range cases { 507 t.Run(tt.name, func(t *testing.T) { 508 tt.proxy.SidecarScope = model.DefaultSidecarScopeForNamespace(tt.push, "default") 509 if diff := cmp.Diff(dnsServer.BuildNameTable(dnsServer.Config{ 510 Node: tt.proxy, 511 Push: tt.push, 512 MulticlusterHeadlessEnabled: tt.enableMultiClusterHeadless, 513 }), tt.expectedNameTable, protocmp.Transform()); diff != "" { 514 t.Fatalf("got diff: %v", diff) 515 } 516 }) 517 } 518 } 519 520 func makeInstances(proxy *model.Proxy, svc *model.Service, servicePort int, targetPort int) []*model.IstioEndpoint { 521 ret := make([]*model.IstioEndpoint, 0) 522 for _, p := range svc.Ports { 523 if p.Port != servicePort { 524 continue 525 } 526 ret = append(ret, &model.IstioEndpoint{ 527 Address: proxy.IPAddresses[0], 528 ServicePortName: p.Name, 529 EndpointPort: uint32(targetPort), 530 }) 531 } 532 return ret 533 }