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

     1  package registry
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  
     8  	"net"
     9  	"time"
    10  
    11  	"github.com/moleculer-go/moleculer"
    12  	"github.com/moleculer-go/moleculer/util"
    13  	"github.com/moleculer-go/moleculer/version"
    14  	log "github.com/sirupsen/logrus"
    15  )
    16  
    17  type Node struct {
    18  	id                string
    19  	sequence          int64
    20  	ipList            []string
    21  	hostname          string
    22  	client            map[string]interface{}
    23  	services          []map[string]interface{}
    24  	isAvailable       bool
    25  	cpu               int64
    26  	cpuSequence       int64
    27  	lastHeartBeatTime int64
    28  	offlineSince      int64
    29  	isLocal           bool
    30  	logger            *log.Entry
    31  }
    32  
    33  func discoverIpList() []string {
    34  	var result []string
    35  	addrs, err := net.InterfaceAddrs()
    36  	if err != nil {
    37  		return make([]string, 0)
    38  	}
    39  	for _, address := range addrs {
    40  		// check the address type and if it is not a loopback the display it
    41  		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
    42  			if ipnet.IP.To4() != nil {
    43  				result = append(result, ipnet.IP.String())
    44  			}
    45  		}
    46  	}
    47  	if result == nil {
    48  		result = []string{"0.0.0.0"}
    49  	}
    50  	return result
    51  }
    52  
    53  func discoverHostname() string {
    54  	hostname, error := os.Hostname()
    55  	if error != nil {
    56  		return "unknown"
    57  	}
    58  	return hostname
    59  }
    60  
    61  func CreateNode(id string, local bool, logger *log.Entry) moleculer.Node {
    62  	ipList := discoverIpList()
    63  	hostname := discoverHostname()
    64  	services := make([]map[string]interface{}, 0)
    65  	node := Node{
    66  		id: id,
    67  		client: map[string]interface{}{
    68  			"type":        "moleculer-go",
    69  			"version":     version.Moleculer(),
    70  			"langVersion": version.Go(),
    71  		},
    72  		ipList:   ipList,
    73  		hostname: hostname,
    74  		services: services,
    75  		logger:   logger,
    76  		isLocal:  local,
    77  		sequence: 1,
    78  	}
    79  	var result moleculer.Node = &node
    80  	return result
    81  }
    82  
    83  func (node *Node) Update(id string, info map[string]interface{}) (bool, []map[string]interface{}) {
    84  	if id != node.id {
    85  		panic(fmt.Errorf("Node.Update() - the id received : %s does not match this node.id : %s", id, node.id))
    86  	}
    87  	node.logger.Debug("node.Update() - info:")
    88  	node.logger.Debug(util.PrettyPrintMap(info))
    89  
    90  	reconnected := !node.isAvailable
    91  
    92  	node.isAvailable = true
    93  	node.lastHeartBeatTime = time.Now().Unix()
    94  	node.offlineSince = 0
    95  
    96  	node.ipList = interfaceToString(info["ipList"].([]interface{}))
    97  	node.hostname = info["hostname"].(string)
    98  	node.client = info["client"].(map[string]interface{})
    99  
   100  	services, removedServices := FilterServices(node.services, info)
   101  	node.logger.Debug("removedServices:", util.PrettyPrintMap(removedServices))
   102  
   103  	node.services = services
   104  	node.sequence = int64Field(info, "seq", 0)
   105  	node.cpu = int64Field(info, "cpu", 0)
   106  	node.cpuSequence = int64Field(info, "cpuSeq", 0)
   107  
   108  	return reconnected, removedServices
   109  }
   110  
   111  // FilterServices return all services excluding local services (example: $node) and return all removed services, services that existed in the currentServices list but
   112  // no longer exist in th new list coming from the info package
   113  func FilterServices(currentServices []map[string]interface{}, info map[string]interface{}) ([]map[string]interface{}, []map[string]interface{}) {
   114  	item, ok := info["services"]
   115  	var incomingServices []interface{}
   116  	if ok {
   117  		incomingServices = item.([]interface{})
   118  	}
   119  	//get current services
   120  	services := []map[string]interface{}{}
   121  	for _, item := range incomingServices {
   122  		incomingService := item.(map[string]interface{})
   123  		incomingName := incomingService["name"].(string)
   124  		if strings.Index(incomingName, "$") == 0 {
   125  			//ignore services prefixed with $ (e.g. $node)
   126  			continue
   127  		}
   128  		services = append(services, incomingService)
   129  	}
   130  	//check for removed services
   131  	removedServices := []map[string]interface{}{}
   132  	for _, currentService := range currentServices {
   133  		currentName := currentService["name"].(string)
   134  
   135  		removed := true
   136  		for _, service := range services {
   137  			if currentName == service["name"].(string) {
   138  				removed = false
   139  				break
   140  			}
   141  		}
   142  		if removed {
   143  			removedServices = append(removedServices, currentService)
   144  		}
   145  	}
   146  	return services, removedServices
   147  }
   148  
   149  func int64Field(values map[string]interface{}, field string, def int64) int64 {
   150  	raw, exists := values[field]
   151  	if !exists {
   152  		return def
   153  	}
   154  	fv, valid := raw.(float64)
   155  	if !valid {
   156  		return def
   157  	}
   158  	return int64(fv)
   159  }
   160  
   161  func interfaceToString(list []interface{}) []string {
   162  	result := make([]string, len(list))
   163  	for index, item := range list {
   164  		result[index] = item.(string)
   165  	}
   166  	return result
   167  }
   168  
   169  // ExportAsMap export the node info as a map
   170  // this map is used to publish the node info to other nodes.
   171  func (node *Node) ExportAsMap() map[string]interface{} {
   172  	resultMap := make(map[string]interface{})
   173  	resultMap["id"] = node.id
   174  	resultMap["services"] = node.services // node.removeInternalServices(node.services)
   175  	resultMap["ipList"] = node.ipList
   176  	resultMap["hostname"] = node.hostname
   177  	resultMap["client"] = node.client
   178  	resultMap["seq"] = node.sequence
   179  	resultMap["cpu"] = node.cpu
   180  	resultMap["cpuSeq"] = node.cpuSequence
   181  	resultMap["available"] = node.IsAvailable()
   182  	resultMap["metadata"] = make(map[string]interface{})
   183  
   184  	return resultMap
   185  }
   186  
   187  func (node *Node) GetID() string {
   188  	return node.id
   189  }
   190  func (node *Node) IsExpired(timeout time.Duration) bool {
   191  	if node.IsLocal() || !node.IsAvailable() {
   192  		return false
   193  	}
   194  	diff := time.Now().Unix() - node.lastHeartBeatTime
   195  	return diff > int64(timeout.Seconds())
   196  }
   197  
   198  func (node *Node) HeartBeat(heartbeat map[string]interface{}) {
   199  	if !node.isAvailable {
   200  		node.isAvailable = true
   201  		node.offlineSince = 0
   202  	}
   203  	node.cpu = int64Field(heartbeat, "cpu", 0)
   204  	node.cpuSequence = int64Field(heartbeat, "cpuSeq", 0)
   205  	node.lastHeartBeatTime = time.Now().Unix()
   206  }
   207  
   208  func (node *Node) Publish(service map[string]interface{}) {
   209  	node.services = append(node.services, service)
   210  }
   211  
   212  func (node *Node) IsAvailable() bool {
   213  	return node.isLocal || node.isAvailable
   214  }
   215  
   216  // Unavailable mark the node as unavailable
   217  func (node *Node) Unavailable() {
   218  	node.isAvailable = false
   219  }
   220  
   221  // Unavailable mark the node as available
   222  func (node *Node) Available() {
   223  	node.isAvailable = true
   224  }
   225  
   226  func (node *Node) IsLocal() bool {
   227  	return node.isLocal
   228  }
   229  
   230  func (node *Node) IncreaseSequence() {
   231  	node.sequence++
   232  }