| // Copyright Istio Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package kube |
| |
| import ( |
| "fmt" |
| "reflect" |
| "strings" |
| "testing" |
| "time" |
| ) |
| |
| import ( |
| "istio.io/api/annotation" |
| coreV1 "k8s.io/api/core/v1" |
| metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pkg/cluster" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/kube" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/protocol" |
| "github.com/apache/dubbo-go-pixiu/pkg/spiffe" |
| ) |
| |
| var ( |
| domainSuffix = "company.com" |
| clusterID = cluster.ID("test-cluster") |
| ) |
| |
| func TestConvertProtocol(t *testing.T) { |
| http := "http" |
| type protocolCase struct { |
| port int32 |
| name string |
| appProtocol *string |
| proto coreV1.Protocol |
| out protocol.Instance |
| } |
| protocols := []protocolCase{ |
| {8888, "", nil, coreV1.ProtocolTCP, protocol.Unsupported}, |
| {25, "", nil, coreV1.ProtocolTCP, protocol.TCP}, |
| {53, "", nil, coreV1.ProtocolTCP, protocol.TCP}, |
| {3306, "", nil, coreV1.ProtocolTCP, protocol.TCP}, |
| {27017, "", nil, coreV1.ProtocolTCP, protocol.TCP}, |
| {8888, "http", nil, coreV1.ProtocolTCP, protocol.HTTP}, |
| {8888, "http-test", nil, coreV1.ProtocolTCP, protocol.HTTP}, |
| {8888, "http", nil, coreV1.ProtocolUDP, protocol.UDP}, |
| {8888, "httptest", nil, coreV1.ProtocolTCP, protocol.Unsupported}, |
| {25, "httptest", nil, coreV1.ProtocolTCP, protocol.TCP}, |
| {53, "httptest", nil, coreV1.ProtocolTCP, protocol.TCP}, |
| {3306, "httptest", nil, coreV1.ProtocolTCP, protocol.TCP}, |
| {27017, "httptest", nil, coreV1.ProtocolTCP, protocol.TCP}, |
| {8888, "https", nil, coreV1.ProtocolTCP, protocol.HTTPS}, |
| {8888, "https-test", nil, coreV1.ProtocolTCP, protocol.HTTPS}, |
| {8888, "http2", nil, coreV1.ProtocolTCP, protocol.HTTP2}, |
| {8888, "http2-test", nil, coreV1.ProtocolTCP, protocol.HTTP2}, |
| {8888, "grpc", nil, coreV1.ProtocolTCP, protocol.GRPC}, |
| {8888, "grpc-test", nil, coreV1.ProtocolTCP, protocol.GRPC}, |
| {8888, "grpc-web", nil, coreV1.ProtocolTCP, protocol.GRPCWeb}, |
| {8888, "grpc-web-test", nil, coreV1.ProtocolTCP, protocol.GRPCWeb}, |
| {8888, "mongo", nil, coreV1.ProtocolTCP, protocol.Mongo}, |
| {8888, "mongo-test", nil, coreV1.ProtocolTCP, protocol.Mongo}, |
| {8888, "redis", nil, coreV1.ProtocolTCP, protocol.Redis}, |
| {8888, "redis-test", nil, coreV1.ProtocolTCP, protocol.Redis}, |
| {8888, "mysql", nil, coreV1.ProtocolTCP, protocol.MySQL}, |
| {8888, "mysql-test", nil, coreV1.ProtocolTCP, protocol.MySQL}, |
| {8888, "tcp", &http, coreV1.ProtocolTCP, protocol.HTTP}, |
| } |
| |
| // Create the list of cases for all of the names in both upper and lowercase. |
| cases := make([]protocolCase, 0, len(protocols)*2) |
| for _, p := range protocols { |
| name := p.name |
| |
| p.name = strings.ToLower(name) |
| cases = append(cases, p) |
| |
| // Don't bother adding uppercase version for empty string. |
| if name != "" { |
| p.name = strings.ToUpper(name) |
| cases = append(cases, p) |
| } |
| } |
| |
| for _, c := range cases { |
| testName := strings.Replace(fmt.Sprintf("%s_%s_%d", c.name, c.proto, c.port), "-", "_", -1) |
| t.Run(testName, func(t *testing.T) { |
| out := kube.ConvertProtocol(c.port, c.name, c.proto, c.appProtocol) |
| if out != c.out { |
| t.Fatalf("convertProtocol(%d, %q, %q) => %q, want %q", c.port, c.name, c.proto, out, c.out) |
| } |
| }) |
| } |
| } |
| |
| func BenchmarkConvertProtocol(b *testing.B) { |
| cases := []struct { |
| name string |
| proto coreV1.Protocol |
| out protocol.Instance |
| }{ |
| {"grpc-web-lowercase", coreV1.ProtocolTCP, protocol.GRPCWeb}, |
| {"GRPC-WEB-mixedcase", coreV1.ProtocolTCP, protocol.GRPCWeb}, |
| {"https-lowercase", coreV1.ProtocolTCP, protocol.HTTPS}, |
| {"HTTPS-mixedcase", coreV1.ProtocolTCP, protocol.HTTPS}, |
| } |
| |
| for _, c := range cases { |
| testName := strings.Replace(c.name, "-", "_", -1) |
| b.Run(testName, func(b *testing.B) { |
| for i := 0; i < b.N; i++ { |
| out := kube.ConvertProtocol(8888, c.name, c.proto, nil) |
| if out != c.out { |
| b.Fatalf("convertProtocol(%q, %q) => %q, want %q", c.name, c.proto, out, c.out) |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestServiceConversion(t *testing.T) { |
| serviceName := "service1" |
| namespace := "default" |
| saA := "serviceaccountA" |
| saB := "serviceaccountB" |
| saC := "spiffe://accounts.google.com/serviceaccountC@cloudservices.gserviceaccount.com" |
| saD := "spiffe://accounts.google.com/serviceaccountD@developer.gserviceaccount.com" |
| |
| oldTrustDomain := spiffe.GetTrustDomain() |
| spiffe.SetTrustDomain(domainSuffix) |
| defer spiffe.SetTrustDomain(oldTrustDomain) |
| |
| ip := "10.0.0.1" |
| |
| tnow := time.Now() |
| localSvc := coreV1.Service{ |
| ObjectMeta: metaV1.ObjectMeta{ |
| Name: serviceName, |
| Namespace: namespace, |
| Annotations: map[string]string{ |
| annotation.AlphaKubernetesServiceAccounts.Name: saA + "," + saB, |
| annotation.AlphaCanonicalServiceAccounts.Name: saC + "," + saD, |
| "other/annotation": "test", |
| }, |
| CreationTimestamp: metaV1.Time{Time: tnow}, |
| }, |
| Spec: coreV1.ServiceSpec{ |
| ClusterIP: ip, |
| Selector: map[string]string{"foo": "bar"}, |
| Ports: []coreV1.ServicePort{ |
| { |
| Name: "http", |
| Port: 8080, |
| Protocol: coreV1.ProtocolTCP, |
| }, |
| { |
| Name: "https", |
| Protocol: coreV1.ProtocolTCP, |
| Port: 443, |
| }, |
| }, |
| }, |
| } |
| |
| service := ConvertService(localSvc, domainSuffix, clusterID) |
| if service == nil { |
| t.Fatalf("could not convert service") |
| } |
| |
| if service.CreationTime != tnow { |
| t.Fatalf("incorrect creation time => %v, want %v", service.CreationTime, tnow) |
| } |
| |
| if len(service.Ports) != len(localSvc.Spec.Ports) { |
| t.Fatalf("incorrect number of ports => %v, want %v", |
| len(service.Ports), len(localSvc.Spec.Ports)) |
| } |
| |
| if service.External() { |
| t.Fatal("service should not be external") |
| } |
| |
| if service.Hostname != ServiceHostname(serviceName, namespace, domainSuffix) { |
| t.Fatalf("service hostname incorrect => %q, want %q", |
| service.Hostname, ServiceHostname(serviceName, namespace, domainSuffix)) |
| } |
| |
| ips := service.ClusterVIPs.GetAddressesFor(clusterID) |
| if len(ips) != 1 { |
| t.Fatalf("number of ips incorrect => %q, want 1", len(ips)) |
| } |
| |
| if ips[0] != ip { |
| t.Fatalf("service IP incorrect => %q, want %q", ips[0], ip) |
| } |
| |
| actualIPs := service.ClusterVIPs.GetAddressesFor(clusterID) |
| expectedIPs := []string{ip} |
| if !reflect.DeepEqual(actualIPs, expectedIPs) { |
| t.Fatalf("service IPs incorrect => %q, want %q", actualIPs, expectedIPs) |
| } |
| |
| if !reflect.DeepEqual(service.Attributes.LabelSelectors, localSvc.Spec.Selector) { |
| t.Fatalf("service label selectors incorrect => %q, want %q", service.Attributes.LabelSelectors, |
| localSvc.Spec.Selector) |
| } |
| |
| sa := service.ServiceAccounts |
| if sa == nil || len(sa) != 4 { |
| t.Fatalf("number of service accounts is incorrect") |
| } |
| expected := []string{ |
| saC, saD, |
| "spiffe://company.com/ns/default/sa/" + saA, |
| "spiffe://company.com/ns/default/sa/" + saB, |
| } |
| if !reflect.DeepEqual(sa, expected) { |
| t.Fatalf("Unexpected service accounts %v (expecting %v)", sa, expected) |
| } |
| } |
| |
| func TestServiceConversionWithEmptyServiceAccountsAnnotation(t *testing.T) { |
| serviceName := "service1" |
| namespace := "default" |
| |
| ip := "10.0.0.1" |
| |
| localSvc := coreV1.Service{ |
| ObjectMeta: metaV1.ObjectMeta{ |
| Name: serviceName, |
| Namespace: namespace, |
| Annotations: map[string]string{}, |
| }, |
| Spec: coreV1.ServiceSpec{ |
| ClusterIP: ip, |
| Ports: []coreV1.ServicePort{ |
| { |
| Name: "http", |
| Port: 8080, |
| Protocol: coreV1.ProtocolTCP, |
| }, |
| { |
| Name: "https", |
| Protocol: coreV1.ProtocolTCP, |
| Port: 443, |
| }, |
| }, |
| }, |
| } |
| |
| service := ConvertService(localSvc, domainSuffix, clusterID) |
| if service == nil { |
| t.Fatalf("could not convert service") |
| } |
| |
| sa := service.ServiceAccounts |
| if len(sa) != 0 { |
| t.Fatalf("number of service accounts is incorrect: %d, expected 0", len(sa)) |
| } |
| } |
| |
| func TestExternalServiceConversion(t *testing.T) { |
| serviceName := "service1" |
| namespace := "default" |
| |
| extSvc := coreV1.Service{ |
| ObjectMeta: metaV1.ObjectMeta{ |
| Name: serviceName, |
| Namespace: namespace, |
| }, |
| Spec: coreV1.ServiceSpec{ |
| Ports: []coreV1.ServicePort{ |
| { |
| Name: "http", |
| Port: 80, |
| Protocol: coreV1.ProtocolTCP, |
| }, |
| }, |
| Type: coreV1.ServiceTypeExternalName, |
| ExternalName: "google.com", |
| }, |
| } |
| |
| service := ConvertService(extSvc, domainSuffix, clusterID) |
| if service == nil { |
| t.Fatalf("could not convert external service") |
| } |
| |
| if len(service.Ports) != len(extSvc.Spec.Ports) { |
| t.Fatalf("incorrect number of ports => %v, want %v", |
| len(service.Ports), len(extSvc.Spec.Ports)) |
| } |
| |
| if !service.External() { |
| t.Fatal("service should be external") |
| } |
| |
| if service.Hostname != ServiceHostname(serviceName, namespace, domainSuffix) { |
| t.Fatalf("service hostname incorrect => %q, want %q", |
| service.Hostname, ServiceHostname(serviceName, namespace, domainSuffix)) |
| } |
| } |
| |
| func TestExternalClusterLocalServiceConversion(t *testing.T) { |
| serviceName := "service1" |
| namespace := "default" |
| |
| extSvc := coreV1.Service{ |
| ObjectMeta: metaV1.ObjectMeta{ |
| Name: serviceName, |
| Namespace: namespace, |
| }, |
| Spec: coreV1.ServiceSpec{ |
| Ports: []coreV1.ServicePort{ |
| { |
| Name: "http", |
| Port: 80, |
| Protocol: coreV1.ProtocolTCP, |
| }, |
| }, |
| Type: coreV1.ServiceTypeExternalName, |
| ExternalName: "some.test.svc.cluster.local", |
| }, |
| } |
| |
| domainSuffix := "cluster.local" |
| |
| service := ConvertService(extSvc, domainSuffix, clusterID) |
| if service == nil { |
| t.Fatalf("could not convert external service") |
| } |
| |
| if len(service.Ports) != len(extSvc.Spec.Ports) { |
| t.Fatalf("incorrect number of ports => %v, want %v", |
| len(service.Ports), len(extSvc.Spec.Ports)) |
| } |
| |
| if !service.External() { |
| t.Fatal("ExternalName service (even if .cluster.local) should be external") |
| } |
| |
| if service.Hostname != ServiceHostname(serviceName, namespace, domainSuffix) { |
| t.Fatalf("service hostname incorrect => %q, want %q", |
| service.Hostname, ServiceHostname(serviceName, namespace, domainSuffix)) |
| } |
| } |
| |
| func TestLBServiceConversion(t *testing.T) { |
| serviceName := "service1" |
| namespace := "default" |
| |
| addresses := []coreV1.LoadBalancerIngress{ |
| { |
| IP: "127.68.32.112", |
| }, |
| { |
| IP: "127.68.32.113", |
| }, |
| { |
| Hostname: "127.68.32.114", |
| }, |
| { |
| Hostname: "127.68.32.115", |
| }, |
| } |
| |
| extSvc := coreV1.Service{ |
| ObjectMeta: metaV1.ObjectMeta{ |
| Name: serviceName, |
| Namespace: namespace, |
| }, |
| Spec: coreV1.ServiceSpec{ |
| Ports: []coreV1.ServicePort{ |
| { |
| Name: "http", |
| Port: 80, |
| Protocol: coreV1.ProtocolTCP, |
| }, |
| }, |
| Type: coreV1.ServiceTypeLoadBalancer, |
| }, |
| Status: coreV1.ServiceStatus{ |
| LoadBalancer: coreV1.LoadBalancerStatus{ |
| Ingress: addresses, |
| }, |
| }, |
| } |
| |
| service := ConvertService(extSvc, domainSuffix, clusterID) |
| if service == nil { |
| t.Fatalf("could not convert external service") |
| } |
| |
| gotAddresses := service.Attributes.ClusterExternalAddresses.GetAddressesFor(clusterID) |
| if len(gotAddresses) == 0 { |
| t.Fatalf("no load balancer addresses found") |
| } |
| |
| for i, addr := range addresses { |
| var want string |
| if len(addr.IP) > 0 { |
| want = addr.IP |
| } else { |
| want = addr.Hostname |
| } |
| got := gotAddresses[i] |
| if got != want { |
| t.Fatalf("Expected address %s but got %s", want, got) |
| } |
| } |
| } |
| |
| func TestSecureNamingSAN(t *testing.T) { |
| pod := &coreV1.Pod{} |
| |
| pod.Annotations = make(map[string]string) |
| |
| ns := "anything" |
| sa := "foo" |
| pod.Namespace = ns |
| pod.Spec.ServiceAccountName = sa |
| |
| san := SecureNamingSAN(pod) |
| |
| expectedSAN := fmt.Sprintf("spiffe://%v/ns/%v/sa/%v", spiffe.GetTrustDomain(), ns, sa) |
| |
| if san != expectedSAN { |
| t.Fatalf("SAN match failed, SAN:%v expectedSAN:%v", san, expectedSAN) |
| } |
| } |