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

     1  package registry
     2  
     3  import (
     4  	"fmt"
     5  	"runtime/debug"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/moleculer-go/moleculer"
    10  	"github.com/moleculer-go/moleculer/payload"
    11  	"github.com/moleculer-go/moleculer/service"
    12  	"github.com/moleculer-go/moleculer/strategy"
    13  	log "github.com/sirupsen/logrus"
    14  )
    15  
    16  type ActionEntry struct {
    17  	targetNodeID string
    18  	action       *service.Action
    19  	isLocal      bool
    20  	service      *service.Service
    21  	logger       *log.Entry
    22  }
    23  
    24  type actionsMap map[string][]ActionEntry
    25  
    26  type ActionCatalog struct {
    27  	actions sync.Map
    28  	logger  *log.Entry
    29  }
    30  
    31  func CreateActionCatalog(logger *log.Entry) *ActionCatalog {
    32  	return &ActionCatalog{actions: sync.Map{}, logger: logger}
    33  }
    34  
    35  var actionCallRecovery = true //TODO extract this to a Config - useful to turn for Debug in tests.
    36  
    37  type ActionError struct {
    38  	message string
    39  	stack   string
    40  	action  string
    41  }
    42  
    43  func (e *ActionError) Error() string {
    44  	return e.message
    45  }
    46  
    47  func (e *ActionError) Stack() string {
    48  	return e.stack
    49  }
    50  
    51  func (e *ActionError) Action() string {
    52  	return e.action
    53  }
    54  
    55  // catchActionError is called defered after invoking a local action
    56  // if there is an error (recover () != nil) this functions log the error and stack track and encapsulate
    57  // the error inside a moleculer.Payload
    58  func (actionEntry *ActionEntry) catchActionError(context moleculer.BrokerContext, result chan moleculer.Payload) {
    59  	if !actionCallRecovery {
    60  		return
    61  	}
    62  	if err := recover(); err != nil {
    63  		stackTrace := string(debug.Stack())
    64  		actionEntry.logger.Error("Action failed: ", context.ActionName(), "\n[Error]: ", err, "\n[Stack Trace]: ", stackTrace)
    65  		errT, isError := err.(error)
    66  		msg := ""
    67  		if isError {
    68  			msg = errT.Error()
    69  		} else {
    70  			msg = fmt.Sprint(err)
    71  		}
    72  		result <- payload.New(&ActionError{msg, stackTrace, actionEntry.action.Name()})
    73  	}
    74  }
    75  
    76  func (actionEntry *ActionEntry) invokeLocalAction(context moleculer.BrokerContext) chan moleculer.Payload {
    77  	result := make(chan moleculer.Payload, 1)
    78  
    79  	actionEntry.logger.Trace("Before Invoking action: ", context.ActionName(), " params: ", context.Payload())
    80  
    81  	go func() {
    82  		defer actionEntry.catchActionError(context, result)
    83  		handler := actionEntry.action.Handler()
    84  		actionResult := handler(context.(moleculer.Context), context.Payload())
    85  
    86  		actionEntry.logger.Trace("After Invoking action: ", context.ActionName(), " result: ", actionResult)
    87  		result <- payload.New(actionResult)
    88  	}()
    89  
    90  	return result
    91  }
    92  
    93  func (actionEntry ActionEntry) TargetNodeID() string {
    94  	return actionEntry.targetNodeID
    95  }
    96  
    97  func (actionEntry ActionEntry) IsLocal() bool {
    98  	return actionEntry.isLocal
    99  }
   100  
   101  func (actionEntry ActionEntry) Service() *service.Service {
   102  	return actionEntry.service
   103  }
   104  
   105  func (actionCatalog *ActionCatalog) listByName() map[string][]ActionEntry {
   106  	result := make(map[string][]ActionEntry)
   107  	actionCatalog.actions.Range(func(key, value interface{}) bool {
   108  		result[key.(string)] = value.([]ActionEntry)
   109  		return true
   110  	})
   111  	return result
   112  }
   113  
   114  // Add a new action to the catalog.
   115  func (actionCatalog *ActionCatalog) Add(action service.Action, serv *service.Service, local bool) {
   116  	entry := ActionEntry{serv.NodeID(), &action, local, serv, actionCatalog.logger}
   117  	name := action.FullName()
   118  	ver := serv.Version()
   119  	if ver != "" && !strings.HasPrefix(name, ver) {
   120  		name = service.JoinVersionToName(name, "v"+ver)
   121  	}
   122  	list, exists := actionCatalog.actions.Load(name)
   123  	if !exists {
   124  		list = []ActionEntry{entry}
   125  	} else {
   126  		list = append(list.([]ActionEntry), entry)
   127  	}
   128  	actionCatalog.actions.Store(name, list)
   129  }
   130  
   131  func (actionCatalog *ActionCatalog) Update(nodeID string, fullname string, updates map[string]interface{}) {
   132  	//TODO .. the only thing that can be udpated is the Action Schema (validation) and that does not exist yet
   133  }
   134  
   135  // RemoveByNode remove actions for the given nodeID.
   136  func (actionCatalog *ActionCatalog) RemoveByNode(nodeID string) {
   137  	actionCatalog.actions.Range(func(key, value interface{}) bool {
   138  		name := key.(string)
   139  		actions := value.([]ActionEntry)
   140  		var toKeep []ActionEntry
   141  		for _, action := range actions {
   142  			if action.targetNodeID != nodeID {
   143  				toKeep = append(toKeep, action)
   144  			}
   145  		}
   146  		if len(toKeep) == 0 {
   147  			actionCatalog.actions.Delete(name)
   148  		} else {
   149  			actionCatalog.actions.Store(name, toKeep)
   150  		}
   151  		return true
   152  	})
   153  }
   154  
   155  func (actionCatalog *ActionCatalog) Remove(nodeID string, name string) {
   156  	value, exists := actionCatalog.actions.Load(name)
   157  	if !exists {
   158  		return
   159  	}
   160  	actions := value.([]ActionEntry)
   161  	var toKeep []ActionEntry
   162  	for _, action := range actions {
   163  		if action.targetNodeID != nodeID {
   164  			toKeep = append(toKeep, action)
   165  		}
   166  	}
   167  	actionCatalog.actions.Store(name, toKeep)
   168  }
   169  
   170  func (actionCatalog *ActionCatalog) NextFromNode(actionName string, nodeID string) *ActionEntry {
   171  	list, exists := actionCatalog.actions.Load(actionName)
   172  	if !exists {
   173  		return nil
   174  	}
   175  	actions := list.([]ActionEntry)
   176  	for _, action := range actions {
   177  		if action.targetNodeID == nodeID {
   178  			return &action
   179  		}
   180  	}
   181  	return nil
   182  }
   183  
   184  func (actionCatalog *ActionCatalog) printDebugActions() {
   185  	allActions := []string{}
   186  	actionCatalog.actions.Range(func(key, value interface{}) bool {
   187  		action := key.(string)
   188  		if strings.Index(action, "$node") == -1 {
   189  			allActions = append(allActions, action)
   190  		}
   191  		return true
   192  	})
   193  	fmt.Println("actions: ", strings.Join(allActions, ", "))
   194  	fmt.Println("")
   195  }
   196  
   197  // Next find all actions registered in this node and use the strategy to select and return the best one to be called.
   198  func (actionCatalog *ActionCatalog) Next(actionName string, stg strategy.Strategy) *ActionEntry {
   199  	actions := actionCatalog.Find(actionName)
   200  	if actions == nil {
   201  		actionCatalog.logger.Debug("actionCatalog.Next() action not found: ", actionName, "  actionCatalog.actions: ", actionCatalog.actions)
   202  		return nil
   203  	}
   204  	nodes := make([]strategy.Selector, len(actions))
   205  	for index, action := range actions {
   206  		nodes[index] = action
   207  		if action.IsLocal() {
   208  			return &action
   209  		}
   210  	}
   211  	if selected := stg.Select(nodes); selected != nil {
   212  		entry := (*selected).(ActionEntry)
   213  		return &entry
   214  	}
   215  	actionCatalog.logger.Debug("actionCatalog.Next() no entries selected for name: ", actionName, "  actionCatalog.actions: ", actionCatalog.actions)
   216  	return nil
   217  }
   218  
   219  func (actionCatalog *ActionCatalog) Find(name string) []ActionEntry {
   220  	list, exists := actionCatalog.actions.Load(name)
   221  	if !exists {
   222  		return nil
   223  	}
   224  	return list.([]ActionEntry)
   225  }