istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/memory/discovery.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 memory
    16  
    17  import (
    18  	"fmt"
    19  	"net/netip"
    20  	"sync"
    21  
    22  	"istio.io/istio/pilot/pkg/model"
    23  	"istio.io/istio/pilot/pkg/serviceregistry/provider"
    24  	"istio.io/istio/pkg/cluster"
    25  	"istio.io/istio/pkg/config/host"
    26  	"istio.io/istio/pkg/config/labels"
    27  	"istio.io/istio/pkg/config/protocol"
    28  	"istio.io/istio/pkg/log"
    29  	"istio.io/istio/pkg/maps"
    30  	"istio.io/istio/pkg/slices"
    31  	"istio.io/istio/pkg/util/sets"
    32  	"istio.io/istio/pkg/workloadapi"
    33  )
    34  
    35  // ServiceDiscovery is a mock discovery interface
    36  type ServiceDiscovery struct {
    37  	services map[host.Name]*model.Service
    38  
    39  	handlers model.ControllerHandlers
    40  
    41  	networkGateways []model.NetworkGateway
    42  	model.NetworkGatewaysHandler
    43  
    44  	// EndpointShards table. Key is the fqdn of the service, ':', port
    45  	instancesByPortNum  map[string][]*model.ServiceInstance
    46  	instancesByPortName map[string][]*model.ServiceInstance
    47  
    48  	// Used by GetProxyServiceInstance, used to configure inbound (list of services per IP)
    49  	// We generally expect a single instance - conflicting services need to be reported.
    50  	ip2instance                map[string][]*model.ServiceInstance
    51  	WantGetProxyServiceTargets []model.ServiceTarget
    52  	InstancesError             error
    53  	Controller                 model.Controller
    54  	ClusterID                  cluster.ID
    55  
    56  	// Used by GetProxyWorkloadLabels
    57  	ip2workloadLabels map[string]labels.Instance
    58  
    59  	addresses map[string]model.AddressInfo
    60  
    61  	// XDSUpdater will push EDS changes to the ADS model.
    62  	XdsUpdater model.XDSUpdater
    63  
    64  	// Single mutex for now - it's for debug only.
    65  	mutex sync.Mutex
    66  }
    67  
    68  var (
    69  	_ model.Controller       = &ServiceDiscovery{}
    70  	_ model.ServiceDiscovery = &ServiceDiscovery{}
    71  )
    72  
    73  // NewServiceDiscovery builds an in-memory ServiceDiscovery
    74  func NewServiceDiscovery(services ...*model.Service) *ServiceDiscovery {
    75  	svcs := map[host.Name]*model.Service{}
    76  	for _, svc := range services {
    77  		svcs[svc.Hostname] = svc
    78  	}
    79  	return &ServiceDiscovery{
    80  		services:            svcs,
    81  		instancesByPortNum:  map[string][]*model.ServiceInstance{},
    82  		instancesByPortName: map[string][]*model.ServiceInstance{},
    83  		ip2instance:         map[string][]*model.ServiceInstance{},
    84  		ip2workloadLabels:   map[string]labels.Instance{},
    85  		addresses:           map[string]model.AddressInfo{},
    86  	}
    87  }
    88  
    89  func (sd *ServiceDiscovery) shardKey() model.ShardKey {
    90  	return model.ShardKey{Cluster: sd.ClusterID, Provider: provider.Mock}
    91  }
    92  
    93  func (sd *ServiceDiscovery) AddWorkload(ip string, labels labels.Instance) {
    94  	sd.ip2workloadLabels[ip] = labels
    95  }
    96  
    97  // AddHTTPService is a helper to add a service of type http, named 'http-main', with the
    98  // specified vip and port.
    99  func (sd *ServiceDiscovery) AddHTTPService(name, vip string, port int) {
   100  	sd.AddService(&model.Service{
   101  		Hostname:       host.Name(name),
   102  		DefaultAddress: vip,
   103  		Ports: model.PortList{
   104  			{
   105  				Name:     "http-main",
   106  				Port:     port,
   107  				Protocol: protocol.HTTP,
   108  			},
   109  		},
   110  	})
   111  }
   112  
   113  // AddService adds an in-memory service and notifies
   114  func (sd *ServiceDiscovery) AddService(svc *model.Service) {
   115  	sd.mutex.Lock()
   116  	svc.Attributes.ServiceRegistry = provider.Mock
   117  	var old *model.Service
   118  	event := model.EventAdd
   119  	if o, f := sd.services[svc.Hostname]; f {
   120  		old = o
   121  		event = model.EventUpdate
   122  	}
   123  	sd.services[svc.Hostname] = svc
   124  
   125  	if sd.XdsUpdater != nil {
   126  		sd.XdsUpdater.SvcUpdate(sd.shardKey(), string(svc.Hostname), svc.Attributes.Namespace, model.EventAdd)
   127  	}
   128  	sd.handlers.NotifyServiceHandlers(old, svc, event)
   129  	sd.mutex.Unlock()
   130  }
   131  
   132  // RemoveService removes an in-memory service.
   133  func (sd *ServiceDiscovery) RemoveService(name host.Name) {
   134  	sd.mutex.Lock()
   135  	svc := sd.services[name]
   136  	delete(sd.services, name)
   137  
   138  	// remove old entries
   139  	for k, v := range sd.ip2instance {
   140  		sd.ip2instance[k] = slices.FilterInPlace(v, func(instance *model.ServiceInstance) bool {
   141  			return instance.Service == nil || instance.Service.Hostname != name
   142  		})
   143  	}
   144  
   145  	if sd.XdsUpdater != nil {
   146  		sd.XdsUpdater.SvcUpdate(sd.shardKey(), string(svc.Hostname), svc.Attributes.Namespace, model.EventDelete)
   147  	}
   148  	sd.handlers.NotifyServiceHandlers(nil, svc, model.EventDelete)
   149  	sd.mutex.Unlock()
   150  }
   151  
   152  // AddInstance adds an in-memory instance and notifies the XDS updater
   153  func (sd *ServiceDiscovery) AddInstance(instance *model.ServiceInstance) {
   154  	sd.mutex.Lock()
   155  	defer sd.mutex.Unlock()
   156  	service := instance.Service.Hostname
   157  	svc := sd.services[service]
   158  	if svc == nil {
   159  		return
   160  	}
   161  	if instance.Endpoint.ServicePortName == "" {
   162  		instance.Endpoint.ServicePortName = instance.ServicePort.Name
   163  	}
   164  	instance.Service = svc
   165  	sd.ip2instance[instance.Endpoint.Address] = append(sd.ip2instance[instance.Endpoint.Address], instance)
   166  
   167  	key := fmt.Sprintf("%s:%d", service, instance.ServicePort.Port)
   168  	instanceList := sd.instancesByPortNum[key]
   169  	sd.instancesByPortNum[key] = append(instanceList, instance)
   170  
   171  	key = fmt.Sprintf("%s:%s", service, instance.ServicePort.Name)
   172  	instanceList = sd.instancesByPortName[key]
   173  	sd.instancesByPortName[key] = append(instanceList, instance)
   174  	eps := make([]*model.IstioEndpoint, 0, len(sd.instancesByPortName[key]))
   175  	for _, port := range svc.Ports {
   176  		key := fmt.Sprintf("%s:%s", service, port.Name)
   177  		for _, i := range sd.instancesByPortName[key] {
   178  			eps = append(eps, i.Endpoint)
   179  		}
   180  	}
   181  	if sd.XdsUpdater != nil {
   182  		sd.XdsUpdater.EDSUpdate(sd.shardKey(), string(service), svc.Attributes.Namespace, eps)
   183  	}
   184  }
   185  
   186  // AddEndpoint adds an endpoint to a service.
   187  func (sd *ServiceDiscovery) AddEndpoint(service host.Name, servicePortName string, servicePort int, address string, port int) *model.ServiceInstance {
   188  	instance := &model.ServiceInstance{
   189  		Service: &model.Service{Hostname: service},
   190  		Endpoint: &model.IstioEndpoint{
   191  			Address:         address,
   192  			ServicePortName: servicePortName,
   193  			EndpointPort:    uint32(port),
   194  		},
   195  		ServicePort: &model.Port{
   196  			Name:     servicePortName,
   197  			Port:     servicePort,
   198  			Protocol: protocol.HTTP,
   199  		},
   200  	}
   201  	sd.AddInstance(instance)
   202  	return instance
   203  }
   204  
   205  // SetEndpoints update the list of endpoints for a service, similar with K8S controller.
   206  func (sd *ServiceDiscovery) SetEndpoints(service string, namespace string, endpoints []*model.IstioEndpoint) {
   207  	sh := host.Name(service)
   208  
   209  	sd.mutex.Lock()
   210  	svc := sd.services[sh]
   211  	if svc == nil {
   212  		sd.mutex.Unlock()
   213  		return
   214  	}
   215  	if svc.Attributes.Namespace != namespace {
   216  		log.Errorf("Service namespace %q != namespace %q", svc.Attributes.Namespace, namespace)
   217  	}
   218  
   219  	// remove old entries
   220  	for k, v := range sd.ip2instance {
   221  		if len(v) > 0 && v[0].Service.Hostname == sh {
   222  			delete(sd.ip2instance, k)
   223  		}
   224  	}
   225  	for k, v := range sd.instancesByPortNum {
   226  		if len(v) > 0 && v[0].Service.Hostname == sh {
   227  			delete(sd.instancesByPortNum, k)
   228  		}
   229  	}
   230  	for k, v := range sd.instancesByPortName {
   231  		if len(v) > 0 && v[0].Service.Hostname == sh {
   232  			delete(sd.instancesByPortName, k)
   233  		}
   234  	}
   235  
   236  	for _, e := range endpoints {
   237  		// servicePortName string, servicePort int, address string, port int
   238  		p, _ := svc.Ports.Get(e.ServicePortName)
   239  
   240  		instance := &model.ServiceInstance{
   241  			Service: svc,
   242  			ServicePort: &model.Port{
   243  				Name:     e.ServicePortName,
   244  				Port:     p.Port,
   245  				Protocol: p.Protocol,
   246  			},
   247  			Endpoint: e,
   248  		}
   249  		sd.ip2instance[instance.Endpoint.Address] = []*model.ServiceInstance{instance}
   250  
   251  		key := fmt.Sprintf("%s:%d", service, instance.ServicePort.Port)
   252  
   253  		instanceList := sd.instancesByPortNum[key]
   254  		sd.instancesByPortNum[key] = append(instanceList, instance)
   255  
   256  		key = fmt.Sprintf("%s:%s", service, instance.ServicePort.Name)
   257  		instanceList = sd.instancesByPortName[key]
   258  		sd.instancesByPortName[key] = append(instanceList, instance)
   259  
   260  	}
   261  	if sd.XdsUpdater != nil {
   262  		sd.XdsUpdater.EDSUpdate(sd.shardKey(), service, namespace, endpoints)
   263  	}
   264  	sd.mutex.Unlock()
   265  }
   266  
   267  // Services implements discovery interface
   268  // Each call to Services() should return a list of new *model.Service
   269  func (sd *ServiceDiscovery) Services() []*model.Service {
   270  	sd.mutex.Lock()
   271  	defer sd.mutex.Unlock()
   272  	out := make([]*model.Service, 0, len(sd.services))
   273  	for _, service := range sd.services {
   274  		out = append(out, service)
   275  	}
   276  	return out
   277  }
   278  
   279  // GetService implements discovery interface
   280  // Each call to GetService() should return a new *model.Service
   281  func (sd *ServiceDiscovery) GetService(hostname host.Name) *model.Service {
   282  	sd.mutex.Lock()
   283  	defer sd.mutex.Unlock()
   284  	return sd.services[hostname]
   285  }
   286  
   287  // GetProxyServiceTargets returns service instances associated with a node, resulting in
   288  // 'in' services.
   289  func (sd *ServiceDiscovery) GetProxyServiceTargets(node *model.Proxy) []model.ServiceTarget {
   290  	sd.mutex.Lock()
   291  	defer sd.mutex.Unlock()
   292  	if sd.WantGetProxyServiceTargets != nil {
   293  		return sd.WantGetProxyServiceTargets
   294  	}
   295  	out := make([]model.ServiceTarget, 0)
   296  	for _, ip := range node.IPAddresses {
   297  		si, found := sd.ip2instance[ip]
   298  		if found {
   299  			out = append(out, slices.Map(si, model.ServiceInstanceToTarget)...)
   300  		}
   301  	}
   302  	return out
   303  }
   304  
   305  func (sd *ServiceDiscovery) GetProxyWorkloadLabels(proxy *model.Proxy) labels.Instance {
   306  	sd.mutex.Lock()
   307  	defer sd.mutex.Unlock()
   308  
   309  	for _, ip := range proxy.IPAddresses {
   310  		if l, found := sd.ip2workloadLabels[ip]; found {
   311  			return l
   312  		}
   313  	}
   314  	return nil
   315  }
   316  
   317  func (sd *ServiceDiscovery) AddGateways(gws ...model.NetworkGateway) {
   318  	sd.networkGateways = append(sd.networkGateways, gws...)
   319  	sd.NotifyGatewayHandlers()
   320  }
   321  
   322  func (sd *ServiceDiscovery) NetworkGateways() []model.NetworkGateway {
   323  	return sd.networkGateways
   324  }
   325  
   326  func (sd *ServiceDiscovery) MCSServices() []model.MCSServiceInfo {
   327  	return nil
   328  }
   329  
   330  // Memory does not support workload handlers; everything is done in terms of instances
   331  func (sd *ServiceDiscovery) AppendWorkloadHandler(func(*model.WorkloadInstance, model.Event)) {}
   332  
   333  // AppendServiceHandler appends a service handler to the controller
   334  func (sd *ServiceDiscovery) AppendServiceHandler(f model.ServiceHandler) {
   335  	sd.handlers.AppendServiceHandler(f)
   336  }
   337  
   338  // Run will run the controller
   339  func (sd *ServiceDiscovery) Run(<-chan struct{}) {}
   340  
   341  // HasSynced always returns true
   342  func (sd *ServiceDiscovery) HasSynced() bool { return true }
   343  
   344  func (sd *ServiceDiscovery) AddressInformation(requests sets.String) ([]model.AddressInfo, sets.String) {
   345  	sd.mutex.Lock()
   346  	defer sd.mutex.Unlock()
   347  	if len(requests) == 0 {
   348  		return maps.Values(sd.addresses), nil
   349  	}
   350  
   351  	var infos []model.AddressInfo
   352  	removed := sets.String{}
   353  	for req := range requests {
   354  		if _, found := sd.addresses[req]; !found {
   355  			removed.Insert(req)
   356  		} else {
   357  			infos = append(infos, sd.addresses[req])
   358  		}
   359  	}
   360  	return infos, removed
   361  }
   362  
   363  func (sd *ServiceDiscovery) AdditionalPodSubscriptions(
   364  	*model.Proxy,
   365  	sets.String,
   366  	sets.String,
   367  ) sets.String {
   368  	return nil
   369  }
   370  
   371  func (sd *ServiceDiscovery) Policies(sets.Set[model.ConfigKey]) []model.WorkloadAuthorization {
   372  	return nil
   373  }
   374  
   375  func (sd *ServiceDiscovery) ServicesForWaypoint(model.WaypointKey) []model.ServiceInfo {
   376  	return nil
   377  }
   378  
   379  func (sd *ServiceDiscovery) Waypoint(string, string) []netip.Addr {
   380  	return nil
   381  }
   382  
   383  func (sd *ServiceDiscovery) WorkloadsForWaypoint(model.WaypointKey) []model.WorkloadInfo {
   384  	return nil
   385  }
   386  
   387  func (sd *ServiceDiscovery) AddWorkloadInfo(infos ...*model.WorkloadInfo) {
   388  	sd.mutex.Lock()
   389  	defer sd.mutex.Unlock()
   390  	for _, info := range infos {
   391  		sd.addresses[info.ResourceName()] = workloadToAddressInfo(info.Workload)
   392  	}
   393  }
   394  
   395  func (sd *ServiceDiscovery) RemoveWorkloadInfo(info *model.WorkloadInfo) {
   396  	sd.mutex.Lock()
   397  	defer sd.mutex.Unlock()
   398  	delete(sd.addresses, info.ResourceName())
   399  }
   400  
   401  func (sd *ServiceDiscovery) AddServiceInfo(infos ...*model.ServiceInfo) {
   402  	sd.mutex.Lock()
   403  	defer sd.mutex.Unlock()
   404  	for _, info := range infos {
   405  		sd.addresses[info.ResourceName()] = serviceToAddressInfo(info.Service)
   406  	}
   407  }
   408  
   409  func (sd *ServiceDiscovery) RemoveServiceInfo(info *model.ServiceInfo) {
   410  	sd.mutex.Lock()
   411  	defer sd.mutex.Unlock()
   412  	delete(sd.addresses, info.ResourceName())
   413  }
   414  
   415  func workloadToAddressInfo(w *workloadapi.Workload) model.AddressInfo {
   416  	return model.AddressInfo{
   417  		Address: &workloadapi.Address{
   418  			Type: &workloadapi.Address_Workload{
   419  				Workload: w,
   420  			},
   421  		},
   422  	}
   423  }
   424  
   425  func serviceToAddressInfo(s *workloadapi.Service) model.AddressInfo {
   426  	return model.AddressInfo{
   427  		Address: &workloadapi.Address{
   428  			Type: &workloadapi.Address_Service{
   429  				Service: s,
   430  			},
   431  		},
   432  	}
   433  }