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

     1  package registry
     2  
     3  import (
     4  	"strings"
     5  	"time"
     6  
     7  	"github.com/moleculer-go/moleculer"
     8  	"github.com/moleculer-go/moleculer/service"
     9  )
    10  
    11  // createNodeService create the local node service -> $node.
    12  func createNodeService(registry *ServiceRegistry) *service.Service {
    13  	var startedTime time.Time
    14  	isAvailable := func(nodeID string) bool {
    15  		node, exists := registry.nodes.findNode(nodeID)
    16  		return exists && node.IsAvailable()
    17  	}
    18  
    19  	isLocal := func(nodeID string) bool {
    20  		return registry.localNode.GetID() == nodeID
    21  	}
    22  
    23  	without := func(in map[string]interface{}, key string) map[string]interface{} {
    24  		delete(in, key)
    25  		return in
    26  	}
    27  	return service.FromSchema(moleculer.ServiceSchema{
    28  		Name: "$node",
    29  		Started: func(moleculer.BrokerContext, moleculer.ServiceSchema) {
    30  			startedTime = time.Now()
    31  		},
    32  		Actions: []moleculer.Action{
    33  			{
    34  				Name: "events",
    35  				Handler: func(context moleculer.Context, params moleculer.Payload) interface{} {
    36  					onlyLocal := params.Get("onlyLocal").Exists() && params.Get("onlyLocal").Bool()
    37  					onlyAvailable := params.Get("onlyAvailable").Exists() && params.Get("onlyAvailable").Bool()
    38  					skipInternal := params.Get("skipInternal").Exists() && params.Get("skipInternal").Bool()
    39  					withEndpoints := params.Get("withEndpoints").Exists() && params.Get("withEndpoints").Bool()
    40  
    41  					result := make([]map[string]interface{}, 0)
    42  					for name, entries := range registry.events.listByName() {
    43  						has := func(check func(nodeID string) bool) bool {
    44  							for _, item := range entries {
    45  								if check(item.service.NodeID()) {
    46  									return true
    47  								}
    48  							}
    49  							return false
    50  						}
    51  						endpoints := func() []map[string]interface{} {
    52  							list := make([]map[string]interface{}, 0)
    53  							for _, item := range entries {
    54  								nodeID := item.service.NodeID()
    55  								list = append(list, map[string]interface{}{
    56  									"nodeID":    nodeID,
    57  									"available": isAvailable(nodeID),
    58  								})
    59  							}
    60  							return list
    61  						}
    62  						if onlyLocal && !has(isLocal) {
    63  							continue
    64  						}
    65  						if onlyAvailable && !has(isAvailable) {
    66  							continue
    67  						}
    68  						if skipInternal && strings.Index(name, "$") == 0 {
    69  							continue
    70  						}
    71  						item := map[string]interface{}{
    72  							"name":      name,
    73  							"group":     entries[0].event.Group(),
    74  							"count":     len(entries),
    75  							"hasLocal":  has(isLocal),
    76  							"available": has(isAvailable),
    77  						}
    78  						if withEndpoints {
    79  							item["endpoints"] = endpoints()
    80  						}
    81  						result = append(result, item)
    82  					}
    83  
    84  					return result
    85  				},
    86  			},
    87  			{
    88  				Name:        "actions",
    89  				Description: "Find and return a list of actions in the registry of this service broker.",
    90  				Handler: func(context moleculer.Context, params moleculer.Payload) interface{} {
    91  					onlyLocal := params.Get("onlyLocal").Exists() && params.Get("onlyLocal").Bool()
    92  					onlyAvailable := params.Get("onlyAvailable").Exists() && params.Get("onlyAvailable").Bool()
    93  					skipInternal := params.Get("skipInternal").Exists() && params.Get("skipInternal").Bool()
    94  					withEndpoints := params.Get("withEndpoints").Exists() && params.Get("withEndpoints").Bool()
    95  
    96  					result := make([]map[string]interface{}, 0)
    97  					for name, entries := range registry.actions.listByName() {
    98  						has := func(check func(nodeID string) bool) bool {
    99  							for _, item := range entries {
   100  								if check(item.service.NodeID()) {
   101  									return true
   102  								}
   103  							}
   104  							return false
   105  						}
   106  						endpoints := func() []map[string]interface{} {
   107  							list := make([]map[string]interface{}, 0)
   108  							for _, item := range entries {
   109  								list = append(list, map[string]interface{}{
   110  									"nodeID":    item.service.NodeID(),
   111  									"available": isAvailable(item.service.NodeID()),
   112  								})
   113  							}
   114  							return list
   115  						}
   116  						context.Logger().Debug("$node.actions name: ", name)
   117  						if onlyLocal && !has(isLocal) {
   118  							continue
   119  						}
   120  						if onlyAvailable && !has(isAvailable) {
   121  							continue
   122  						}
   123  						if skipInternal && strings.Index(name, "$") == 0 {
   124  							continue
   125  						}
   126  						item := map[string]interface{}{
   127  							"name":      name,
   128  							"count":     len(entries),
   129  							"hasLocal":  has(isLocal),
   130  							"available": has(isAvailable),
   131  						}
   132  						if withEndpoints {
   133  							item["endpoints"] = endpoints()
   134  						}
   135  						result = append(result, item)
   136  						context.Logger().Debug("$node.actions name: ", name, " contents: ", item)
   137  					}
   138  
   139  					return result
   140  				},
   141  			},
   142  			{
   143  				Name:        "services",
   144  				Description: "Find and return a list of services in the registry of this service broker.",
   145  				Schema: moleculer.ObjectSchema{
   146  					Source: struct {
   147  						withActions   bool
   148  						withEndpoints bool
   149  						withEvents    bool `required:"true"`
   150  						skipInternal  bool
   151  						onlyAvailable bool
   152  						onlyLocal     bool
   153  						result        []map[string]interface{}
   154  					}{withActions: true},
   155  				},
   156  				Handler: func(context moleculer.Context, params moleculer.Payload) interface{} {
   157  					context.Logger().Debug("$node.services params: ", params.Value())
   158  
   159  					//TODO simplify this by removing the .Exists() check once we have action schema validation and default values assignment.
   160  					skipInternal := params.Get("skipInternal").Exists() && params.Get("skipInternal").Bool()
   161  					onlyAvailable := params.Get("onlyAvailable").Exists() && params.Get("onlyAvailable").Bool()
   162  					onlyLocal := params.Get("onlyLocal").Exists() && params.Get("onlyLocal").Bool()
   163  					withActions := params.Get("withActions").Exists() && params.Get("withActions").Bool()
   164  					withEvents := params.Get("withEvents").Exists() && params.Get("withEvents").Bool()
   165  					withEndpoints := params.Get("withEndpoints").Exists() && params.Get("withEndpoints").Bool()
   166  
   167  					result := make([]map[string]interface{}, 0)
   168  					for name, entries := range registry.services.listByName() {
   169  						has := func(check func(nodeID string) bool) bool {
   170  							for _, item := range entries {
   171  								if check(item.nodeID) {
   172  									return true
   173  								}
   174  							}
   175  							return false
   176  						}
   177  						endpoints := func() []map[string]interface{} {
   178  							list := make([]map[string]interface{}, 0)
   179  							for _, item := range entries {
   180  								list = append(list, map[string]interface{}{
   181  									"nodeID":    item.nodeID,
   182  									"available": isAvailable(item.nodeID),
   183  								})
   184  							}
   185  							return list
   186  						}
   187  						if onlyLocal && !has(isLocal) {
   188  							continue
   189  						}
   190  						if onlyAvailable && !has(isAvailable) {
   191  							continue
   192  						}
   193  						if skipInternal && strings.Index(name, "$") == 0 {
   194  							continue
   195  						}
   196  						smap := entries[0].service.AsMap()
   197  						smap["available"] = has(isAvailable)
   198  						smap["hasLocal"] = has(isLocal)
   199  						smap = without(smap, "nodeID")
   200  						if withEndpoints {
   201  							smap["endpoints"] = endpoints()
   202  						}
   203  						if !withActions {
   204  							smap = without(smap, "actions")
   205  						}
   206  						if !withEvents {
   207  							smap = without(smap, "events")
   208  						}
   209  						result = append(result, smap)
   210  					}
   211  					return result
   212  				},
   213  			},
   214  			{
   215  				Name:        "list",
   216  				Description: "Find and return a list of nodes in the registry of this service broker.",
   217  				Handler: func(context moleculer.Context, params moleculer.Payload) interface{} {
   218  					withServices := params.Get("withServices").Exists() && params.Get("withServices").Bool()
   219  					onlyAvailable := params.Get("onlyAvailable").Exists() && params.Get("onlyAvailable").Bool()
   220  
   221  					nodes := registry.nodes.list()
   222  					result := make([]map[string]interface{}, 0)
   223  					for _, node := range nodes {
   224  						if onlyAvailable && !node.IsAvailable() {
   225  							continue
   226  						}
   227  						maps := node.ExportAsMap()
   228  						if withServices {
   229  							if !isLocal(node.GetID()) {
   230  								maps["services"] = filterLocal(maps["services"].([]map[string]interface{}))
   231  							}
   232  						} else {
   233  							delete(maps, "services")
   234  						}
   235  						result = append(result, maps)
   236  					}
   237  
   238  					return result
   239  				},
   240  			},
   241  			//
   242  			/* FIXME: currently returns incomplete payload */
   243  			{
   244  				Name:        "health",
   245  				Description: "Return health status of local node including transit, os, cpu, memory, process, network, client information.",
   246  				Handler: func(context moleculer.Context, params moleculer.Payload) interface{} {
   247  					/* TODO: map as JSON which follows standard structure
   248  					{ cpu:
   249  						 { load1: 1.802734375,
   250  						 load5: 1.8603515625,
   251  						 load15: 1.82666015625,
   252  						 cores: 16,
   253  						 utilization: 11 },
   254  						mem:
   255  						 { free: 886513664,
   256  						 total: 68719476736,
   257  						 percent: 1.2900471687316895 },
   258  						os:
   259  						 { uptime: 1507874,
   260  						 type: 'Darwin',
   261  						 release: '18.5.0',
   262  						 hostname: 'imac.local',
   263  						 arch: 'x64',
   264  						 platform: 'darwin',
   265  						 user:
   266  							{ uid: 501,
   267  							gid: 20,
   268  							username: 'dehypnosis',
   269  							homedir: '/Users/dehypnosis',
   270  							shell: '/bin/zsh' } },
   271  						process:
   272  						 { pid: 67218,
   273  						 memory:
   274  							{ rss: 60497920,
   275  							heapTotal: 32743424,
   276  							heapUsed: 27003144,
   277  							external: 104085 },
   278  						 uptime: 80.434,
   279  						 argv:
   280  							[ '/usr/local/bin/node',
   281  							'/usr/local/bin/moleculer',
   282  							'connect',
   283  							'nats://dev.nats.svc.cluster.local:4222' ] },
   284  						client: { type: 'nodejs', version: '0.13.8', langVersion: 'v8.16.0' },
   285  						net: { ip: [ '222.107.184.34', '172.30.1.7' ] },
   286  						transit:
   287  						 { stat:
   288  							{ packets:
   289  							 { sent: { count: 28, bytes: 13409 },
   290  								 received: { count: 158, bytes: 199426 } } } },
   291  						time:
   292  						 { now: 1556737026387,
   293  						 iso: '2019-05-01T18:57:06.387Z',
   294  						 utc: 'Wed, 01 May 2019 18:57:06 GMT' } }
   295  					*/
   296  					nodeInfo := registry.localNode.ExportAsMap()
   297  					return map[string]interface{}{
   298  						"cpu": map[string]interface{}{},
   299  						"mem": map[string]interface{}{},
   300  						"os":  map[string]interface{}{},
   301  						"process": map[string]interface{}{
   302  							"uptime": time.Since(startedTime),
   303  						},
   304  						"client": nodeInfo["client"],
   305  						"net": map[string]interface{}{
   306  							"ip": nodeInfo["ipList"],
   307  						},
   308  						"transit": map[string]interface{}{
   309  							// TODO
   310  						},
   311  						"time": map[string]interface{}{
   312  							// TODO
   313  						},
   314  					}
   315  				},
   316  			},
   317  			/* TODO: support $node.options */
   318  			{
   319  				Name:        "options",
   320  				Description: "Return broker configuration of local node.",
   321  				Handler: func(context moleculer.Context, params moleculer.Payload) interface{} {
   322  					/* TODO: map as JSON which follows standard structure
   323  					{ logger: true,
   324  						transporter: 'nats://dev.nats.svc.cluster.local:4222',
   325  						nodeID: 'cli-imac.local-67218',
   326  						namespace: '',
   327  						logLevel: null,
   328  						logFormatter: 'default',
   329  						logObjectPrinter: null,
   330  						requestTimeout: 0,
   331  						retryPolicy:
   332  						 { enabled: false,
   333  						 retries: 5,
   334  						 delay: 100,
   335  						 maxDelay: 1000,
   336  						 factor: 2,
   337  						 check: [Function: check] },
   338  						maxCallLevel: 0,
   339  						heartbeatInterval: 5,
   340  						heartbeatTimeout: 15,
   341  						tracking: { enabled: false, shutdownTimeout: 5000 },
   342  						disableBalancer: false,
   343  						registry: { strategy: 'RoundRobin', preferLocal: true },
   344  						circuitBreaker:
   345  						 { enabled: false,
   346  						 threshold: 0.5,
   347  						 windowTime: 60,
   348  						 minRequestCount: 20,
   349  						 halfOpenTime: 10000,
   350  						 check: [Function: check] },
   351  						bulkhead: { enabled: false, concurrency: 10, maxQueueSize: 100 },
   352  						transit:
   353  						 { maxQueueSize: 50000,
   354  						 packetLogFilter: [],
   355  						 disableReconnect: false,
   356  						 disableVersionCheck: false },
   357  						cacher: null,
   358  						serializer: null,
   359  						validation: true,
   360  						validator: null,
   361  						metrics: false,
   362  						metricsRate: 1,
   363  						internalServices: true,
   364  						internalMiddlewares: true,
   365  						hotReload: false,
   366  						middlewares: null,
   367  						replCommands: null }
   368  					*/
   369  					// from registry.broker.Config ?
   370  					return map[string]interface{}{}
   371  				},
   372  			},
   373  		},
   374  	}, registry.broker)
   375  }
   376  
   377  func filterLocal(in []map[string]interface{}) []map[string]interface{} {
   378  	out := make([]map[string]interface{}, 0)
   379  	for _, item := range in {
   380  		if strings.Index(item["name"].(string), "$") == 0 {
   381  			continue
   382  		}
   383  		out = append(out, item)
   384  	}
   385  	return out
   386  }