github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/registry/memory/memory.go (about)

     1  // Licensed under the Apache License, Version 2.0 (the "License");
     2  // you may not use this file except in compliance with the License.
     3  // You may obtain a copy of the License at
     4  //
     5  //     https://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS,
     9  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10  // See the License for the specific language governing permissions and
    11  // limitations under the License.
    12  //
    13  // Original source: github.com/micro/go-micro/v3/registry/memory/memory.go
    14  
    15  // Package memory provides an in-memory registry
    16  package memory
    17  
    18  import (
    19  	"context"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/google/uuid"
    24  	"github.com/tickoalcantara12/micro/v3/service/logger"
    25  	"github.com/tickoalcantara12/micro/v3/service/registry"
    26  )
    27  
    28  var (
    29  	sendEventTime = 10 * time.Millisecond
    30  	ttlPruneTime  = time.Second
    31  )
    32  
    33  type node struct {
    34  	*registry.Node
    35  	TTL      time.Duration
    36  	LastSeen time.Time
    37  }
    38  
    39  type record struct {
    40  	Name      string
    41  	Version   string
    42  	Metadata  map[string]string
    43  	Nodes     map[string]*node
    44  	Endpoints []*registry.Endpoint
    45  }
    46  
    47  type Registry struct {
    48  	options registry.Options
    49  
    50  	sync.RWMutex
    51  	// records is a KV map with domain name as the key and a services map as the value
    52  	records  map[string]services
    53  	watchers map[string]*Watcher
    54  }
    55  
    56  // services is a KV map with service name as the key and a map of records as the value
    57  type services map[string]map[string]*record
    58  
    59  // NewRegistry returns an initialized in-memory registry
    60  func NewRegistry(opts ...registry.Option) registry.Registry {
    61  	options := registry.Options{
    62  		Context: context.Background(),
    63  	}
    64  	for _, o := range opts {
    65  		o(&options)
    66  	}
    67  
    68  	// records can be passed for testing purposes
    69  	records := getServiceRecords(options.Context)
    70  	if records == nil {
    71  		records = make(services)
    72  	}
    73  
    74  	reg := &Registry{
    75  		options:  options,
    76  		records:  map[string]services{registry.DefaultDomain: records},
    77  		watchers: make(map[string]*Watcher),
    78  	}
    79  
    80  	go reg.ttlPrune()
    81  
    82  	return reg
    83  }
    84  
    85  func (m *Registry) ttlPrune() {
    86  	prune := time.NewTicker(ttlPruneTime)
    87  	defer prune.Stop()
    88  
    89  	for {
    90  		select {
    91  		case <-prune.C:
    92  			m.Lock()
    93  			for domain, services := range m.records {
    94  				for service, versions := range services {
    95  					for version, record := range versions {
    96  						for id, n := range record.Nodes {
    97  							if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
    98  								if logger.V(logger.DebugLevel, logger.DefaultLogger) {
    99  									logger.Debugf("Registry TTL expired for node %s of service %s", n.Id, service)
   100  								}
   101  								delete(m.records[domain][service][version].Nodes, id)
   102  							}
   103  						}
   104  
   105  						// if there are no nodes then delete the version
   106  						if len(m.records[domain][service][version].Nodes) == 0 {
   107  							delete(m.records[domain][service], version)
   108  						}
   109  					}
   110  
   111  					// if there are no versions left delete the service
   112  					if len(m.records[domain][service]) == 0 {
   113  						delete(m.records[domain], service)
   114  					}
   115  				}
   116  			}
   117  			m.Unlock()
   118  		}
   119  	}
   120  }
   121  
   122  func (m *Registry) sendEvent(r *registry.Result) {
   123  	m.RLock()
   124  	watchers := make([]*Watcher, 0, len(m.watchers))
   125  	for _, w := range m.watchers {
   126  		watchers = append(watchers, w)
   127  	}
   128  	m.RUnlock()
   129  
   130  	for _, w := range watchers {
   131  		select {
   132  		case <-w.exit:
   133  			m.Lock()
   134  			delete(m.watchers, w.id)
   135  			m.Unlock()
   136  		default:
   137  			select {
   138  			case w.res <- r:
   139  			case <-time.After(sendEventTime):
   140  			}
   141  		}
   142  	}
   143  }
   144  
   145  func (m *Registry) Init(opts ...registry.Option) error {
   146  	for _, o := range opts {
   147  		o(&m.options)
   148  	}
   149  
   150  	// add services
   151  	m.Lock()
   152  	defer m.Unlock()
   153  
   154  	// get the existing services from the records
   155  	srvs, ok := m.records[registry.DefaultDomain]
   156  	if !ok {
   157  		srvs = make(services)
   158  	}
   159  
   160  	// loop through the services and if it doesn't yet exist, add it to the slice. This is used for
   161  	// testing purposes.
   162  	for name, record := range getServiceRecords(m.options.Context) {
   163  		if _, ok := srvs[name]; !ok {
   164  			srvs[name] = record
   165  			continue
   166  		}
   167  
   168  		for version, r := range record {
   169  			if _, ok := srvs[name][version]; !ok {
   170  				srvs[name][version] = r
   171  				continue
   172  			}
   173  		}
   174  	}
   175  
   176  	// set the services in the registry
   177  	m.records[registry.DefaultDomain] = srvs
   178  	return nil
   179  }
   180  
   181  func (m *Registry) Options() registry.Options {
   182  	return m.options
   183  }
   184  
   185  func (m *Registry) Register(s *registry.Service, opts ...registry.RegisterOption) error {
   186  	m.Lock()
   187  	defer m.Unlock()
   188  
   189  	// parse the options, fallback to the default domain
   190  	var options registry.RegisterOptions
   191  	for _, o := range opts {
   192  		o(&options)
   193  	}
   194  	if len(options.Domain) == 0 {
   195  		options.Domain = registry.DefaultDomain
   196  	}
   197  
   198  	// get the services for this domain from the registry
   199  	srvs, ok := m.records[options.Domain]
   200  	if !ok {
   201  		srvs = make(services)
   202  	}
   203  
   204  	// domain is set in metadata so it can be passed to watchers
   205  	if s.Metadata == nil {
   206  		s.Metadata = map[string]string{"domain": options.Domain}
   207  	} else {
   208  		s.Metadata["domain"] = options.Domain
   209  	}
   210  
   211  	// ensure the service name exists
   212  	r := serviceToRecord(s, options.TTL)
   213  	if _, ok := srvs[s.Name]; !ok {
   214  		srvs[s.Name] = make(map[string]*record)
   215  	}
   216  
   217  	if _, ok := srvs[s.Name][s.Version]; !ok {
   218  		srvs[s.Name][s.Version] = r
   219  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   220  			logger.Debugf("Registry added new service: %s, version: %s", s.Name, s.Version)
   221  		}
   222  		m.records[options.Domain] = srvs
   223  		go m.sendEvent(&registry.Result{Action: "create", Service: s})
   224  	}
   225  
   226  	var addedNodes bool
   227  
   228  	for _, n := range s.Nodes {
   229  		// check if already exists
   230  		if _, ok := srvs[s.Name][s.Version].Nodes[n.Id]; ok {
   231  			continue
   232  		}
   233  
   234  		metadata := make(map[string]string)
   235  
   236  		// make copy of metadata
   237  		for k, v := range n.Metadata {
   238  			metadata[k] = v
   239  		}
   240  
   241  		// set the domain
   242  		metadata["domain"] = options.Domain
   243  
   244  		// add the node
   245  		srvs[s.Name][s.Version].Nodes[n.Id] = &node{
   246  			Node: &registry.Node{
   247  				Id:       n.Id,
   248  				Address:  n.Address,
   249  				Metadata: metadata,
   250  			},
   251  			TTL:      options.TTL,
   252  			LastSeen: time.Now(),
   253  		}
   254  
   255  		addedNodes = true
   256  	}
   257  
   258  	if addedNodes {
   259  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   260  			logger.Debugf("Registry added new node to service: %s, version: %s", s.Name, s.Version)
   261  		}
   262  		go m.sendEvent(&registry.Result{Action: "update", Service: s})
   263  	} else {
   264  		// refresh TTL and timestamp
   265  		for _, n := range s.Nodes {
   266  			if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   267  				logger.Debugf("Updated registration for service: %s, version: %s", s.Name, s.Version)
   268  			}
   269  			srvs[s.Name][s.Version].Nodes[n.Id].TTL = options.TTL
   270  			srvs[s.Name][s.Version].Nodes[n.Id].LastSeen = time.Now()
   271  		}
   272  	}
   273  
   274  	m.records[options.Domain] = srvs
   275  	return nil
   276  }
   277  
   278  func (m *Registry) Deregister(s *registry.Service, opts ...registry.DeregisterOption) error {
   279  	m.Lock()
   280  	defer m.Unlock()
   281  
   282  	// parse the options, fallback to the default domain
   283  	var options registry.DeregisterOptions
   284  	for _, o := range opts {
   285  		o(&options)
   286  	}
   287  	if len(options.Domain) == 0 {
   288  		options.Domain = registry.DefaultDomain
   289  	}
   290  
   291  	// domain is set in metadata so it can be passed to watchers
   292  	if s.Metadata == nil {
   293  		s.Metadata = map[string]string{"domain": options.Domain}
   294  	} else {
   295  		s.Metadata["domain"] = options.Domain
   296  	}
   297  
   298  	// if the domain doesn't exist, there is nothing to deregister
   299  	services, ok := m.records[options.Domain]
   300  	if !ok {
   301  		return nil
   302  	}
   303  
   304  	// if no services with this name and version exist, there is nothing to deregister
   305  	versions, ok := services[s.Name]
   306  	if !ok {
   307  		return nil
   308  	}
   309  
   310  	version, ok := versions[s.Version]
   311  	if !ok {
   312  		return nil
   313  	}
   314  
   315  	// deregister all of the service nodes from this version
   316  	for _, n := range s.Nodes {
   317  		if _, ok := version.Nodes[n.Id]; ok {
   318  			if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   319  				logger.Debugf("Registry removed node from service: %s, version: %s", s.Name, s.Version)
   320  			}
   321  			delete(version.Nodes, n.Id)
   322  		}
   323  	}
   324  
   325  	// if the nodes not empty, we replace the version in the store and exist, the rest of the logic
   326  	// is cleanup
   327  	if len(version.Nodes) > 0 {
   328  		m.records[options.Domain][s.Name][s.Version] = version
   329  		go m.sendEvent(&registry.Result{Action: "update", Service: s})
   330  		return nil
   331  	}
   332  
   333  	// if this version was the only version of the service, we can remove the whole service from the
   334  	// registry and exit
   335  	if len(versions) == 1 {
   336  		delete(m.records[options.Domain], s.Name)
   337  		go m.sendEvent(&registry.Result{Action: "delete", Service: s})
   338  
   339  		if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   340  			logger.Debugf("Registry removed service: %s", s.Name)
   341  		}
   342  		return nil
   343  	}
   344  
   345  	// there are other versions of the service running, so only remove this version of it
   346  	delete(m.records[options.Domain][s.Name], s.Version)
   347  	go m.sendEvent(&registry.Result{Action: "delete", Service: s})
   348  	if logger.V(logger.DebugLevel, logger.DefaultLogger) {
   349  		logger.Debugf("Registry removed service: %s, version: %s", s.Name, s.Version)
   350  	}
   351  
   352  	return nil
   353  }
   354  
   355  func (m *Registry) GetService(name string, opts ...registry.GetOption) ([]*registry.Service, error) {
   356  	// parse the options, fallback to the default domain
   357  	var options registry.GetOptions
   358  	for _, o := range opts {
   359  		o(&options)
   360  	}
   361  	if len(options.Domain) == 0 {
   362  		options.Domain = registry.DefaultDomain
   363  	}
   364  
   365  	// if it's a wildcard domain, return from all domains
   366  	if options.Domain == registry.WildcardDomain {
   367  		m.RLock()
   368  		recs := m.records
   369  		m.RUnlock()
   370  
   371  		var services []*registry.Service
   372  
   373  		for domain := range recs {
   374  			srvs, err := m.GetService(name, append(opts, registry.GetDomain(domain))...)
   375  			if err == registry.ErrNotFound {
   376  				continue
   377  			} else if err != nil {
   378  				return nil, err
   379  			}
   380  			services = append(services, srvs...)
   381  		}
   382  
   383  		if len(services) == 0 {
   384  			return nil, registry.ErrNotFound
   385  		}
   386  		return services, nil
   387  	}
   388  
   389  	m.RLock()
   390  	defer m.RUnlock()
   391  
   392  	// check the domain exists
   393  	services, ok := m.records[options.Domain]
   394  	if !ok {
   395  		return nil, registry.ErrNotFound
   396  	}
   397  
   398  	// check the service exists
   399  	versions, ok := services[name]
   400  	if !ok || len(versions) == 0 {
   401  		return nil, registry.ErrNotFound
   402  	}
   403  
   404  	// serialize the response
   405  	result := make([]*registry.Service, len(versions))
   406  
   407  	var i int
   408  
   409  	for _, r := range versions {
   410  		result[i] = recordToService(r, options.Domain)
   411  		i++
   412  	}
   413  
   414  	return result, nil
   415  }
   416  
   417  func (m *Registry) ListServices(opts ...registry.ListOption) ([]*registry.Service, error) {
   418  	// parse the options, fallback to the default domain
   419  	var options registry.ListOptions
   420  	for _, o := range opts {
   421  		o(&options)
   422  	}
   423  	if len(options.Domain) == 0 {
   424  		options.Domain = registry.DefaultDomain
   425  	}
   426  
   427  	// if it's a wildcard domain, list from all domains
   428  	if options.Domain == registry.WildcardDomain {
   429  		m.RLock()
   430  		recs := m.records
   431  		m.RUnlock()
   432  
   433  		var services []*registry.Service
   434  
   435  		for domain := range recs {
   436  			srvs, err := m.ListServices(append(opts, registry.ListDomain(domain))...)
   437  			if err != nil {
   438  				return nil, err
   439  			}
   440  			services = append(services, srvs...)
   441  		}
   442  
   443  		return services, nil
   444  	}
   445  
   446  	m.RLock()
   447  	defer m.RUnlock()
   448  
   449  	// ensure the domain exists
   450  	services, ok := m.records[options.Domain]
   451  	if !ok {
   452  		return make([]*registry.Service, 0), nil
   453  	}
   454  
   455  	// serialize the result, each version counts as an individual service
   456  	var result []*registry.Service
   457  
   458  	for _, service := range services {
   459  		for _, version := range service {
   460  			result = append(result, recordToService(version, options.Domain))
   461  		}
   462  	}
   463  
   464  	return result, nil
   465  }
   466  
   467  func (m *Registry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
   468  	// parse the options, fallback to the default domain
   469  	var wo registry.WatchOptions
   470  	for _, o := range opts {
   471  		o(&wo)
   472  	}
   473  	if len(wo.Domain) == 0 {
   474  		wo.Domain = registry.DefaultDomain
   475  	}
   476  
   477  	// construct the watcher
   478  	w := &Watcher{
   479  		exit: make(chan bool),
   480  		res:  make(chan *registry.Result),
   481  		id:   uuid.New().String(),
   482  		wo:   wo,
   483  	}
   484  
   485  	m.Lock()
   486  	m.watchers[w.id] = w
   487  	m.Unlock()
   488  
   489  	return w, nil
   490  }
   491  
   492  func (m *Registry) String() string {
   493  	return "memory"
   494  }