istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/conversion_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 kube 16 17 import ( 18 "fmt" 19 "reflect" 20 "strings" 21 "testing" 22 "time" 23 24 corev1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 27 "istio.io/api/annotation" 28 "istio.io/istio/pkg/cluster" 29 "istio.io/istio/pkg/config/kube" 30 "istio.io/istio/pkg/config/protocol" 31 "istio.io/istio/pkg/spiffe" 32 ) 33 34 var ( 35 domainSuffix = "company.com" 36 clusterID = cluster.ID("test-cluster") 37 ) 38 39 func TestConvertProtocol(t *testing.T) { 40 http := "http" 41 type protocolCase struct { 42 port int32 43 name string 44 appProtocol *string 45 proto corev1.Protocol 46 out protocol.Instance 47 } 48 protocols := []protocolCase{ 49 {8888, "", nil, corev1.ProtocolTCP, protocol.Unsupported}, 50 {25, "", nil, corev1.ProtocolTCP, protocol.TCP}, 51 {53, "", nil, corev1.ProtocolTCP, protocol.TCP}, 52 {3306, "", nil, corev1.ProtocolTCP, protocol.TCP}, 53 {27017, "", nil, corev1.ProtocolTCP, protocol.TCP}, 54 {8888, "http", nil, corev1.ProtocolTCP, protocol.HTTP}, 55 {8888, "http-test", nil, corev1.ProtocolTCP, protocol.HTTP}, 56 {8888, "http", nil, corev1.ProtocolUDP, protocol.UDP}, 57 {8888, "httptest", nil, corev1.ProtocolTCP, protocol.Unsupported}, 58 {25, "httptest", nil, corev1.ProtocolTCP, protocol.TCP}, 59 {53, "httptest", nil, corev1.ProtocolTCP, protocol.TCP}, 60 {3306, "httptest", nil, corev1.ProtocolTCP, protocol.TCP}, 61 {27017, "httptest", nil, corev1.ProtocolTCP, protocol.TCP}, 62 {8888, "https", nil, corev1.ProtocolTCP, protocol.HTTPS}, 63 {8888, "https-test", nil, corev1.ProtocolTCP, protocol.HTTPS}, 64 {8888, "http2", nil, corev1.ProtocolTCP, protocol.HTTP2}, 65 {8888, "http2-test", nil, corev1.ProtocolTCP, protocol.HTTP2}, 66 {8888, "grpc", nil, corev1.ProtocolTCP, protocol.GRPC}, 67 {8888, "grpc-test", nil, corev1.ProtocolTCP, protocol.GRPC}, 68 {8888, "grpc-web", nil, corev1.ProtocolTCP, protocol.GRPCWeb}, 69 {8888, "grpc-web-test", nil, corev1.ProtocolTCP, protocol.GRPCWeb}, 70 {8888, "mongo", nil, corev1.ProtocolTCP, protocol.Mongo}, 71 {8888, "mongo-test", nil, corev1.ProtocolTCP, protocol.Mongo}, 72 {8888, "redis", nil, corev1.ProtocolTCP, protocol.Redis}, 73 {8888, "redis-test", nil, corev1.ProtocolTCP, protocol.Redis}, 74 {8888, "mysql", nil, corev1.ProtocolTCP, protocol.MySQL}, 75 {8888, "mysql-test", nil, corev1.ProtocolTCP, protocol.MySQL}, 76 {8888, "tcp", &http, corev1.ProtocolTCP, protocol.HTTP}, 77 } 78 79 // Create the list of cases for all of the names in both upper and lowercase. 80 cases := make([]protocolCase, 0, len(protocols)*2) 81 for _, p := range protocols { 82 name := p.name 83 84 p.name = strings.ToLower(name) 85 cases = append(cases, p) 86 87 // Don't bother adding uppercase version for empty string. 88 if name != "" { 89 p.name = strings.ToUpper(name) 90 cases = append(cases, p) 91 } 92 } 93 94 for _, c := range cases { 95 testName := strings.Replace(fmt.Sprintf("%s_%s_%d", c.name, c.proto, c.port), "-", "_", -1) 96 t.Run(testName, func(t *testing.T) { 97 out := kube.ConvertProtocol(c.port, c.name, c.proto, c.appProtocol) 98 if out != c.out { 99 t.Fatalf("convertProtocol(%d, %q, %q) => %q, want %q", c.port, c.name, c.proto, out, c.out) 100 } 101 }) 102 } 103 } 104 105 func BenchmarkConvertProtocol(b *testing.B) { 106 cases := []struct { 107 name string 108 proto corev1.Protocol 109 out protocol.Instance 110 }{ 111 {"grpc-web-lowercase", corev1.ProtocolTCP, protocol.GRPCWeb}, 112 {"GRPC-WEB-mixedcase", corev1.ProtocolTCP, protocol.GRPCWeb}, 113 {"https-lowercase", corev1.ProtocolTCP, protocol.HTTPS}, 114 {"HTTPS-mixedcase", corev1.ProtocolTCP, protocol.HTTPS}, 115 } 116 117 for _, c := range cases { 118 testName := strings.Replace(c.name, "-", "_", -1) 119 b.Run(testName, func(b *testing.B) { 120 for i := 0; i < b.N; i++ { 121 out := kube.ConvertProtocol(8888, c.name, c.proto, nil) 122 if out != c.out { 123 b.Fatalf("convertProtocol(%q, %q) => %q, want %q", c.name, c.proto, out, c.out) 124 } 125 } 126 }) 127 } 128 } 129 130 func TestServiceConversion(t *testing.T) { 131 serviceName := "service1" 132 namespace := "default" 133 saA := "serviceaccountA" 134 saB := "serviceaccountB" 135 saC := "spiffe://accounts.google.com/serviceaccountC@cloudservices.gserviceaccount.com" 136 saD := "spiffe://accounts.google.com/serviceaccountD@developer.gserviceaccount.com" 137 138 oldTrustDomain := spiffe.GetTrustDomain() 139 spiffe.SetTrustDomain(domainSuffix) 140 defer spiffe.SetTrustDomain(oldTrustDomain) 141 142 ip := "10.0.0.1" 143 144 tnow := time.Now() 145 localSvc := corev1.Service{ 146 ObjectMeta: metav1.ObjectMeta{ 147 Name: serviceName, 148 Namespace: namespace, 149 Annotations: map[string]string{ 150 annotation.AlphaKubernetesServiceAccounts.Name: saA + "," + saB, 151 annotation.AlphaCanonicalServiceAccounts.Name: saC + "," + saD, 152 "other/annotation": "test", 153 }, 154 CreationTimestamp: metav1.Time{Time: tnow}, 155 }, 156 Spec: corev1.ServiceSpec{ 157 ClusterIP: ip, 158 Selector: map[string]string{"foo": "bar"}, 159 Ports: []corev1.ServicePort{ 160 { 161 Name: "http", 162 Port: 8080, 163 Protocol: corev1.ProtocolTCP, 164 }, 165 { 166 Name: "https", 167 Protocol: corev1.ProtocolTCP, 168 Port: 443, 169 }, 170 }, 171 }, 172 } 173 174 service := ConvertService(localSvc, domainSuffix, clusterID) 175 if service == nil { 176 t.Fatalf("could not convert service") 177 } 178 179 if service.CreationTime != tnow { 180 t.Fatalf("incorrect creation time => %v, want %v", service.CreationTime, tnow) 181 } 182 183 if len(service.Ports) != len(localSvc.Spec.Ports) { 184 t.Fatalf("incorrect number of ports => %v, want %v", 185 len(service.Ports), len(localSvc.Spec.Ports)) 186 } 187 188 if service.External() { 189 t.Fatal("service should not be external") 190 } 191 192 if service.Hostname != ServiceHostname(serviceName, namespace, domainSuffix) { 193 t.Fatalf("service hostname incorrect => %q, want %q", 194 service.Hostname, ServiceHostname(serviceName, namespace, domainSuffix)) 195 } 196 197 ips := service.ClusterVIPs.GetAddressesFor(clusterID) 198 if len(ips) != 1 { 199 t.Fatalf("number of ips incorrect => %q, want 1", len(ips)) 200 } 201 202 if ips[0] != ip { 203 t.Fatalf("service IP incorrect => %q, want %q", ips[0], ip) 204 } 205 206 actualIPs := service.ClusterVIPs.GetAddressesFor(clusterID) 207 expectedIPs := []string{ip} 208 if !reflect.DeepEqual(actualIPs, expectedIPs) { 209 t.Fatalf("service IPs incorrect => %q, want %q", actualIPs, expectedIPs) 210 } 211 212 if !reflect.DeepEqual(service.Attributes.LabelSelectors, localSvc.Spec.Selector) { 213 t.Fatalf("service label selectors incorrect => %q, want %q", service.Attributes.LabelSelectors, 214 localSvc.Spec.Selector) 215 } 216 217 sa := service.ServiceAccounts 218 if sa == nil || len(sa) != 4 { 219 t.Fatalf("number of service accounts is incorrect") 220 } 221 expected := []string{ 222 saC, saD, 223 "spiffe://company.com/ns/default/sa/" + saA, 224 "spiffe://company.com/ns/default/sa/" + saB, 225 } 226 if !reflect.DeepEqual(sa, expected) { 227 t.Fatalf("Unexpected service accounts %v (expecting %v)", sa, expected) 228 } 229 } 230 231 func TestServiceConversionWithEmptyServiceAccountsAnnotation(t *testing.T) { 232 serviceName := "service1" 233 namespace := "default" 234 235 ip := "10.0.0.1" 236 237 localSvc := corev1.Service{ 238 ObjectMeta: metav1.ObjectMeta{ 239 Name: serviceName, 240 Namespace: namespace, 241 Annotations: map[string]string{}, 242 }, 243 Spec: corev1.ServiceSpec{ 244 ClusterIP: ip, 245 Ports: []corev1.ServicePort{ 246 { 247 Name: "http", 248 Port: 8080, 249 Protocol: corev1.ProtocolTCP, 250 }, 251 { 252 Name: "https", 253 Protocol: corev1.ProtocolTCP, 254 Port: 443, 255 }, 256 }, 257 }, 258 } 259 260 service := ConvertService(localSvc, domainSuffix, clusterID) 261 if service == nil { 262 t.Fatalf("could not convert service") 263 } 264 265 sa := service.ServiceAccounts 266 if len(sa) != 0 { 267 t.Fatalf("number of service accounts is incorrect: %d, expected 0", len(sa)) 268 } 269 } 270 271 func TestExternalServiceConversion(t *testing.T) { 272 serviceName := "service1" 273 namespace := "default" 274 275 extSvc := corev1.Service{ 276 ObjectMeta: metav1.ObjectMeta{ 277 Name: serviceName, 278 Namespace: namespace, 279 }, 280 Spec: corev1.ServiceSpec{ 281 Ports: []corev1.ServicePort{ 282 { 283 Name: "http", 284 Port: 80, 285 Protocol: corev1.ProtocolTCP, 286 }, 287 }, 288 Type: corev1.ServiceTypeExternalName, 289 ExternalName: "google.com", 290 }, 291 } 292 293 service := ConvertService(extSvc, domainSuffix, clusterID) 294 if service == nil { 295 t.Fatalf("could not convert external service") 296 } 297 298 if len(service.Ports) != len(extSvc.Spec.Ports) { 299 t.Fatalf("incorrect number of ports => %v, want %v", 300 len(service.Ports), len(extSvc.Spec.Ports)) 301 } 302 303 if !service.External() { 304 t.Fatal("service should be external") 305 } 306 307 if service.Hostname != ServiceHostname(serviceName, namespace, domainSuffix) { 308 t.Fatalf("service hostname incorrect => %q, want %q", 309 service.Hostname, ServiceHostname(serviceName, namespace, domainSuffix)) 310 } 311 312 if service.Attributes.Type != string(extSvc.Spec.Type) || 313 service.Attributes.ExternalName != extSvc.Spec.ExternalName { 314 t.Fatalf("service attributes incorrect => %v/%v, want %v/%v", 315 service.Attributes.Type, service.Attributes.ExternalName, extSvc.Spec.Type, extSvc.Spec.ExternalName) 316 } 317 } 318 319 func TestExternalClusterLocalServiceConversion(t *testing.T) { 320 serviceName := "service1" 321 namespace := "default" 322 323 extSvc := corev1.Service{ 324 ObjectMeta: metav1.ObjectMeta{ 325 Name: serviceName, 326 Namespace: namespace, 327 }, 328 Spec: corev1.ServiceSpec{ 329 Ports: []corev1.ServicePort{ 330 { 331 Name: "http", 332 Port: 80, 333 Protocol: corev1.ProtocolTCP, 334 }, 335 }, 336 Type: corev1.ServiceTypeExternalName, 337 ExternalName: "some.test.svc.cluster.local", 338 }, 339 } 340 341 domainSuffix := "cluster.local" 342 343 service := ConvertService(extSvc, domainSuffix, clusterID) 344 if service == nil { 345 t.Fatalf("could not convert external service") 346 } 347 348 if len(service.Ports) != len(extSvc.Spec.Ports) { 349 t.Fatalf("incorrect number of ports => %v, want %v", 350 len(service.Ports), len(extSvc.Spec.Ports)) 351 } 352 353 if !service.External() { 354 t.Fatal("ExternalName service (even if .cluster.local) should be external") 355 } 356 357 if service.Hostname != ServiceHostname(serviceName, namespace, domainSuffix) { 358 t.Fatalf("service hostname incorrect => %q, want %q", 359 service.Hostname, ServiceHostname(serviceName, namespace, domainSuffix)) 360 } 361 } 362 363 func TestLBServiceConversion(t *testing.T) { 364 serviceName := "service1" 365 namespace := "default" 366 367 addresses := []corev1.LoadBalancerIngress{ 368 { 369 IP: "127.68.32.112", 370 }, 371 { 372 IP: "127.68.32.113", 373 }, 374 { 375 Hostname: "127.68.32.114", 376 }, 377 { 378 Hostname: "127.68.32.115", 379 }, 380 } 381 382 extSvc := corev1.Service{ 383 ObjectMeta: metav1.ObjectMeta{ 384 Name: serviceName, 385 Namespace: namespace, 386 }, 387 Spec: corev1.ServiceSpec{ 388 Ports: []corev1.ServicePort{ 389 { 390 Name: "http", 391 Port: 80, 392 Protocol: corev1.ProtocolTCP, 393 }, 394 }, 395 Type: corev1.ServiceTypeLoadBalancer, 396 }, 397 Status: corev1.ServiceStatus{ 398 LoadBalancer: corev1.LoadBalancerStatus{ 399 Ingress: addresses, 400 }, 401 }, 402 } 403 404 service := ConvertService(extSvc, domainSuffix, clusterID) 405 if service == nil { 406 t.Fatalf("could not convert external service") 407 } 408 409 gotAddresses := service.Attributes.ClusterExternalAddresses.GetAddressesFor(clusterID) 410 if len(gotAddresses) == 0 { 411 t.Fatalf("no load balancer addresses found") 412 } 413 414 for i, addr := range addresses { 415 var want string 416 if len(addr.IP) > 0 { 417 want = addr.IP 418 } else { 419 want = addr.Hostname 420 } 421 got := gotAddresses[i] 422 if got != want { 423 t.Fatalf("Expected address %s but got %s", want, got) 424 } 425 } 426 } 427 428 func TestInternalTrafficPolicyServiceConversion(t *testing.T) { 429 serviceName := "service1" 430 namespace := "default" 431 local := corev1.ServiceInternalTrafficPolicyLocal 432 433 svc := corev1.Service{ 434 ObjectMeta: metav1.ObjectMeta{ 435 Name: serviceName, 436 Namespace: namespace, 437 }, 438 Spec: corev1.ServiceSpec{ 439 Ports: []corev1.ServicePort{ 440 { 441 Name: "http", 442 Port: 80, 443 Protocol: corev1.ProtocolTCP, 444 }, 445 }, 446 InternalTrafficPolicy: &local, 447 }, 448 } 449 450 service := ConvertService(svc, domainSuffix, clusterID) 451 if service == nil { 452 t.Fatalf("could not convert service") 453 } 454 455 if !service.Attributes.NodeLocal { 456 t.Fatal("not node local") 457 } 458 } 459 460 func TestSecureNamingSAN(t *testing.T) { 461 pod := &corev1.Pod{} 462 463 pod.Annotations = make(map[string]string) 464 465 ns := "anything" 466 sa := "foo" 467 pod.Namespace = ns 468 pod.Spec.ServiceAccountName = sa 469 470 san := SecureNamingSAN(pod) 471 472 expectedSAN := fmt.Sprintf("spiffe://%v/ns/%v/sa/%v", spiffe.GetTrustDomain(), ns, sa) 473 474 if san != expectedSAN { 475 t.Fatalf("SAN match failed, SAN:%v expectedSAN:%v", san, expectedSAN) 476 } 477 }