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  }