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  }