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 }