| // 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 serviceentry |
| |
| import ( |
| "fmt" |
| "reflect" |
| "sort" |
| "strings" |
| "testing" |
| "time" |
| ) |
| |
| import ( |
| "istio.io/api/label" |
| networking "istio.io/api/networking/v1alpha3" |
| "k8s.io/apimachinery/pkg/types" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pilot/pkg/config/memory" |
| "github.com/apache/dubbo-go-pixiu/pilot/pkg/model" |
| "github.com/apache/dubbo-go-pixiu/pkg/cluster" |
| "github.com/apache/dubbo-go-pixiu/pkg/config" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/constants" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/host" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/labels" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/schema/collections" |
| "github.com/apache/dubbo-go-pixiu/pkg/config/schema/gvk" |
| "github.com/apache/dubbo-go-pixiu/pkg/spiffe" |
| "github.com/apache/dubbo-go-pixiu/pkg/test" |
| "github.com/apache/dubbo-go-pixiu/pkg/test/util/retry" |
| ) |
| |
| func createConfigs(configs []*config.Config, store model.ConfigStore, t testing.TB) { |
| t.Helper() |
| for _, cfg := range configs { |
| _, err := store.Create(*cfg) |
| if err != nil && strings.Contains(err.Error(), "item already exists") { |
| _, err := store.Update(*cfg) |
| if err != nil { |
| t.Fatalf("error occurred updating ServiceEntry config: %v", err) |
| } |
| } else if err != nil { |
| t.Fatalf("error occurred creating ServiceEntry config: %v", err) |
| } |
| } |
| } |
| |
| func callInstanceHandlers(instances []*model.WorkloadInstance, sd *Controller, ev model.Event, t testing.TB) { |
| t.Helper() |
| for _, instance := range instances { |
| sd.WorkloadInstanceHandler(instance, ev) |
| } |
| } |
| |
| func deleteConfigs(configs []*config.Config, store model.ConfigStore, t testing.TB) { |
| t.Helper() |
| for _, cfg := range configs { |
| err := store.Delete(cfg.GroupVersionKind, cfg.Name, cfg.Namespace, nil) |
| if err != nil { |
| t.Errorf("error occurred crearting ServiceEntry config: %v", err) |
| } |
| } |
| } |
| |
| type Event struct { |
| kind string |
| host string |
| namespace string |
| proxyIP string |
| endpoints int |
| pushReq *model.PushRequest |
| } |
| |
| type FakeXdsUpdater struct { |
| // Events tracks notifications received by the updater |
| Events chan Event |
| } |
| |
| var _ model.XDSUpdater = &FakeXdsUpdater{} |
| |
| func (fx *FakeXdsUpdater) EDSUpdate(_ model.ShardKey, hostname string, namespace string, entry []*model.IstioEndpoint) { |
| fx.Events <- Event{kind: "eds", host: hostname, namespace: namespace, endpoints: len(entry)} |
| } |
| |
| func (fx *FakeXdsUpdater) EDSCacheUpdate(_ model.ShardKey, _, _ string, _ []*model.IstioEndpoint) { |
| } |
| |
| func (fx *FakeXdsUpdater) ConfigUpdate(req *model.PushRequest) { |
| fx.Events <- Event{kind: "xds", pushReq: req} |
| } |
| |
| func (fx *FakeXdsUpdater) ProxyUpdate(_ cluster.ID, ip string) { |
| fx.Events <- Event{kind: "xds", proxyIP: ip} |
| } |
| |
| func (fx *FakeXdsUpdater) SvcUpdate(_ model.ShardKey, hostname string, namespace string, _ model.Event) { |
| fx.Events <- Event{kind: "svcupdate", host: hostname, namespace: namespace} |
| } |
| |
| func (fx *FakeXdsUpdater) RemoveShard(_ model.ShardKey) { |
| fx.Events <- Event{kind: "removeshard"} |
| } |
| |
| func waitUntilEvent(t testing.TB, ch chan Event, event Event) { |
| t.Helper() |
| for { |
| select { |
| case e := <-ch: |
| if e == event { |
| return |
| } |
| case <-time.After(2 * time.Second): |
| t.Fatalf("timed out waiting for event %v", event) |
| return |
| } |
| } |
| } |
| |
| func waitForEvent(t testing.TB, ch chan Event) Event { |
| t.Helper() |
| select { |
| case e := <-ch: |
| return e |
| case <-time.After(2 * time.Second): |
| t.Fatalf("timed out waiting for event") |
| return Event{} |
| } |
| } |
| |
| func initServiceDiscovery() (model.ConfigStore, *Controller, chan Event, func()) { |
| return initServiceDiscoveryWithOpts(false) |
| } |
| |
| // initServiceDiscoveryWithoutEvents initializes a test setup with no events. This avoids excessive attempts to push |
| // EDS updates to a full queue |
| func initServiceDiscoveryWithoutEvents(t test.Failer) (model.ConfigStore, *Controller) { |
| store := memory.Make(collections.Pilot) |
| configController := memory.NewController(store) |
| |
| stop := make(chan struct{}) |
| go configController.Run(stop) |
| |
| eventch := make(chan Event, 100) |
| xdsUpdater := &FakeXdsUpdater{ |
| Events: eventch, |
| } |
| go func() { |
| for { |
| select { |
| case <-stop: |
| return |
| case <-eventch: // drain |
| } |
| } |
| }() |
| |
| istioStore := model.MakeIstioStore(configController) |
| serviceController := NewController(configController, istioStore, xdsUpdater) |
| t.Cleanup(func() { |
| close(stop) |
| }) |
| return istioStore, serviceController |
| } |
| |
| func initServiceDiscoveryWithOpts(workloadOnly bool, opts ...Option) (model.ConfigStore, *Controller, chan Event, func()) { |
| store := memory.Make(collections.Pilot) |
| configController := memory.NewController(store) |
| |
| stop := make(chan struct{}) |
| go configController.Run(stop) |
| |
| eventch := make(chan Event, 100) |
| xdsUpdater := &FakeXdsUpdater{ |
| Events: eventch, |
| } |
| |
| istioStore := model.MakeIstioStore(configController) |
| var controller *Controller |
| if !workloadOnly { |
| controller = NewController(configController, istioStore, xdsUpdater, opts...) |
| } else { |
| controller = NewWorkloadEntryController(configController, istioStore, xdsUpdater, opts...) |
| } |
| go controller.Run(stop) |
| return istioStore, controller, eventch, func() { |
| close(stop) |
| } |
| } |
| |
| func TestServiceDiscoveryServices(t *testing.T) { |
| store, sd, eventCh, stopFn := initServiceDiscovery() |
| defer stopFn() |
| expectedServices := []*model.Service{ |
| makeService("*.istio.io", "httpDNSRR", constants.UnspecifiedIP, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSRoundRobinLB), |
| makeService("*.google.com", "httpDNS", constants.UnspecifiedIP, map[string]int{"http-port": 80, "http-alt-port": 8080}, true, model.DNSLB), |
| makeService("tcpstatic.com", "tcpStatic", "172.217.0.1", map[string]int{"tcp-444": 444}, true, model.ClientSideLB), |
| } |
| |
| createConfigs([]*config.Config{httpDNS, httpDNSRR, tcpStatic}, store, t) |
| |
| waitUntilEvent(t, eventCh, Event{ |
| kind: "svcupdate", |
| host: "*.google.com", |
| namespace: httpDNS.Namespace, |
| }) |
| |
| waitUntilEvent(t, eventCh, Event{ |
| kind: "svcupdate", |
| host: "*.istio.io", |
| namespace: httpDNSRR.Namespace, |
| }) |
| |
| waitUntilEvent(t, eventCh, Event{ |
| kind: "svcupdate", |
| host: "tcpstatic.com", |
| namespace: tcpStatic.Namespace, |
| }) |
| |
| services := sd.Services() |
| sortServices(services) |
| sortServices(expectedServices) |
| if err := compare(t, services, expectedServices); err != nil { |
| t.Error(err) |
| } |
| } |
| |
| func TestServiceDiscoveryGetService(t *testing.T) { |
| hostname := "*.google.com" |
| hostDNE := "does.not.exist.local" |
| |
| store, sd, eventCh, stopFn := initServiceDiscovery() |
| defer stopFn() |
| |
| createConfigs([]*config.Config{httpDNS, tcpStatic}, store, t) |
| waitForEvent(t, eventCh) |
| waitForEvent(t, eventCh) |
| service := sd.GetService(host.Name(hostDNE)) |
| if service != nil { |
| t.Errorf("GetService(%q) => should not exist, got %s", hostDNE, service.Hostname) |
| } |
| |
| service = sd.GetService(host.Name(hostname)) |
| if service == nil { |
| t.Fatalf("GetService(%q) => should exist", hostname) |
| } |
| if service.Hostname != host.Name(hostname) { |
| t.Errorf("GetService(%q) => %q, want %q", hostname, service.Hostname, hostname) |
| } |
| } |
| |
| // TestServiceDiscoveryServiceUpdate test various add/update/delete events for ServiceEntry |
| // nolint: lll |
| func TestServiceDiscoveryServiceUpdate(t *testing.T) { |
| store, sd, events, stopFn := initServiceDiscovery() |
| defer stopFn() |
| // httpStaticOverlayUpdated is the same as httpStaticOverlay but with an extra endpoint added to test updates |
| httpStaticOverlayUpdated := func() *config.Config { |
| c := httpStaticOverlay.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| se.Endpoints = append(se.Endpoints, &networking.WorkloadEntry{ |
| Address: "6.6.6.6", |
| Labels: map[string]string{"other": "bar"}, |
| }) |
| return &c |
| }() |
| // httpStaticOverlayUpdatedInstance is the same as httpStaticOverlayUpdated but with an extra endpoint added that has the same address |
| httpStaticOverlayUpdatedInstance := func() *config.Config { |
| c := httpStaticOverlayUpdated.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| se.Endpoints = append(se.Endpoints, &networking.WorkloadEntry{ |
| Address: "6.6.6.6", |
| Labels: map[string]string{"some-new-label": "bar"}, |
| }) |
| return &c |
| }() |
| |
| // httpStaticOverlayUpdatedNop is the same as httpStaticOverlayUpdated but with a NOP change |
| httpStaticOverlayUpdatedNop := func() *config.Config { |
| c := httpStaticOverlayUpdated.DeepCopy() |
| return &c |
| }() |
| |
| // httpStaticOverlayUpdatedNs is the same as httpStaticOverlay but with an extra endpoint and different namespace added to test updates |
| httpStaticOverlayUpdatedNs := func() *config.Config { |
| c := httpStaticOverlay.DeepCopy() |
| c.Namespace = "other" |
| se := c.Spec.(*networking.ServiceEntry) |
| se.Endpoints = append(se.Endpoints, &networking.WorkloadEntry{ |
| Address: "7.7.7.7", |
| Labels: map[string]string{"namespace": "bar"}, |
| }) |
| return &c |
| }() |
| |
| // Setup the expected instances for `httpStatic`. This will be added/removed from as we add various configs |
| baseInstances := []*model.ServiceInstance{ |
| makeInstance(httpStatic, "2.2.2.2", 7080, httpStatic.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| makeInstance(httpStatic, "2.2.2.2", 18080, httpStatic.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS), |
| makeInstance(httpStatic, "3.3.3.3", 1080, httpStatic.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| makeInstance(httpStatic, "3.3.3.3", 8080, httpStatic.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS), |
| makeInstance(httpStatic, "4.4.4.4", 1080, httpStatic.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"foo": "bar"}, PlainText), |
| makeInstance(httpStatic, "4.4.4.4", 8080, httpStatic.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"foo": "bar"}, PlainText), |
| } |
| |
| t.Run("simple entry", func(t *testing.T) { |
| // Create a SE, expect the base instances |
| createConfigs([]*config.Config{httpStatic}, store, t) |
| instances := baseInstances |
| expectServiceInstances(t, sd, httpStatic, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "*.google.com", namespace: httpStatic.Namespace}, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: httpStatic.Spec.(*networking.ServiceEntry).Hosts[0], Namespace: httpStatic.Namespace}: {}}}}) |
| }) |
| |
| t.Run("add entry", func(t *testing.T) { |
| // Create another SE for the same host, expect these instances to get added |
| createConfigs([]*config.Config{httpStaticOverlay}, store, t) |
| instances := append(baseInstances, |
| makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText)) |
| expectServiceInstances(t, sd, httpStatic, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "*.google.com", namespace: httpStaticOverlay.Namespace}, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: httpStaticOverlay.Spec.(*networking.ServiceEntry).Hosts[0], Namespace: httpStaticOverlay.Namespace}: {}}}}) |
| }) |
| |
| t.Run("add endpoint", func(t *testing.T) { |
| // Update the SE for the same host, expect these instances to get added |
| createConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t) |
| instances := append(baseInstances, |
| makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText), |
| makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText)) |
| expectServiceInstances(t, sd, httpStatic, 0, instances) |
| expectEvents(t, events, Event{kind: "eds", host: "*.google.com", namespace: httpStaticOverlay.Namespace, endpoints: len(instances)}) |
| |
| // Make a NOP change, expect that there are no changes |
| createConfigs([]*config.Config{httpStaticOverlayUpdatedNop}, store, t) |
| expectServiceInstances(t, sd, httpStaticOverlayUpdatedNop, 0, instances) |
| // TODO this could trigger no changes |
| expectEvents(t, events, Event{kind: "eds", host: "*.google.com", namespace: httpStaticOverlay.Namespace, endpoints: len(instances)}) |
| }) |
| |
| t.Run("overlapping address", func(t *testing.T) { |
| // Add another SE with an additional endpoint with a matching address |
| createConfigs([]*config.Config{httpStaticOverlayUpdatedInstance}, store, t) |
| instances := append(baseInstances, |
| makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText), |
| makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText), |
| makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"some-new-label": "bar"}, PlainText)) |
| expectServiceInstances(t, sd, httpStaticOverlayUpdatedInstance, 0, instances) |
| proxyInstances := []*model.ServiceInstance{ |
| makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText), |
| makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"some-new-label": "bar"}, PlainText), |
| } |
| expectProxyInstances(t, sd, proxyInstances, "6.6.6.6") |
| // TODO 45 is wrong |
| expectEvents(t, events, Event{kind: "eds", host: "*.google.com", namespace: httpStaticOverlay.Namespace, endpoints: len(instances)}) |
| |
| // Remove the additional endpoint |
| createConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t) |
| instances = append(baseInstances, |
| makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText), |
| makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText)) |
| expectServiceInstances(t, sd, httpStatic, 0, instances) |
| proxyInstances = []*model.ServiceInstance{ |
| makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText), |
| } |
| expectProxyInstances(t, sd, proxyInstances, "6.6.6.6") |
| expectEvents(t, events, Event{kind: "eds", host: "*.google.com", namespace: httpStaticOverlay.Namespace, endpoints: len(instances)}) |
| }) |
| |
| t.Run("update removes endpoint", func(t *testing.T) { |
| // Update the SE for the same host to remove the endpoint |
| createConfigs([]*config.Config{httpStaticOverlay}, store, t) |
| instances := append(baseInstances, |
| makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText)) |
| expectServiceInstances(t, sd, httpStaticOverlay, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "eds", host: "*.google.com", namespace: httpStaticOverlay.Namespace, endpoints: len(instances)}) |
| }) |
| |
| t.Run("different namespace", func(t *testing.T) { |
| // Update the SE for the same host in a different ns, expect these instances to get added |
| createConfigs([]*config.Config{httpStaticOverlayUpdatedNs}, store, t) |
| instances := []*model.ServiceInstance{ |
| makeInstance(httpStaticOverlayUpdatedNs, "5.5.5.5", 4567, httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText), |
| makeInstance(httpStaticOverlayUpdatedNs, "7.7.7.7", 4567, httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"namespace": "bar"}, PlainText), |
| } |
| // This lookup is per-namespace, so we should only see the objects in the same namespace |
| expectServiceInstances(t, sd, httpStaticOverlayUpdatedNs, 0, instances) |
| // Expect a full push, as the Service has changed |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "*.google.com", namespace: "other"}, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Hosts[0], Namespace: httpStaticOverlayUpdatedNs.Namespace}: {}}}}) |
| }) |
| |
| t.Run("delete entry", func(t *testing.T) { |
| // Delete the additional SE in same namespace , expect it to get removed |
| deleteConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t) |
| expectServiceInstances(t, sd, httpStatic, 0, baseInstances) |
| // Check the other namespace is untouched |
| instances := []*model.ServiceInstance{ |
| makeInstance(httpStaticOverlayUpdatedNs, "5.5.5.5", 4567, httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText), |
| makeInstance(httpStaticOverlayUpdatedNs, "7.7.7.7", 4567, httpStaticOverlayUpdatedNs.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"namespace": "bar"}, PlainText), |
| } |
| expectServiceInstances(t, sd, httpStaticOverlayUpdatedNs, 0, instances) |
| // svcUpdate is not triggered since `httpStatic` is there and has instances, so we should |
| // not delete the endpoints shards of "*.google.com". We xpect a full push as the service has changed. |
| expectEvents(t, events, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: "*.google.com", Namespace: httpStaticOverlayUpdated.Namespace}: {}}}}, |
| ) |
| |
| // delete httpStatic, no "*.google.com" service exists now. |
| deleteConfigs([]*config.Config{httpStatic}, store, t) |
| // svcUpdate is triggered since "*.google.com" in same namespace is deleted and |
| // we need to delete endpoint shards. We expect a full push as the service has changed. |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "*.google.com", namespace: httpStatic.Namespace}, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: "*.google.com", Namespace: httpStaticOverlayUpdated.Namespace}: {}}}}, |
| ) |
| |
| // add back httpStatic |
| createConfigs([]*config.Config{httpStatic}, store, t) |
| instances = baseInstances |
| expectServiceInstances(t, sd, httpStatic, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "*.google.com", namespace: httpStatic.Namespace}, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: httpStatic.Spec.(*networking.ServiceEntry).Hosts[0], Namespace: httpStatic.Namespace}: {}}}}) |
| |
| // Add back the ServiceEntry, expect these instances to get added |
| createConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t) |
| instances = append(baseInstances, |
| makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText), |
| makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText)) |
| expectServiceInstances(t, sd, httpStatic, 0, instances) |
| // Service change, so we need a full push |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "*.google.com", namespace: httpStaticOverlay.Namespace}, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: "*.google.com", Namespace: httpStaticOverlayUpdated.Namespace}: {}}}}) |
| }) |
| |
| t.Run("change host", func(t *testing.T) { |
| // same as httpStaticOverlayUpdated but with an additional host |
| httpStaticHost := func() *config.Config { |
| c := httpStaticOverlayUpdated.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| se.Hosts = append(se.Hosts, "other.com") |
| return &c |
| }() |
| createConfigs([]*config.Config{httpStaticHost}, store, t) |
| instances := append(baseInstances, |
| makeInstance(httpStaticOverlay, "5.5.5.5", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText), |
| makeInstance(httpStaticOverlay, "6.6.6.6", 4567, httpStaticOverlay.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText)) |
| // This is not applied, just to make makeInstance pick the right service. |
| otherHost := func() *config.Config { |
| c := httpStaticOverlayUpdated.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| se.Hosts = []string{"other.com"} |
| return &c |
| }() |
| instances2 := []*model.ServiceInstance{ |
| makeInstance(otherHost, "5.5.5.5", 4567, httpStaticHost.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"overlay": "bar"}, PlainText), |
| makeInstance(otherHost, "6.6.6.6", 4567, httpStaticHost.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"other": "bar"}, PlainText), |
| } |
| expectServiceInstances(t, sd, httpStaticHost, 0, instances, instances2) |
| // Service change, so we need a full push |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "other.com", namespace: httpStaticOverlayUpdated.Namespace}, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: "other.com", Namespace: httpStaticOverlayUpdated.Namespace}: {}}}}) // service added |
| |
| // restore this config and remove the added host. |
| createConfigs([]*config.Config{httpStaticOverlayUpdated}, store, t) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "other.com", namespace: httpStatic.Namespace}, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: "other.com", Namespace: httpStaticOverlayUpdated.Namespace}: {}}}}) // service deleted |
| }) |
| |
| t.Run("change dns endpoints", func(t *testing.T) { |
| // Setup the expected instances for DNS. This will be added/removed from as we add various configs |
| instances1 := []*model.ServiceInstance{ |
| makeInstance(tcpDNS, "lon.google.com", 444, tcpDNS.Spec.(*networking.ServiceEntry).Ports[0], |
| nil, MTLS), |
| makeInstance(tcpDNS, "in.google.com", 444, tcpDNS.Spec.(*networking.ServiceEntry).Ports[0], |
| nil, MTLS), |
| } |
| |
| // This is not applied, just to make makeInstance pick the right service. |
| tcpDNSUpdated := func() *config.Config { |
| c := tcpDNS.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| se.Endpoints = []*networking.WorkloadEntry{ |
| { |
| Address: "lon.google.com", |
| Labels: map[string]string{label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel}, |
| }, |
| } |
| return &c |
| }() |
| |
| instances2 := []*model.ServiceInstance{ |
| makeInstance(tcpDNS, "lon.google.com", 444, tcpDNS.Spec.(*networking.ServiceEntry).Ports[0], |
| nil, MTLS), |
| } |
| |
| createConfigs([]*config.Config{tcpDNS}, store, t) |
| expectServiceInstances(t, sd, tcpDNS, 0, instances1) |
| // Service change, so we need a full push |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "tcpdns.com", namespace: tcpDNS.Namespace}, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{Kind: gvk.ServiceEntry, Name: "tcpdns.com", Namespace: tcpDNS.Namespace}: {}}}}) // service added |
| |
| // now update the config |
| createConfigs([]*config.Config{tcpDNSUpdated}, store, t) |
| expectEvents(t, events, |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{{ |
| Kind: gvk.ServiceEntry, Name: "tcpdns.com", |
| Namespace: tcpDNSUpdated.Namespace, |
| }: {}}}}) // service deleted |
| expectServiceInstances(t, sd, tcpDNS, 0, instances2) |
| }) |
| |
| t.Run("change workload selector", func(t *testing.T) { |
| // same as selector but with an additional host |
| selector1 := func() *config.Config { |
| c := httpStaticOverlay.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| se.Hosts = append(se.Hosts, "selector1.com") |
| se.Endpoints = nil |
| se.WorkloadSelector = &networking.WorkloadSelector{ |
| Labels: map[string]string{"app": "wle"}, |
| } |
| return &c |
| }() |
| createConfigs([]*config.Config{selector1}, store, t) |
| // Service change, so we need a full push |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "selector1.com", namespace: httpStaticOverlay.Namespace}, |
| Event{kind: "svcupdate", host: "*.google.com", namespace: httpStaticOverlay.Namespace}, |
| |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{ |
| {Kind: gvk.ServiceEntry, Name: "*.google.com", Namespace: selector1.Namespace}: {}, |
| {Kind: gvk.ServiceEntry, Name: "selector1.com", Namespace: selector1.Namespace}: {}, |
| }}}) // service added |
| |
| selector1Updated := func() *config.Config { |
| c := selector1.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| se.WorkloadSelector = &networking.WorkloadSelector{ |
| Labels: map[string]string{"app": "wle1"}, |
| } |
| return &c |
| }() |
| createConfigs([]*config.Config{selector1Updated}, store, t) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "*.google.com", namespace: httpStaticOverlay.Namespace}, |
| Event{kind: "svcupdate", host: "selector1.com", namespace: httpStaticOverlay.Namespace}, |
| |
| Event{kind: "xds", pushReq: &model.PushRequest{ConfigsUpdated: map[model.ConfigKey]struct{}{ |
| {Kind: gvk.ServiceEntry, Name: "*.google.com", Namespace: selector1.Namespace}: {}, |
| {Kind: gvk.ServiceEntry, Name: "selector1.com", Namespace: selector1.Namespace}: {}, |
| }}}) // service updated |
| }) |
| } |
| |
| func TestServiceDiscoveryWorkloadUpdate(t *testing.T) { |
| store, sd, events, stopFn := initServiceDiscovery() |
| defer stopFn() |
| |
| // Setup a couple workload entries for test. These will be selected by the `selector` SE |
| wle := createWorkloadEntry("wl", selector.Name, |
| &networking.WorkloadEntry{ |
| Address: "2.2.2.2", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: "default", |
| }) |
| wle2 := createWorkloadEntry("wl2", selector.Name, |
| &networking.WorkloadEntry{ |
| Address: "3.3.3.3", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: "default", |
| }) |
| wle3 := createWorkloadEntry("wl3", selector.Name, |
| &networking.WorkloadEntry{ |
| Address: "abc.def", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: "default", |
| }) |
| dnsWle := createWorkloadEntry("dnswl", dnsSelector.Namespace, |
| &networking.WorkloadEntry{ |
| Address: "4.4.4.4", |
| Labels: map[string]string{"app": "dns-wle"}, |
| ServiceAccount: "default", |
| }) |
| |
| t.Run("service entry", func(t *testing.T) { |
| // Add just the ServiceEntry with selector. We should see no instances |
| createConfigs([]*config.Config{selector}, store, t) |
| instances := []*model.ServiceInstance{} |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "selector.com", namespace: selector.Namespace}, |
| Event{kind: "xds"}) |
| }) |
| |
| t.Run("add workload", func(t *testing.T) { |
| // Add a WLE, we expect this to update |
| createConfigs([]*config.Config{wle}, store, t) |
| |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], |
| map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], |
| map[string]string{"app": "wle"}, "default"), |
| } |
| for _, i := range instances { |
| i.Endpoint.WorkloadName = "wl" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "xds", proxyIP: "2.2.2.2"}, |
| Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 2}, |
| ) |
| }) |
| |
| t.Run("add dns service entry", func(t *testing.T) { |
| // Add just the ServiceEntry with selector. We should see no instances |
| createConfigs([]*config.Config{dnsSelector}, store, t) |
| instances := []*model.ServiceInstance{} |
| expectProxyInstances(t, sd, instances, "4.4.4.4") |
| expectServiceInstances(t, sd, dnsSelector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "dns.selector.com", namespace: dnsSelector.Namespace}, |
| Event{kind: "xds"}) |
| }) |
| |
| t.Run("add dns workload", func(t *testing.T) { |
| // Add a WLE, we expect this to update |
| createConfigs([]*config.Config{dnsWle}, store, t) |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(dnsSelector, "4.4.4.4", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], |
| map[string]string{"app": "dns-wle"}, "default"), |
| makeInstanceWithServiceAccount(dnsSelector, "4.4.4.4", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], |
| map[string]string{"app": "dns-wle"}, "default"), |
| } |
| for _, i := range instances { |
| i.Endpoint.WorkloadName = "dnswl" |
| i.Endpoint.Namespace = dnsSelector.Namespace |
| } |
| expectProxyInstances(t, sd, instances, "4.4.4.4") |
| expectServiceInstances(t, sd, dnsSelector, 0, instances) |
| expectEvents(t, events, Event{kind: "xds"}) |
| }) |
| |
| t.Run("another workload", func(t *testing.T) { |
| // Add a different WLE |
| createConfigs([]*config.Config{wle2}, store, t) |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"), |
| } |
| for _, i := range instances { |
| i.Endpoint.WorkloadName = "wl" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| instances = append(instances, |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default")) |
| for _, i := range instances[2:] { |
| i.Endpoint.WorkloadName = "wl2" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "xds", proxyIP: "3.3.3.3"}, |
| Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 4}, |
| ) |
| }) |
| |
| t.Run("ignore host workload", func(t *testing.T) { |
| // Add a WLE with host address. Should be ignored by static service entry. |
| createConfigs([]*config.Config{wle3}, store, t) |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"), |
| } |
| for _, i := range instances { |
| i.Endpoint.WorkloadName = "wl" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| instances = append(instances, |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default")) |
| for _, i := range instances[2:] { |
| i.Endpoint.WorkloadName = "wl2" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "xds", proxyIP: "abc.def"}, |
| ) |
| }) |
| |
| t.Run("deletion", func(t *testing.T) { |
| // Delete the configs, it should be gone |
| deleteConfigs([]*config.Config{wle2}, store, t) |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"), |
| } |
| for _, i := range instances { |
| i.Endpoint.WorkloadName = "wl" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 2}) |
| |
| // Delete the other config |
| deleteConfigs([]*config.Config{wle}, store, t) |
| instances = []*model.ServiceInstance{} |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectEvents(t, events, Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 0}) |
| |
| // Add the config back |
| createConfigs([]*config.Config{wle}, store, t) |
| instances = []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"), |
| } |
| for _, i := range instances { |
| i.Endpoint.WorkloadName = "wl" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "xds", proxyIP: "2.2.2.2"}, |
| Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 2}, |
| ) |
| }) |
| } |
| |
| func TestServiceDiscoveryWorkloadChangeLabel(t *testing.T) { |
| store, sd, events, stopFn := initServiceDiscovery() |
| defer stopFn() |
| |
| wle := createWorkloadEntry("wl", selector.Name, |
| &networking.WorkloadEntry{ |
| Address: "2.2.2.2", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: "default", |
| }) |
| |
| wle2 := createWorkloadEntry("wl", selector.Name, |
| &networking.WorkloadEntry{ |
| Address: "2.2.2.2", |
| Labels: map[string]string{"app": "wle2"}, |
| ServiceAccount: "default", |
| }) |
| wle3 := createWorkloadEntry("wl3", selector.Name, |
| &networking.WorkloadEntry{ |
| Address: "3.3.3.3", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: "default", |
| }) |
| |
| t.Run("service entry", func(t *testing.T) { |
| // Add just the ServiceEntry with selector. We should see no instances |
| createConfigs([]*config.Config{selector}, store, t) |
| instances := []*model.ServiceInstance{} |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "selector.com", namespace: selector.Namespace}, |
| Event{kind: "xds"}) |
| }) |
| |
| t.Run("change label removing all", func(t *testing.T) { |
| // Add a WLE, we expect this to update |
| createConfigs([]*config.Config{wle}, store, t) |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], |
| map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], |
| map[string]string{"app": "wle"}, "default"), |
| } |
| for _, i := range instances { |
| i.Endpoint.WorkloadName = "wl" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "xds", proxyIP: "2.2.2.2"}, |
| Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 2}, |
| ) |
| |
| createConfigs([]*config.Config{wle2}, store, t) |
| instances = []*model.ServiceInstance{} |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectEvents(t, events, Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 0}) |
| }) |
| |
| t.Run("change label removing one", func(t *testing.T) { |
| // Add a WLE, we expect this to update |
| createConfigs([]*config.Config{wle}, store, t) |
| expectEvents(t, events, |
| Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 2}, |
| ) |
| // add a wle, expect this to be an add |
| createConfigs([]*config.Config{wle3}, store, t) |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], |
| map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], |
| map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], |
| map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], |
| map[string]string{"app": "wle"}, "default"), |
| } |
| for _, i := range instances[:2] { |
| i.Endpoint.WorkloadName = "wl" |
| i.Endpoint.Namespace = selector.Name |
| } |
| for _, i := range instances[2:] { |
| i.Endpoint.WorkloadName = "wl3" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectProxyInstances(t, sd, instances[:2], "2.2.2.2") |
| expectProxyInstances(t, sd, instances[2:], "3.3.3.3") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "xds", proxyIP: "3.3.3.3"}, |
| Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 4}, |
| ) |
| |
| createConfigs([]*config.Config{wle2}, store, t) |
| instances = []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], |
| map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], |
| map[string]string{"app": "wle"}, "default"), |
| } |
| for _, i := range instances { |
| i.Endpoint.WorkloadName = "wl3" |
| i.Endpoint.Namespace = selector.Name |
| } |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectProxyInstances(t, sd, instances, "3.3.3.3") |
| expectEvents(t, events, Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 2}) |
| }) |
| } |
| |
| func TestServiceDiscoveryWorkloadInstance(t *testing.T) { |
| store, sd, events, stopFn := initServiceDiscovery() |
| defer stopFn() |
| |
| // Setup a couple of workload instances for test. These will be selected by the `selector` SE |
| fi1 := &model.WorkloadInstance{ |
| Name: selector.Name, |
| Namespace: selector.Namespace, |
| Endpoint: &model.IstioEndpoint{ |
| Address: "2.2.2.2", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: spiffe.MustGenSpiffeURI(selector.Name, "default"), |
| TLSMode: model.IstioMutualTLSModeLabel, |
| }, |
| } |
| |
| fi2 := &model.WorkloadInstance{ |
| Name: "some-other-name", |
| Namespace: selector.Namespace, |
| Endpoint: &model.IstioEndpoint{ |
| Address: "3.3.3.3", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: spiffe.MustGenSpiffeURI(selector.Name, "default"), |
| TLSMode: model.IstioMutualTLSModeLabel, |
| }, |
| } |
| |
| fi3 := &model.WorkloadInstance{ |
| Name: "another-name", |
| Namespace: dnsSelector.Namespace, |
| Endpoint: &model.IstioEndpoint{ |
| Address: "2.2.2.2", |
| Labels: map[string]string{"app": "dns-wle"}, |
| ServiceAccount: spiffe.MustGenSpiffeURI(dnsSelector.Name, "default"), |
| TLSMode: model.IstioMutualTLSModeLabel, |
| }, |
| } |
| |
| t.Run("service entry", func(t *testing.T) { |
| // Add just the ServiceEntry with selector. We should see no instances |
| createConfigs([]*config.Config{selector}, store, t) |
| instances := []*model.ServiceInstance{} |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "selector.com", namespace: selector.Namespace}, |
| Event{kind: "xds"}) |
| }) |
| |
| t.Run("add another service entry", func(t *testing.T) { |
| createConfigs([]*config.Config{dnsSelector}, store, t) |
| instances := []*model.ServiceInstance{} |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, dnsSelector, 0, instances) |
| expectEvents(t, events, |
| Event{kind: "svcupdate", host: "dns.selector.com", namespace: dnsSelector.Namespace}, |
| Event{kind: "xds"}) |
| }) |
| |
| t.Run("add workload instance", func(t *testing.T) { |
| // Add a workload instance, we expect this to update |
| callInstanceHandlers([]*model.WorkloadInstance{fi1}, sd, model.EventAdd, t) |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"), |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 2}) |
| }) |
| |
| t.Run("another workload instance", func(t *testing.T) { |
| // Add a different instance |
| callInstanceHandlers([]*model.WorkloadInstance{fi2}, sd, model.EventAdd, t) |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"), |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| instances = append(instances, |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "3.3.3.3", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default")) |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 4}) |
| }) |
| |
| t.Run("delete workload instance", func(t *testing.T) { |
| // Delete the instances, it should be gone |
| callInstanceHandlers([]*model.WorkloadInstance{fi2}, sd, model.EventDelete, t) |
| instances := []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 444, |
| selector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "wle"}, "default"), |
| makeInstanceWithServiceAccount(selector, "2.2.2.2", 445, |
| selector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "wle"}, "default"), |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 2}) |
| |
| key := instancesKey{namespace: selector.Namespace, hostname: "selector.com"} |
| namespacedName := types.NamespacedName{Namespace: selector.Namespace, Name: selector.Name} |
| if len(sd.serviceInstances.ip2instance) != 1 { |
| t.Fatalf("service instances store `ip2instance` memory leak, expect 1, got %d", len(sd.serviceInstances.ip2instance)) |
| } |
| if len(sd.serviceInstances.instances[key]) != 1 { |
| t.Fatalf("service instances store `instances` memory leak, expect 1, got %d", len(sd.serviceInstances.instances[key])) |
| } |
| if len(sd.serviceInstances.instancesBySE[namespacedName]) != 1 { |
| t.Fatalf("service instances store `instancesBySE` memory leak, expect 1, got %d", len(sd.serviceInstances.instancesBySE[namespacedName])) |
| } |
| |
| // The following sections mimic this scenario: |
| // f1 starts terminating, f3 picks up the IP, f3 delete event (pod |
| // not ready yet) comes before f1 |
| // |
| // Delete f3 event |
| callInstanceHandlers([]*model.WorkloadInstance{fi3}, sd, model.EventDelete, t) |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| |
| // Delete f1 event |
| callInstanceHandlers([]*model.WorkloadInstance{fi1}, sd, model.EventDelete, t) |
| instances = []*model.ServiceInstance{} |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, selector, 0, instances) |
| expectEvents(t, events, Event{kind: "eds", host: "selector.com", namespace: selector.Namespace, endpoints: 0}) |
| |
| if len(sd.serviceInstances.ip2instance) != 0 { |
| t.Fatalf("service instances store `ip2instance` memory leak, expect 0, got %d", len(sd.serviceInstances.ip2instance)) |
| } |
| if len(sd.serviceInstances.instances[key]) != 0 { |
| t.Fatalf("service instances store `instances` memory leak, expect 0, got %d", len(sd.serviceInstances.instances[key])) |
| } |
| if len(sd.serviceInstances.instancesBySE[namespacedName]) != 0 { |
| t.Fatalf("service instances store `instancesBySE` memory leak, expect 0, got %d", len(sd.serviceInstances.instancesBySE[namespacedName])) |
| } |
| |
| // Add f3 event |
| callInstanceHandlers([]*model.WorkloadInstance{fi3}, sd, model.EventAdd, t) |
| instances = []*model.ServiceInstance{ |
| makeInstanceWithServiceAccount(dnsSelector, "2.2.2.2", 444, |
| dnsSelector.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"app": "dns-wle"}, "default"), |
| makeInstanceWithServiceAccount(dnsSelector, "2.2.2.2", 445, |
| dnsSelector.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"app": "dns-wle"}, "default"), |
| } |
| expectProxyInstances(t, sd, instances, "2.2.2.2") |
| expectServiceInstances(t, sd, dnsSelector, 0, instances) |
| expectEvents(t, events, Event{kind: "eds", host: "dns.selector.com", namespace: dnsSelector.Namespace, endpoints: 2}) |
| }) |
| } |
| |
| func expectProxyInstances(t testing.TB, sd *Controller, expected []*model.ServiceInstance, ip string) { |
| t.Helper() |
| // The system is eventually consistent, so add some retries |
| retry.UntilSuccessOrFail(t, func() error { |
| instances := sd.GetProxyServiceInstances(&model.Proxy{IPAddresses: []string{ip}, Metadata: &model.NodeMetadata{}}) |
| sortServiceInstances(instances) |
| sortServiceInstances(expected) |
| if err := compare(t, instances, expected); err != nil { |
| return err |
| } |
| return nil |
| }, retry.Converge(2), retry.Timeout(time.Second*5)) |
| } |
| |
| func expectEvents(t testing.TB, ch chan Event, events ...Event) { |
| cmpPushRequest := func(expectReq, gotReq *model.PushRequest) bool { |
| var expectConfigs, gotConfigs map[model.ConfigKey]struct{} |
| if expectReq != nil { |
| expectConfigs = expectReq.ConfigsUpdated |
| } |
| if gotReq != nil { |
| gotConfigs = gotReq.ConfigsUpdated |
| } |
| |
| return reflect.DeepEqual(expectConfigs, gotConfigs) |
| } |
| |
| t.Helper() |
| for _, event := range events { |
| got := waitForEvent(t, ch) |
| if event.pushReq != nil { |
| if !cmpPushRequest(event.pushReq, got.pushReq) { |
| t.Fatalf("expected event %+v %+v, got %+v %+v", event, event.pushReq, got, got.pushReq) |
| } |
| } |
| |
| event.pushReq, got.pushReq = nil, nil |
| if event != got { |
| t.Fatalf("expected event %+v, got %+v", event, got) |
| } |
| } |
| // Drain events |
| for { |
| select { |
| case e := <-ch: |
| t.Logf("ignoring event %+v", e) |
| if len(events) == 0 { |
| t.Fatalf("got unexpected event %+v", e) |
| } |
| default: |
| return |
| } |
| } |
| } |
| |
| func expectServiceInstances(t testing.TB, sd *Controller, cfg *config.Config, port int, expected ...[]*model.ServiceInstance) { |
| t.Helper() |
| svcs := convertServices(*cfg) |
| if len(svcs) != len(expected) { |
| t.Fatalf("got more services than expected: %v vs %v", len(svcs), len(expected)) |
| } |
| // The system is eventually consistent, so add some retries |
| retry.UntilSuccessOrFail(t, func() error { |
| for i, svc := range svcs { |
| instances := sd.InstancesByPort(svc, port, nil) |
| sortServiceInstances(instances) |
| sortServiceInstances(expected[i]) |
| if err := compare(t, instances, expected[i]); err != nil { |
| return fmt.Errorf("%d: %v", i, err) |
| } |
| } |
| return nil |
| }, retry.Converge(2), retry.Timeout(time.Second*1)) |
| } |
| |
| func TestServiceDiscoveryGetProxyServiceInstances(t *testing.T) { |
| store, sd, _, stopFn := initServiceDiscovery() |
| defer stopFn() |
| |
| createConfigs([]*config.Config{httpStatic, tcpStatic}, store, t) |
| |
| expectProxyInstances(t, sd, []*model.ServiceInstance{ |
| makeInstance(httpStatic, "2.2.2.2", 7080, httpStatic.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| makeInstance(httpStatic, "2.2.2.2", 18080, httpStatic.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS), |
| makeInstance(tcpStatic, "2.2.2.2", 444, tcpStatic.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| }, "2.2.2.2") |
| } |
| |
| // Keeping this test for legacy - but it never happens in real life. |
| func TestServiceDiscoveryInstances(t *testing.T) { |
| store, sd, _, stopFn := initServiceDiscovery() |
| defer stopFn() |
| |
| createConfigs([]*config.Config{httpDNS, tcpStatic}, store, t) |
| |
| expectServiceInstances(t, sd, httpDNS, 0, []*model.ServiceInstance{ |
| makeInstance(httpDNS, "us.google.com", 7080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| makeInstance(httpDNS, "us.google.com", 18080, httpDNS.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS), |
| makeInstance(httpDNS, "uk.google.com", 1080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| makeInstance(httpDNS, "uk.google.com", 8080, httpDNS.Spec.(*networking.ServiceEntry).Ports[1], nil, MTLS), |
| makeInstance(httpDNS, "de.google.com", 80, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"foo": "bar"}, MTLS), |
| makeInstance(httpDNS, "de.google.com", 8080, httpDNS.Spec.(*networking.ServiceEntry).Ports[1], map[string]string{"foo": "bar"}, MTLS), |
| }) |
| } |
| |
| // Keeping this test for legacy - but it never happens in real life. |
| func TestServiceDiscoveryInstances1Port(t *testing.T) { |
| store, sd, _, stopFn := initServiceDiscovery() |
| defer stopFn() |
| |
| createConfigs([]*config.Config{httpDNS, tcpStatic}, store, t) |
| |
| expectServiceInstances(t, sd, httpDNS, 80, []*model.ServiceInstance{ |
| makeInstance(httpDNS, "us.google.com", 7080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| makeInstance(httpDNS, "uk.google.com", 1080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| makeInstance(httpDNS, "de.google.com", 80, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"foo": "bar"}, MTLS), |
| }) |
| } |
| |
| func TestNonServiceConfig(t *testing.T) { |
| store, sd, _, stopFn := initServiceDiscovery() |
| defer stopFn() |
| |
| // Create a non-service configuration element. This should not affect the service registry at all. |
| cfg := config.Config{ |
| Meta: config.Meta{ |
| GroupVersionKind: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().GroupVersionKind(), |
| Name: "fakeDestinationRule", |
| Namespace: "default", |
| Domain: "cluster.local", |
| CreationTimestamp: GlobalTime, |
| }, |
| Spec: &networking.DestinationRule{ |
| Host: "fakehost", |
| }, |
| } |
| _, err := store.Create(cfg) |
| if err != nil { |
| t.Errorf("error occurred crearting ServiceEntry config: %v", err) |
| } |
| |
| // Now create some service entries and verify that it's added to the registry. |
| createConfigs([]*config.Config{httpDNS, tcpStatic}, store, t) |
| expectServiceInstances(t, sd, httpDNS, 80, []*model.ServiceInstance{ |
| makeInstance(httpDNS, "us.google.com", 7080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| makeInstance(httpDNS, "uk.google.com", 1080, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], nil, MTLS), |
| makeInstance(httpDNS, "de.google.com", 80, httpDNS.Spec.(*networking.ServiceEntry).Ports[0], map[string]string{"foo": "bar"}, MTLS), |
| }) |
| } |
| |
| // nolint: lll |
| func TestServicesDiff(t *testing.T) { |
| updatedHTTPDNS := &config.Config{ |
| Meta: config.Meta{ |
| GroupVersionKind: collections.IstioNetworkingV1Alpha3Serviceentries.Resource().GroupVersionKind(), |
| Name: "httpDNS", |
| Namespace: "httpDNS", |
| CreationTimestamp: GlobalTime, |
| Labels: map[string]string{label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel}, |
| }, |
| Spec: &networking.ServiceEntry{ |
| Hosts: []string{"*.google.com", "*.mail.com"}, |
| Ports: []*networking.Port{ |
| {Number: 80, Name: "http-port", Protocol: "http"}, |
| {Number: 8080, Name: "http-alt-port", Protocol: "http"}, |
| }, |
| Endpoints: []*networking.WorkloadEntry{ |
| { |
| Address: "us.google.com", |
| Ports: map[string]uint32{"http-port": 7080, "http-alt-port": 18080}, |
| Labels: map[string]string{label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel}, |
| }, |
| { |
| Address: "uk.google.com", |
| Ports: map[string]uint32{"http-port": 1080}, |
| Labels: map[string]string{label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel}, |
| }, |
| { |
| Address: "de.google.com", |
| Labels: map[string]string{"foo": "bar", label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel}, |
| }, |
| }, |
| Location: networking.ServiceEntry_MESH_EXTERNAL, |
| Resolution: networking.ServiceEntry_DNS, |
| }, |
| } |
| |
| updatedHTTPDNSPort := func() *config.Config { |
| c := updatedHTTPDNS.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| var ports []*networking.Port |
| ports = append(ports, se.Ports...) |
| ports = append(ports, &networking.Port{Number: 9090, Name: "http-new-port", Protocol: "http"}) |
| se.Ports = ports |
| return &c |
| }() |
| |
| updatedEndpoint := func() *config.Config { |
| c := updatedHTTPDNS.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| var endpoints []*networking.WorkloadEntry |
| endpoints = append(endpoints, se.Endpoints...) |
| endpoints = append(endpoints, &networking.WorkloadEntry{ |
| Address: "in.google.com", |
| Labels: map[string]string{"foo": "bar", label.SecurityTlsMode.Name: model.IstioMutualTLSModeLabel}, |
| }) |
| se.Endpoints = endpoints |
| return &c |
| }() |
| |
| stringsToHosts := func(hosts []string) []host.Name { |
| ret := make([]host.Name, len(hosts)) |
| for i, hostname := range hosts { |
| ret[i] = host.Name(hostname) |
| } |
| return ret |
| } |
| |
| cases := []struct { |
| name string |
| current *config.Config |
| new *config.Config |
| |
| added []host.Name |
| deleted []host.Name |
| updated []host.Name |
| unchanged []host.Name |
| }{ |
| { |
| name: "same config", |
| current: updatedHTTPDNS, |
| new: updatedHTTPDNS, |
| unchanged: stringsToHosts(updatedHTTPDNS.Spec.(*networking.ServiceEntry).Hosts), |
| }, |
| { |
| name: "same config with different name", |
| current: updatedHTTPDNS, |
| new: func() *config.Config { |
| c := updatedHTTPDNS.DeepCopy() |
| c.Name = "httpDNS1" |
| return &c |
| }(), |
| unchanged: stringsToHosts(updatedHTTPDNS.Spec.(*networking.ServiceEntry).Hosts), |
| }, |
| { |
| name: "different resolution", |
| current: updatedHTTPDNS, |
| new: func() *config.Config { |
| c := updatedHTTPDNS.DeepCopy() |
| c.Spec.(*networking.ServiceEntry).Resolution = networking.ServiceEntry_NONE |
| return &c |
| }(), |
| updated: stringsToHosts(updatedHTTPDNS.Spec.(*networking.ServiceEntry).Hosts), |
| }, |
| { |
| name: "config modified with added/deleted host", |
| current: updatedHTTPDNS, |
| new: func() *config.Config { |
| c := updatedHTTPDNS.DeepCopy() |
| se := c.Spec.(*networking.ServiceEntry) |
| se.Hosts = []string{"*.google.com", "host.com"} |
| return &c |
| }(), |
| added: []host.Name{"host.com"}, |
| deleted: []host.Name{"*.mail.com"}, |
| unchanged: []host.Name{"*.google.com"}, |
| }, |
| { |
| name: "config modified with additional port", |
| current: updatedHTTPDNS, |
| new: updatedHTTPDNSPort, |
| updated: stringsToHosts(updatedHTTPDNS.Spec.(*networking.ServiceEntry).Hosts), |
| }, |
| { |
| name: "same config with additional endpoint", |
| current: updatedHTTPDNS, |
| new: updatedEndpoint, |
| unchanged: stringsToHosts(updatedHTTPDNS.Spec.(*networking.ServiceEntry).Hosts), |
| }, |
| } |
| |
| servicesHostnames := func(services []*model.Service) []host.Name { |
| if len(services) == 0 { |
| return nil |
| } |
| ret := make([]host.Name, len(services)) |
| for i, svc := range services { |
| ret[i] = svc.Hostname |
| } |
| return ret |
| } |
| |
| for _, tt := range cases { |
| if tt.name != "same config with additional endpoint" { |
| continue |
| } |
| t.Run(tt.name, func(t *testing.T) { |
| as := convertServices(*tt.current) |
| bs := convertServices(*tt.new) |
| added, deleted, updated, unchanged := servicesDiff(as, bs) |
| for i, item := range []struct { |
| hostnames []host.Name |
| services []*model.Service |
| }{ |
| {tt.added, added}, |
| {tt.deleted, deleted}, |
| {tt.updated, updated}, |
| {tt.unchanged, unchanged}, |
| } { |
| if !reflect.DeepEqual(servicesHostnames(item.services), item.hostnames) { |
| t.Errorf("ServicesChanged %d got %v, want %v", i, servicesHostnames(item.services), item.hostnames) |
| } |
| } |
| }) |
| } |
| } |
| |
| func sortServices(services []*model.Service) { |
| sort.Slice(services, func(i, j int) bool { return services[i].Hostname < services[j].Hostname }) |
| for _, service := range services { |
| sortPorts(service.Ports) |
| } |
| } |
| |
| func sortServiceInstances(instances []*model.ServiceInstance) { |
| labelsToSlice := func(labels labels.Instance) []string { |
| out := make([]string, 0, len(labels)) |
| for k, v := range labels { |
| out = append(out, fmt.Sprintf("%s=%s", k, v)) |
| } |
| sort.Strings(out) |
| return out |
| } |
| |
| sort.Slice(instances, func(i, j int) bool { |
| if instances[i].Service.Hostname == instances[j].Service.Hostname { |
| if instances[i].Endpoint.EndpointPort == instances[j].Endpoint.EndpointPort { |
| if instances[i].Endpoint.Address == instances[j].Endpoint.Address { |
| if len(instances[i].Endpoint.Labels) == len(instances[j].Endpoint.Labels) { |
| iLabels := labelsToSlice(instances[i].Endpoint.Labels) |
| jLabels := labelsToSlice(instances[j].Endpoint.Labels) |
| for k := range iLabels { |
| if iLabels[k] < jLabels[k] { |
| return true |
| } |
| } |
| } |
| return len(instances[i].Endpoint.Labels) < len(instances[j].Endpoint.Labels) |
| } |
| return instances[i].Endpoint.Address < instances[j].Endpoint.Address |
| } |
| return instances[i].Endpoint.EndpointPort < instances[j].Endpoint.EndpointPort |
| } |
| return instances[i].Service.Hostname < instances[j].Service.Hostname |
| }) |
| } |
| |
| func sortPorts(ports []*model.Port) { |
| sort.Slice(ports, func(i, j int) bool { |
| if ports[i].Port == ports[j].Port { |
| if ports[i].Name == ports[j].Name { |
| return ports[i].Protocol < ports[j].Protocol |
| } |
| return ports[i].Name < ports[j].Name |
| } |
| return ports[i].Port < ports[j].Port |
| }) |
| } |
| |
| func Test_autoAllocateIP_conditions(t *testing.T) { |
| tests := []struct { |
| name string |
| inServices []*model.Service |
| wantServices []*model.Service |
| }{ |
| { |
| name: "no allocation for passthrough", |
| inServices: []*model.Service{ |
| { |
| Hostname: "foo.com", |
| Resolution: model.Passthrough, |
| DefaultAddress: "0.0.0.0", |
| }, |
| }, |
| wantServices: []*model.Service{ |
| { |
| Hostname: "foo.com", |
| Resolution: model.Passthrough, |
| DefaultAddress: "0.0.0.0", |
| }, |
| }, |
| }, |
| { |
| name: "no allocation if address exists", |
| inServices: []*model.Service{ |
| { |
| Hostname: "foo.com", |
| Resolution: model.ClientSideLB, |
| DefaultAddress: "1.1.1.1", |
| }, |
| }, |
| wantServices: []*model.Service{ |
| { |
| Hostname: "foo.com", |
| Resolution: model.ClientSideLB, |
| DefaultAddress: "1.1.1.1", |
| }, |
| }, |
| }, |
| { |
| name: "no allocation if hostname is wildcard", |
| inServices: []*model.Service{ |
| { |
| Hostname: "*.foo.com", |
| Resolution: model.ClientSideLB, |
| DefaultAddress: "1.1.1.1", |
| }, |
| }, |
| wantServices: []*model.Service{ |
| { |
| Hostname: "*.foo.com", |
| Resolution: model.ClientSideLB, |
| DefaultAddress: "1.1.1.1", |
| }, |
| }, |
| }, |
| { |
| name: "allocate IP for clientside lb", |
| inServices: []*model.Service{ |
| { |
| Hostname: "foo.com", |
| Resolution: model.ClientSideLB, |
| DefaultAddress: "0.0.0.0", |
| }, |
| }, |
| wantServices: []*model.Service{ |
| { |
| Hostname: "foo.com", |
| Resolution: model.ClientSideLB, |
| DefaultAddress: "0.0.0.0", |
| AutoAllocatedIPv4Address: "240.240.0.1", |
| AutoAllocatedIPv6Address: "2001:2::f0f0:1", |
| }, |
| }, |
| }, |
| { |
| name: "allocate IP for dns lb", |
| inServices: []*model.Service{ |
| { |
| Hostname: "foo.com", |
| Resolution: model.DNSLB, |
| DefaultAddress: "0.0.0.0", |
| }, |
| }, |
| wantServices: []*model.Service{ |
| { |
| Hostname: "foo.com", |
| Resolution: model.DNSLB, |
| DefaultAddress: "0.0.0.0", |
| AutoAllocatedIPv4Address: "240.240.0.1", |
| AutoAllocatedIPv6Address: "2001:2::f0f0:1", |
| }, |
| }, |
| }, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| got := autoAllocateIPs(tt.inServices) |
| if got[0].AutoAllocatedIPv4Address != tt.wantServices[0].AutoAllocatedIPv4Address { |
| t.Errorf("autoAllocateIPs() AutoAllocatedIPv4Address = %v, want %v", |
| got[0].AutoAllocatedIPv4Address, tt.wantServices[0].AutoAllocatedIPv4Address) |
| } |
| if got[0].AutoAllocatedIPv6Address != tt.wantServices[0].AutoAllocatedIPv6Address { |
| t.Errorf("autoAllocateIPs() AutoAllocatedIPv4Address = %v, want %v", |
| got[0].AutoAllocatedIPv6Address, tt.wantServices[0].AutoAllocatedIPv6Address) |
| } |
| }) |
| } |
| } |
| |
| func Test_autoAllocateIP_values(t *testing.T) { |
| inServices := make([]*model.Service, 512) |
| for i := 0; i < 512; i++ { |
| temp := model.Service{ |
| Hostname: "foo.com", |
| Resolution: model.ClientSideLB, |
| DefaultAddress: constants.UnspecifiedIP, |
| } |
| inServices[i] = &temp |
| } |
| gotServices := autoAllocateIPs(inServices) |
| |
| // out of the 512 IPs, we dont expect the following IPs |
| // 240.240.0.0 |
| // 240.240.0.255 |
| // 240.240.1.0 |
| // 240.240.1.255 |
| // 240.240.2.0 |
| // 240.240.2.255 |
| // The last IP should be 240.240.2.4 |
| doNotWant := map[string]bool{ |
| "240.240.0.0": true, |
| "240.240.0.255": true, |
| "240.240.1.0": true, |
| "240.240.1.255": true, |
| "240.240.2.0": true, |
| "240.240.2.255": true, |
| } |
| expectedLastIP := "240.240.2.4" |
| if gotServices[len(gotServices)-1].AutoAllocatedIPv4Address != expectedLastIP { |
| t.Errorf("expected last IP address to be %s, got %s", expectedLastIP, gotServices[len(gotServices)-1].AutoAllocatedIPv4Address) |
| } |
| |
| gotIPMap := make(map[string]bool) |
| for _, svc := range gotServices { |
| if svc.AutoAllocatedIPv4Address == "" || doNotWant[svc.AutoAllocatedIPv4Address] { |
| t.Errorf("unexpected value for auto allocated IP address %s", svc.AutoAllocatedIPv4Address) |
| } |
| if gotIPMap[svc.AutoAllocatedIPv4Address] { |
| t.Errorf("multiple allocations of same IP address to different services: %s", svc.AutoAllocatedIPv4Address) |
| } |
| gotIPMap[svc.AutoAllocatedIPv4Address] = true |
| } |
| } |
| |
| func TestWorkloadEntryOnlyMode(t *testing.T) { |
| store, registry, _, cleanup := initServiceDiscoveryWithOpts(true) |
| defer cleanup() |
| createConfigs([]*config.Config{httpStatic}, store, t) |
| svcs := registry.Services() |
| if len(svcs) > 0 { |
| t.Fatalf("expected 0 services, got %d", len(svcs)) |
| } |
| svc := registry.GetService("*.google.com") |
| if svc != nil { |
| t.Fatalf("expected nil, got %v", svc) |
| } |
| } |
| |
| func BenchmarkServiceEntryHandler(b *testing.B) { |
| _, sd := initServiceDiscoveryWithoutEvents(b) |
| stopCh := make(chan struct{}) |
| go sd.Run(stopCh) |
| defer close(stopCh) |
| for i := 0; i < b.N; i++ { |
| sd.serviceEntryHandler(config.Config{}, *httpDNS, model.EventAdd) |
| sd.serviceEntryHandler(config.Config{}, *httpDNSRR, model.EventAdd) |
| sd.serviceEntryHandler(config.Config{}, *tcpDNS, model.EventAdd) |
| sd.serviceEntryHandler(config.Config{}, *tcpStatic, model.EventAdd) |
| |
| sd.serviceEntryHandler(config.Config{}, *httpDNS, model.EventDelete) |
| sd.serviceEntryHandler(config.Config{}, *httpDNSRR, model.EventDelete) |
| sd.serviceEntryHandler(config.Config{}, *tcpDNS, model.EventDelete) |
| sd.serviceEntryHandler(config.Config{}, *tcpStatic, model.EventDelete) |
| } |
| } |
| |
| func BenchmarkWorkloadInstanceHandler(b *testing.B) { |
| store, sd := initServiceDiscoveryWithoutEvents(b) |
| stopCh := make(chan struct{}) |
| go sd.Run(stopCh) |
| defer close(stopCh) |
| // Add just the ServiceEntry with selector. We should see no instances |
| createConfigs([]*config.Config{selector, dnsSelector}, store, b) |
| |
| // Setup a couple of workload instances for test. These will be selected by the `selector` SE |
| fi1 := &model.WorkloadInstance{ |
| Name: selector.Name, |
| Namespace: selector.Namespace, |
| Endpoint: &model.IstioEndpoint{ |
| Address: "2.2.2.2", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: spiffe.MustGenSpiffeURI(selector.Name, "default"), |
| TLSMode: model.IstioMutualTLSModeLabel, |
| }, |
| } |
| |
| fi2 := &model.WorkloadInstance{ |
| Name: "some-other-name", |
| Namespace: selector.Namespace, |
| Endpoint: &model.IstioEndpoint{ |
| Address: "3.3.3.3", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: spiffe.MustGenSpiffeURI(selector.Name, "default"), |
| TLSMode: model.IstioMutualTLSModeLabel, |
| }, |
| } |
| |
| fi3 := &model.WorkloadInstance{ |
| Name: "another-name", |
| Namespace: dnsSelector.Namespace, |
| Endpoint: &model.IstioEndpoint{ |
| Address: "2.2.2.2", |
| Labels: map[string]string{"app": "dns-wle"}, |
| ServiceAccount: spiffe.MustGenSpiffeURI(dnsSelector.Name, "default"), |
| TLSMode: model.IstioMutualTLSModeLabel, |
| }, |
| } |
| for i := 0; i < b.N; i++ { |
| sd.WorkloadInstanceHandler(fi1, model.EventAdd) |
| sd.WorkloadInstanceHandler(fi2, model.EventAdd) |
| sd.WorkloadInstanceHandler(fi3, model.EventDelete) |
| |
| sd.WorkloadInstanceHandler(fi2, model.EventDelete) |
| sd.WorkloadInstanceHandler(fi1, model.EventDelete) |
| sd.WorkloadInstanceHandler(fi3, model.EventDelete) |
| } |
| } |
| |
| func BenchmarkWorkloadEntryHandler(b *testing.B) { |
| // Setup a couple workload entries for test. These will be selected by the `selector` SE |
| wle := createWorkloadEntry("wl", selector.Name, |
| &networking.WorkloadEntry{ |
| Address: "2.2.2.2", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: "default", |
| }) |
| wle2 := createWorkloadEntry("wl2", selector.Name, |
| &networking.WorkloadEntry{ |
| Address: "3.3.3.3", |
| Labels: map[string]string{"app": "wle"}, |
| ServiceAccount: "default", |
| }) |
| dnsWle := createWorkloadEntry("dnswl", dnsSelector.Namespace, |
| &networking.WorkloadEntry{ |
| Address: "4.4.4.4", |
| Labels: map[string]string{"app": "dns-wle"}, |
| ServiceAccount: "default", |
| }) |
| |
| store, sd := initServiceDiscoveryWithoutEvents(b) |
| stopCh := make(chan struct{}) |
| go sd.Run(stopCh) |
| defer close(stopCh) |
| // Add just the ServiceEntry with selector. We should see no instances |
| createConfigs([]*config.Config{selector}, store, b) |
| |
| for i := 0; i < b.N; i++ { |
| sd.workloadEntryHandler(config.Config{}, *wle, model.EventAdd) |
| sd.workloadEntryHandler(config.Config{}, *dnsWle, model.EventAdd) |
| sd.workloadEntryHandler(config.Config{}, *wle2, model.EventAdd) |
| |
| sd.workloadEntryHandler(config.Config{}, *wle, model.EventDelete) |
| sd.workloadEntryHandler(config.Config{}, *dnsWle, model.EventDelete) |
| sd.workloadEntryHandler(config.Config{}, *wle2, model.EventDelete) |
| } |
| } |