github.com/moleculer-go/moleculer@v0.3.3/registry/registry.go (about)

     1  package registry
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/moleculer-go/moleculer/middleware"
    12  
    13  	"github.com/moleculer-go/moleculer/payload"
    14  
    15  	"github.com/moleculer-go/moleculer"
    16  	"github.com/moleculer-go/moleculer/service"
    17  	"github.com/moleculer-go/moleculer/strategy"
    18  
    19  	"github.com/moleculer-go/moleculer/transit"
    20  	"github.com/moleculer-go/moleculer/transit/pubsub"
    21  	log "github.com/sirupsen/logrus"
    22  )
    23  
    24  type messageHandlerFunc func(message moleculer.Payload)
    25  
    26  type ServiceRegistry struct {
    27  	logger                *log.Entry
    28  	transit               transit.Transit
    29  	localNode             moleculer.Node
    30  	nodes                 *NodeCatalog
    31  	services              *ServiceCatalog
    32  	actions               *ActionCatalog
    33  	events                *EventCatalog
    34  	broker                *moleculer.BrokerDelegates
    35  	strategy              strategy.Strategy
    36  	stopping              bool
    37  	heartbeatFrequency    time.Duration
    38  	heartbeatTimeout      time.Duration
    39  	offlineCheckFrequency time.Duration
    40  	offlineTimeout        time.Duration
    41  	nodeReceivedMutex     *sync.Mutex
    42  	namespace             string
    43  }
    44  
    45  // createTransit create a transit instance based on the config.
    46  func createTransit(broker *moleculer.BrokerDelegates) transit.Transit {
    47  	transit := pubsub.Create(broker)
    48  	return transit
    49  }
    50  
    51  // createStrategy create a strategy instance based on the config.
    52  func createStrategy(broker *moleculer.BrokerDelegates) strategy.Strategy {
    53  	//TODO: when new strategies are addes.. adde config check here to load the right one.
    54  	if broker.Config.StrategyFactory != nil {
    55  		return broker.Config.StrategyFactory().(strategy.Strategy)
    56  	}
    57  
    58  	return strategy.RandomStrategy{}
    59  }
    60  
    61  func CreateRegistry(nodeID string, broker *moleculer.BrokerDelegates) *ServiceRegistry {
    62  	config := broker.Config
    63  	transit := createTransit(broker)
    64  	strategy := createStrategy(broker)
    65  	logger := broker.Logger("registry", nodeID)
    66  	localNode := CreateNode(nodeID, true, logger.WithField("Node", nodeID))
    67  	localNode.Unavailable()
    68  	registry := &ServiceRegistry{
    69  		broker:                broker,
    70  		transit:               transit,
    71  		strategy:              strategy,
    72  		logger:                logger,
    73  		localNode:             localNode,
    74  		actions:               CreateActionCatalog(logger.WithField("catalog", "Actions")),
    75  		events:                CreateEventCatalog(logger.WithField("catalog", "Events")),
    76  		services:              CreateServiceCatalog(logger.WithField("catalog", "Services")),
    77  		nodes:                 CreateNodesCatalog(logger.WithField("catalog", "Nodes")),
    78  		heartbeatFrequency:    config.HeartbeatFrequency,
    79  		heartbeatTimeout:      config.HeartbeatTimeout,
    80  		offlineCheckFrequency: config.OfflineCheckFrequency,
    81  		offlineTimeout:        config.OfflineTimeout,
    82  		stopping:              false,
    83  		nodeReceivedMutex:     &sync.Mutex{},
    84  		namespace:             config.Namespace,
    85  	}
    86  
    87  	registry.logger.Debug("Service Registry created for broker: ", nodeID)
    88  
    89  	broker.Bus().On("$broker.started", func(args ...interface{}) {
    90  		registry.logger.Debug("Registry -> $broker.started event")
    91  		registry.localNode.Available()
    92  	})
    93  
    94  	registry.setupMessageHandlers()
    95  
    96  	return registry
    97  }
    98  
    99  func (registry *ServiceRegistry) KnowService(name string) bool {
   100  	return registry.services.FindByName(name)
   101  }
   102  
   103  func (registry *ServiceRegistry) KnowAction(name string) bool {
   104  	return registry.actions.Find(name) != nil
   105  }
   106  
   107  func (registry *ServiceRegistry) KnowNode(nodeID string) bool {
   108  	_, found := registry.nodes.findNode(nodeID)
   109  	return found
   110  }
   111  
   112  func (registry *ServiceRegistry) LocalNode() moleculer.Node {
   113  	return registry.localNode
   114  }
   115  
   116  func (registry *ServiceRegistry) setupMessageHandlers() {
   117  	messageHandler := map[string]messageHandlerFunc{
   118  		"HEARTBEAT":  registry.filterMessages(registry.heartbeatMessageReceived),
   119  		"DISCONNECT": registry.filterMessages(registry.disconnectMessageReceived),
   120  		"INFO":       registry.filterMessages(registry.remoteNodeInfoReceived),
   121  	}
   122  	registry.broker.Bus().On("$registry.transit.message", func(args ...interface{}) {
   123  		registry.logger.Trace("Registry -> $registry.transit.message event - args: ", args)
   124  		command := args[0].(string)
   125  		message := args[1].(moleculer.Payload)
   126  		handler := messageHandler[command]
   127  		if handler == nil {
   128  			panic(errors.New(fmt.Sprint("Registry - $registry.transit.message event - invalid command:", command)))
   129  		}
   130  		handler(message)
   131  	})
   132  }
   133  
   134  func (registry *ServiceRegistry) Stop() {
   135  	registry.logger.Debug("Registry Stopping...")
   136  	registry.stopping = true
   137  	err := <-registry.transit.Disconnect()
   138  	registry.localNode.Unavailable()
   139  	if err != nil {
   140  		registry.logger.Debug("Error trying to disconnect transit - error: ", err)
   141  		return
   142  	}
   143  	registry.logger.Debug("Transit Disconnected -> Registry Full Stop!")
   144  }
   145  
   146  func (registry *ServiceRegistry) LocalServices() []*service.Service {
   147  	return []*service.Service{createNodeService(registry)}
   148  }
   149  
   150  // Start : start the registry background processes.
   151  func (registry *ServiceRegistry) Start() {
   152  	registry.logger.Debug("Registry Start() ")
   153  	registry.stopping = false
   154  	err := <-registry.transit.Connect()
   155  	if err != nil {
   156  		panic(errors.New(fmt.Sprint("Could not connect to the transit. err: ", err)))
   157  	}
   158  	<-registry.transit.DiscoverNodes()
   159  
   160  	registry.nodes.Add(registry.localNode)
   161  
   162  	if registry.heartbeatFrequency > 0 {
   163  		go registry.loopWhileAlive(registry.heartbeatFrequency, registry.transit.SendHeartbeat)
   164  	}
   165  	if registry.heartbeatTimeout > 0 {
   166  		go registry.loopWhileAlive(registry.heartbeatTimeout, registry.checkExpiredRemoteNodes)
   167  	}
   168  	if registry.offlineCheckFrequency > 0 {
   169  		go registry.loopWhileAlive(registry.offlineCheckFrequency, registry.checkOfflineNodes)
   170  	}
   171  }
   172  
   173  func (registry *ServiceRegistry) ServiceForAction(name string) []*service.Service {
   174  	actions := registry.actions.Find(name)
   175  	if actions != nil {
   176  		result := make([]*service.Service, len(actions))
   177  		for i, action := range actions {
   178  			result[i] = action.Service()
   179  		}
   180  		return result
   181  	}
   182  	return nil
   183  }
   184  
   185  // HandleRemoteEvent handle when a remote event is delivered and call all the local handlers.
   186  func (registry *ServiceRegistry) HandleRemoteEvent(context moleculer.BrokerContext) {
   187  	name := context.EventName()
   188  	groups := context.Groups()
   189  	if registry.stopping {
   190  		registry.logger.Error("HandleRemoteEvent() - registry is stopping. Discarding event -> name: ", name, " groups: ", groups)
   191  		return
   192  	}
   193  	broadcast := context.IsBroadcast()
   194  	registry.logger.Debug("HandleRemoteEvent() - name: ", name, " groups: ", groups)
   195  
   196  	var stg strategy.Strategy
   197  	if !broadcast {
   198  		stg = registry.strategy
   199  	}
   200  	entries := registry.events.Find(name, groups, true, true, stg)
   201  	for _, localEvent := range entries {
   202  		go localEvent.emitLocalEvent(context)
   203  	}
   204  }
   205  
   206  // LoadBalanceEvent load balance an event based on the known targetNodes.
   207  func (registry *ServiceRegistry) LoadBalanceEvent(context moleculer.BrokerContext) []*EventEntry {
   208  	name := context.EventName()
   209  	params := context.Payload()
   210  	groups := context.Groups()
   211  	eventSig := fmt.Sprint("name: ", name, " groups: ", groups)
   212  	registry.logger.Trace("LoadBalanceEvent() - ", eventSig, " params: ", params)
   213  
   214  	entries := registry.events.Find(name, groups, true, false, registry.strategy)
   215  	if entries == nil {
   216  		msg := fmt.Sprint("Broker - no endpoints found for event: ", name, " it was discarded!")
   217  		registry.logger.Warn(msg)
   218  		return nil
   219  	}
   220  
   221  	for _, eventEntry := range entries {
   222  		if eventEntry.isLocal {
   223  			eventEntry.emitLocalEvent(context)
   224  		} else {
   225  			registry.emitRemoteEvent(context, eventEntry)
   226  		}
   227  	}
   228  	registry.logger.Trace("LoadBalanceEvent() - ", eventSig, " End.")
   229  	return entries
   230  }
   231  
   232  func (registry *ServiceRegistry) BroadcastEvent(context moleculer.BrokerContext) []*EventEntry {
   233  	name := context.EventName()
   234  	groups := context.Groups()
   235  	eventSig := fmt.Sprint("name: ", name, " groups: ", groups)
   236  	registry.logger.Trace("BroadcastEvent() - ", eventSig, " payload: ", context.Payload())
   237  
   238  	entries := registry.events.Find(name, groups, false, false, nil)
   239  	if entries == nil {
   240  		msg := fmt.Sprint("Broker - no endpoints found for event: ", name, " it was discarded!")
   241  		registry.logger.Warn(msg)
   242  		return nil
   243  	}
   244  
   245  	for _, eventEntry := range entries {
   246  		if eventEntry.isLocal {
   247  			eventEntry.emitLocalEvent(context)
   248  		} else {
   249  			registry.emitRemoteEvent(context, eventEntry)
   250  		}
   251  	}
   252  	registry.logger.Trace("BroadcastEvent() - ", eventSig, " End.")
   253  	return entries
   254  }
   255  
   256  // DelegateCall : invoke a service action and return a channel which will eventualy deliver the results ;).
   257  // This call might be local or remote.
   258  func (registry *ServiceRegistry) LoadBalanceCall(context moleculer.BrokerContext, opts ...moleculer.Options) chan moleculer.Payload {
   259  	actionName := context.ActionName()
   260  	params := context.Payload()
   261  
   262  	registry.logger.Trace("LoadBalanceCall() - actionName: ", actionName, " params: ", params, " namespace: ", registry.namespace, " opts: ", opts)
   263  
   264  	actionEntry := registry.nextAction(actionName, registry.strategy, opts...)
   265  	if actionEntry == nil {
   266  		msg := "Registry - endpoint not found for actionName: " + actionName
   267  		if registry.namespace != "" {
   268  			msg = msg + " namespace: " + registry.namespace
   269  		}
   270  		registry.logger.Error(msg)
   271  		resultChan := make(chan moleculer.Payload, 1)
   272  		resultChan <- payload.Error(msg)
   273  		return resultChan
   274  	}
   275  	registry.logger.Debug("LoadBalanceCall() - actionName: ", actionName, " target nodeID: ", actionEntry.TargetNodeID())
   276  
   277  	if actionEntry.isLocal {
   278  		registry.broker.MiddlewareHandler("beforeLocalAction", context)
   279  		result := <-actionEntry.invokeLocalAction(context)
   280  		tempParams := registry.broker.MiddlewareHandler("afterLocalAction", middleware.AfterActionParams{context, result})
   281  		actionParams := tempParams.(middleware.AfterActionParams)
   282  
   283  		resultChan := make(chan moleculer.Payload, 1)
   284  		resultChan <- actionParams.Result
   285  		return resultChan
   286  	}
   287  
   288  	registry.broker.MiddlewareHandler("beforeRemoteAction", context)
   289  	result := <-registry.invokeRemoteAction(context, actionEntry)
   290  	tempParams := registry.broker.MiddlewareHandler("afterRemoteAction", middleware.AfterActionParams{context, result})
   291  	actionParams := tempParams.(middleware.AfterActionParams)
   292  
   293  	resultChan := make(chan moleculer.Payload, 1)
   294  	resultChan <- actionParams.Result
   295  	return resultChan
   296  
   297  }
   298  
   299  func (registry *ServiceRegistry) emitRemoteEvent(context moleculer.BrokerContext, eventEntry *EventEntry) {
   300  	context.SetTargetNodeID(eventEntry.TargetNodeID())
   301  	registry.logger.Trace("Before invoking remote event: ", context.EventName(), " context.TargetNodeID: ", context.TargetNodeID(), " context.Payload(): ", context.Payload())
   302  	registry.transit.Emit(context)
   303  }
   304  
   305  func (registry *ServiceRegistry) invokeRemoteAction(context moleculer.BrokerContext, actionEntry *ActionEntry) chan moleculer.Payload {
   306  	result := make(chan moleculer.Payload, 1)
   307  	context.SetTargetNodeID(actionEntry.TargetNodeID())
   308  	registry.logger.Trace("Before invoking remote action: ", context.ActionName(), " context.TargetNodeID: ", context.TargetNodeID(), " context.Payload(): ", context.Payload())
   309  
   310  	go func() {
   311  		actionResult := <-registry.transit.Request(context)
   312  		registry.logger.Trace("remote request done! action: ", context.ActionName(), " results: ", actionResult)
   313  		if registry.stopping {
   314  			registry.logger.Error("invokeRemoteAction() - registry is stopping. Discarding action result -> name: ", context.ActionName())
   315  			result <- payload.New(errors.New("can't complete request! registry stopping..."))
   316  		} else {
   317  			result <- actionResult
   318  		}
   319  	}()
   320  	return result
   321  }
   322  
   323  // removeServicesByNodeID
   324  func (registry *ServiceRegistry) removeServicesByNodeID(nodeID string) {
   325  	svcs := registry.services.RemoveByNode(nodeID)
   326  	if len(svcs) > 0 {
   327  		for _, svc := range svcs {
   328  			registry.broker.Bus().EmitAsync(
   329  				"$registry.service.removed",
   330  				[]interface{}{svc.Summary()})
   331  		}
   332  	}
   333  	registry.actions.RemoveByNode(nodeID)
   334  	registry.events.RemoveByNode(nodeID)
   335  }
   336  
   337  // disconnectNode remove node info (actions, events) from local registry.
   338  func (registry *ServiceRegistry) disconnectNode(nodeID string) {
   339  	node, exists := registry.nodes.findNode(nodeID)
   340  	if !exists {
   341  		return
   342  	}
   343  	registry.removeServicesByNodeID(nodeID)
   344  	node.Unavailable()
   345  	registry.broker.Bus().EmitAsync("$node.disconnected", []interface{}{nodeID})
   346  	registry.logger.Warnf("Node %s disconnected ", nodeID)
   347  }
   348  
   349  func (registry *ServiceRegistry) checkExpiredRemoteNodes() {
   350  	expiredNodes := registry.nodes.expiredNodes(registry.heartbeatTimeout)
   351  	for _, node := range expiredNodes {
   352  		registry.disconnectNode(node.GetID())
   353  	}
   354  }
   355  
   356  func (registry *ServiceRegistry) checkOfflineNodes() {
   357  	expiredNodes := registry.nodes.expiredNodes(registry.offlineTimeout)
   358  	for _, node := range expiredNodes {
   359  		nodeID := node.GetID()
   360  		registry.nodes.removeNode(nodeID)
   361  		registry.logger.Warnf("Removed offline Node: %s  from the registry because it hasn't submitted heartbeat in %d seconds.", nodeID, registry.offlineTimeout)
   362  	}
   363  }
   364  
   365  // loopWhileAlive : can the delegate runction in the given frequency and stop whe  the registry is stopping
   366  func (registry *ServiceRegistry) loopWhileAlive(frequency time.Duration, delegate func()) {
   367  	for {
   368  		if registry.stopping {
   369  			break
   370  		}
   371  		delegate()
   372  		time.Sleep(frequency)
   373  	}
   374  }
   375  
   376  func (registry *ServiceRegistry) filterMessages(handler func(message moleculer.Payload)) func(message moleculer.Payload) {
   377  	return func(message moleculer.Payload) {
   378  		if registry.stopping {
   379  			registry.logger.Warn("filterMessages() - registry is stopping. Discarding message: ", message)
   380  			return
   381  		}
   382  		if message.Get("sender").Exists() && message.Get("sender").String() == registry.localNode.GetID() {
   383  			registry.logger.Debug("filterMessages() - Same host message (sender == localNodeID). discarding... ", message)
   384  			return
   385  		}
   386  		handler(message)
   387  	}
   388  }
   389  
   390  func (registry *ServiceRegistry) heartbeatMessageReceived(message moleculer.Payload) {
   391  	heartbeat := message.RawMap()
   392  	succesful := registry.nodes.HeartBeat(heartbeat)
   393  	if !succesful {
   394  		sender := heartbeat["sender"].(string)
   395  		registry.transit.DiscoverNode(sender)
   396  	}
   397  }
   398  
   399  // disconnectMessageReceived handles when a disconnect msg is received.
   400  // It remove all actions/events from the sender node from the local registry.
   401  func (registry *ServiceRegistry) disconnectMessageReceived(message moleculer.Payload) {
   402  	sender := message.Get("sender").String()
   403  	node, exists := registry.nodes.findNode(sender)
   404  	registry.logger.Debug("disconnectMessageReceived() sender: ", sender, " exists: ", exists)
   405  	if exists {
   406  		registry.disconnectNode(node.GetID())
   407  	}
   408  }
   409  
   410  func compatibility(info map[string]interface{}) map[string]interface{} {
   411  	_, exists := info["version"]
   412  	if !exists {
   413  		info["version"] = ""
   414  	}
   415  	return info
   416  }
   417  
   418  // remoteNodeInfoReceived process the remote node info message and add to local registry.
   419  func (registry *ServiceRegistry) remoteNodeInfoReceived(message moleculer.Payload) {
   420  	registry.nodeReceivedMutex.Lock()
   421  	defer registry.nodeReceivedMutex.Unlock()
   422  
   423  	msgMap := message.RawMap()
   424  	nodeID := message.Get("sender").String()
   425  	services := message.Get("services").MapArray()
   426  	exists, reconnected, removedServices := registry.nodes.Info(msgMap)
   427  
   428  	for _, serviceInfo := range services {
   429  		serviceInfo = compatibility(serviceInfo)
   430  		svc, newService, updatedActions, newActions, deletedActions, updatedEvents, newEvents, deletedEvents := registry.services.updateRemote(nodeID, serviceInfo)
   431  
   432  		for _, newAction := range newActions {
   433  			serviceAction := service.CreateServiceAction(
   434  				serviceInfo["name"].(string),
   435  				newAction.Name(),
   436  				nil,
   437  				moleculer.ObjectSchema{nil})
   438  			registry.actions.Add(serviceAction, svc, false)
   439  		}
   440  
   441  		for _, updates := range updatedActions {
   442  			fullname := updates["name"].(string)
   443  			registry.actions.Update(nodeID, fullname, updates)
   444  		}
   445  
   446  		for _, deleted := range deletedActions {
   447  			fullname := deleted.FullName()
   448  			registry.actions.Remove(nodeID, fullname)
   449  		}
   450  
   451  		for _, newEvent := range newEvents {
   452  			serviceEvent := service.CreateServiceEvent(
   453  				newEvent.Name(),
   454  				serviceInfo["name"].(string),
   455  				newEvent.Group(),
   456  				newEvent.Handler())
   457  			registry.events.Add(serviceEvent, svc, false)
   458  		}
   459  
   460  		for _, updates := range updatedEvents {
   461  			name := updates["name"].(string)
   462  			registry.events.Update(nodeID, name, updates)
   463  		}
   464  
   465  		for _, deleted := range deletedEvents {
   466  			name := deleted.Name()
   467  			registry.events.Remove(nodeID, name)
   468  		}
   469  
   470  		if newService {
   471  			registry.logger.Infof("Registry - remote %s service is registered.", svc.FullName())
   472  
   473  			registry.broker.Bus().EmitAsync(
   474  				"$registry.service.added",
   475  				[]interface{}{svc.Summary()})
   476  		}
   477  
   478  		for _, removedService := range removedServices {
   479  			name := removedService["name"].(string)
   480  			registry.services.Remove(nodeID, name)
   481  		}
   482  	}
   483  
   484  	var neighbours int64
   485  	if message.Get("neighbours").Exists() {
   486  		neighbours = message.Get("neighbours").Int64()
   487  	}
   488  
   489  	eventParam := []interface{}{nodeID, neighbours}
   490  	eventName := "$node.connected"
   491  	if exists {
   492  		eventName = "$node.updated"
   493  	} else if reconnected {
   494  		eventName = "$node.reconnected"
   495  	}
   496  	registry.broker.Bus().EmitAsync(eventName, eventParam)
   497  }
   498  
   499  // subscribeInternalEvent subscribe event listeners for internal events (e.g. $node.disconnected) using the localBus.
   500  func (registry *ServiceRegistry) subscribeInternalEvent(event service.Event) {
   501  	registry.broker.Bus().On(event.Name(), func(data ...interface{}) {
   502  		params := payload.New(nil)
   503  		if len(data) > 0 {
   504  			params = payload.New(data[0])
   505  		}
   506  		brokerContext := registry.broker.BrokerContext()
   507  		eventContext := brokerContext.ChildEventContext(event.Name(), params, nil, false)
   508  		event.Handler()(eventContext.(moleculer.Context), params)
   509  	})
   510  }
   511  
   512  // AddLocalService : add a local service to the registry
   513  // it will create endpoints for all service actions.
   514  func (registry *ServiceRegistry) AddLocalService(service *service.Service) {
   515  	if registry.services.Find(service.Name(), service.Version(), registry.localNode.GetID()) {
   516  		registry.logger.Trace("registry - AddLocalService() - Service already registered, will ignore.. service fullName: ", service.FullName())
   517  		return
   518  	}
   519  
   520  	registry.services.Add(service)
   521  
   522  	actions := service.Actions()
   523  	events := service.Events()
   524  
   525  	for _, action := range actions {
   526  		registry.actions.Add(action, service, true)
   527  	}
   528  	for _, event := range events {
   529  		if strings.Index(event.Name(), "$") == 0 {
   530  			registry.subscribeInternalEvent(event)
   531  		} else {
   532  			registry.events.Add(event, service, true)
   533  		}
   534  	}
   535  	registry.localNode.Publish(service.AsMap())
   536  	registry.logger.Debug("Registry published local service: ", service.FullName(), " # actions: ", len(actions), " # events: ", len(events), " nodeID: ", service.NodeID())
   537  	registry.notifyServiceAdded(service.Summary())
   538  }
   539  
   540  // notifyServiceAdded notify when a service is added to the registry.
   541  func (registry *ServiceRegistry) notifyServiceAdded(svc map[string]string) {
   542  	if registry.broker.IsStarted() {
   543  		registry.broker.Bus().EmitAsync(
   544  			"$registry.service.added",
   545  			[]interface{}{svc})
   546  	} else {
   547  		registry.broker.Bus().Once("$broker.started", func(...interface{}) {
   548  			registry.broker.Bus().EmitAsync(
   549  				"$registry.service.added",
   550  				[]interface{}{svc})
   551  		})
   552  	}
   553  }
   554  
   555  // nextAction it will find and return the next action to be invoked.
   556  // If multiple nodes that contain this action are found it will use the strategy to decide which one to use.
   557  func (registry *ServiceRegistry) nextAction(actionName string, strategy strategy.Strategy, opts ...moleculer.Options) *ActionEntry {
   558  	if len(opts) > 0 && opts[0].NodeID != "" {
   559  		return registry.actions.NextFromNode(actionName, opts[0].NodeID)
   560  	}
   561  	return registry.actions.Next(actionName, strategy)
   562  }
   563  
   564  func (registry *ServiceRegistry) KnownEventListeners(addNode bool) []string {
   565  	events := registry.events.list()
   566  	result := make([]string, len(events))
   567  	for index, event := range events {
   568  		if addNode {
   569  			result[index] = fmt.Sprint(event.targetNodeID, ".", event.event.ServiceName(), ".", event.event.Name())
   570  		} else {
   571  			result[index] = fmt.Sprint(event.event.ServiceName(), ".", event.event.Name())
   572  		}
   573  
   574  	}
   575  	sort.Strings(result)
   576  	return result
   577  }
   578  
   579  func (registry *ServiceRegistry) KnownNodes() []string {
   580  	nodes := registry.nodes.list()
   581  	result := make([]string, len(nodes))
   582  	for index, node := range nodes {
   583  		result[index] = node.GetID()
   584  	}
   585  	sort.Strings(result)
   586  	return result
   587  }