├── .gitignore ├── CHANGELOG.md ├── README.md ├── configure_helper.go ├── daemon.go ├── endpoint.go ├── endpoint_test.go ├── go.mod ├── go.sum ├── json_message.go ├── json_message_test.go ├── key_define.go ├── message.go ├── runnable.go ├── runnable_test.go ├── transaction_engine.go └── transaction_engine_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea 15 | log 16 | framework -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## [1.0.10] 2023-09-07 4 | 5 | ### Changed 6 | 7 | - update to go 1.19 8 | - update dependent packages 9 | 10 | ## [1.0.9] 2022-08-22 11 | 12 | ### Changed 13 | 14 | - Remove debug log in Daemion.isRunning 15 | 16 | ## [1.0.8] 2022-08-14 17 | 18 | ### Fixed 19 | 20 | - isRunning panic with null process 21 | 22 | ## [1.0.7] 2021-06-15 23 | 24 | ### Changed 25 | 26 | - Update go.sum 27 | 28 | ## [1.0.6] 2021-06-15 29 | 30 | ### Fixed 31 | 32 | - Snapshot cause daemon halt 33 | 34 | ## [1.0.5] 2021-06-14 35 | 36 | ### Added 37 | 38 | - Add IsRunning to SimpleRunner 39 | - Add Snapshot interface to daemon 40 | 41 | ## [1.0.4] 2021-02-18 42 | 43 | ### Added 44 | 45 | - Request/Response: SearchGuest/ModifyAutoStart 46 | 47 | ## [1.0.3] 2020-10-31 48 | 49 | ### Added 50 | 51 | - ParamKeyFrom/ParamKeyTo/ParamKeyIndex/ParamKeySecurity/ParamKeyPolicy 52 | - Get/Add/Modify/Remove GuestRule 53 | - Change guest rule order/default action 54 | - Change security policy rule order 55 | - Sync disk/media images 56 | 57 | ## [1.0.2] 2020-04-12 58 | 59 | ### Added 60 | 61 | - ParamKeyData/ParamKeyDisplay/ParamKeyDevice/ParamKeyProtocol/ParamKeyInterface/ParamKeyAction/ParamKeyTemplate 62 | - Query/Add/Modify/Remove for Cell Storage 63 | - Query/Get/Create/Modify/Delete for System Template 64 | - ResetSecret 65 | 66 | ## [1.0.1] 2020-01-02 67 | ### Added 68 | 69 | - ParamKeyHardware from MAC address 70 | 71 | ## 2019-6-23 72 | 73 | ### Added 74 | 75 | - Resource: ResourcePriority/ResourceDiskThreshold/ResourceNetworkThreshold 76 | 77 | - Operate: OperateGetBatchStop 78 | 79 | - Request/Response: ModifyPriority/ModifyDiskThreshold/ModifyNetworkThreshold/StartBatchStopGuest/GetBatchStopGuest 80 | 81 | ### Changed 82 | 83 | - Generate module name base on a specified interface 84 | 85 | ## 2019-6-3 86 | 87 | ### Fixed 88 | 89 | - Cache truncated payload for parsing JSON message 90 | 91 | ## 2019-4-17 92 | 93 | ### Added 94 | 95 | - Add service type: ServiceTypeImage (Image Server) 96 | 97 | - Register service handler 98 | 99 | ## 2019-2-28 100 | 101 | ### Added 102 | 103 | - Message/Runnable/Transaction test 104 | 105 | - Modify guest name 106 | 107 | - Batch creating/deleting guest 108 | 109 | - Message::SetID() 110 | 111 | - CloneJsonMessage 112 | 113 | ### Changed 114 | 115 | - Refactor runnable 116 | 117 | - Enable concurrent session 118 | 119 | - Create pip/pid/log base on abs binary path 120 | 121 | - Redirect message to incoming channel when the target is self. 122 | 123 | ### Fixed 124 | 125 | - Endpoint test 126 | 127 | - Peer Endpoint keep recover after stopped 128 | 129 | ## 2018-11-27 130 | 131 | ### Added 132 | 133 | - Resource: ResourceAddressPool/ResourceAddressRange 134 | 135 | - Request/Response: QueryAddressPool/GetAddressPool/CreateAddressPool/DeleteAddressPool/ModifyAddressPool/QueryAddressRange/GetAddressRange/AddAddressRange/RemoveAddressRange 136 | 137 | - Key: ParamKeyStart/ParamKeyEnd/ParamKeyMask/ParamKeyAllocate/ParamKeyGateway/ParamKeyServer/ParamKeyAssign/ParamKeyInternal/ParamKeyExternal 138 | 139 | - Event: AddressPoolChanged 140 | 141 | ## 2018-10-22 142 | 143 | ### Added 144 | 145 | - Operate: OperateEnable/OperateDisable/OperateMigrate/OperatePurge 146 | 147 | - Request/Response: EnableComputePoolCell/DisableComputePoolCell/MigrateInstance/PurgeInstance/AttachInstance/DetachInstance 148 | 149 | - Event: InstanceMigratedEvent/InstancePurgedEvent/InstanceAttachedEvent/InstanceDetachedEvent 150 | 151 | - Distinguish connection closed by the user or lost 152 | 153 | ## 2018-9-28 154 | 155 | ### Added 156 | 157 | - Resource: ResourceStoragePool 158 | 159 | - Key: ParamKeyAttach 160 | 161 | - Request/Response: QueryStoragePool/GetStoragePool/QueryStoragePoolStatus/GetStoragePoolStatus/CreateStoragePool/DeleteStoragePool/ModifyStoragePool 162 | 163 | - Event: ComputeCellDisconnectedEvent/ComputeCellAddedEvent/ComputeCellRemovedEvent 164 | 165 | ### Changed 166 | 167 | - Don't recover stub service when stop endpoint 168 | 169 | ## 2018-8-21 170 | 171 | ### Added 172 | 173 | - Key: ParamKeyPrevious/ParamKeyNext/ParamKeyCurrent/ParamKeyCreate/ParamKeyModify 174 | 175 | - Resource: ResourceSnapshot/ResourceMedia 176 | 177 | - Operate: OperateAttach/OperateDetach/OperateCommit/OperatePull/OperateRestore 178 | 179 | - Request/Response: InsertMedia/EjectMedia/QuerySnapshot/GetSnapshot/CreateSnapshot/DeleteSnapshot/RestoreSnapshot/CommitSnapshot/PullSnapshot 180 | 181 | - Event: MediaAttachedEvent/MediaDetachedEvent/SnapshotResumedEvent 182 | 183 | ## 2018-8-14 184 | 185 | ### Added 186 | 187 | - Key: ParamKeyVersion/ ParamKeyAdmin/ ParamKeyModule 188 | 189 | ## 2018-7-28 190 | 191 | ### Added 192 | 193 | - Resource: ResourceCore/ResourceMemory/ResourceDisk/ResourceAuth 194 | 195 | - Operate: OperateResize/OperateShrink 196 | 197 | - Request/Response: ModifyCore/ModifyMemory/ModifyAuth/ResizeDisk/ShrinkDisk 198 | 199 | - Key: ParamKeyImmediate/ParamKeySystem 200 | 201 | ## 2018-7-24 202 | 203 | ### Added 204 | 205 | - AddressChangedEvent/ResourceAddress 206 | 207 | ## [0.1.2] - 2018-7-16 208 | 209 | Author: Akumas 210 | 211 | ### Modified 212 | 213 | - send disconnect to remote when stop gracefully 214 | 215 | - detect and reconnect stub services when connection lost 216 | 217 | - reduce connection check interval 218 | 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nano Framework 2 | 3 | 4 | 5 | Common functions and defines for Nano 6 | 7 | Usages: 8 | 9 | ``` 10 | import "github.com/project-nano/framework" 11 | ``` 12 | 13 | Main structs 14 | 15 | 16 | - Message/JsonMessage 17 | - DaemonizedService 18 | - EndpointService 19 | - TransactionEngine -------------------------------------------------------------------------------- /configure_helper.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "strconv" 5 | "fmt" 6 | "errors" 7 | "net" 8 | "strings" 9 | ) 10 | 11 | func SelectEthernetInterface(description string, requireUpLink bool) (name string, err error) { 12 | interfaceList, err := net.Interfaces() 13 | if err != nil { 14 | return 15 | } 16 | var checkFlag net.Flags 17 | if requireUpLink{ 18 | checkFlag = net.FlagMulticast | net.FlagPointToPoint | net.FlagUp 19 | }else{ 20 | checkFlag = net.FlagMulticast | net.FlagPointToPoint 21 | } 22 | var options []string 23 | for _, i := range interfaceList { 24 | if i.Flags&net.FlagLoopback != 0 { 25 | //ignore loop back 26 | continue 27 | } 28 | if i.Flags&checkFlag != 0 { 29 | if strings.HasPrefix(i.Name, "e"){ 30 | options = append(options, i.Name) 31 | } 32 | } 33 | } 34 | if 0 == len(options){ 35 | return "", errors.New("no ethernet interface available") 36 | }else if 1 == len(options){ 37 | return options[0], nil 38 | } 39 | //output select menu 40 | var selectMap = map[int]string{} //index to name 41 | for index, name := range options{ 42 | selectMap[index] = name 43 | } 44 | var input string 45 | var exists bool 46 | for { 47 | for index, name := range selectMap{ 48 | fmt.Printf("%d> %s\n", index, name) 49 | } 50 | fmt.Printf("enter index to select interface as %s: ", description) 51 | fmt.Scanln(&input) 52 | selected, err := strconv.Atoi(input) 53 | if err != nil{ 54 | fmt.Printf("invalid input : %s", input) 55 | continue 56 | } 57 | name, exists = selectMap[selected] 58 | if !exists{ 59 | fmt.Printf("invalid selection: %d", selected) 60 | continue 61 | } 62 | return name, nil 63 | } 64 | 65 | } 66 | 67 | func ChooseIPV4Address(description string) (address string, err error){ 68 | options, err := searchIPv4Address() 69 | if err != nil{ 70 | return 71 | } 72 | if 1 == len(options){ 73 | address, err = InputString(description, options[0]) 74 | }else{ 75 | var optionMap = map[string]string{}//index to ip 76 | for index, address := range options{ 77 | key := strconv.Itoa(index) 78 | optionMap[key] = address 79 | fmt.Printf("%d> %s\n", index, address) 80 | } 81 | fmt.Printf("enter index to select address as %s, or input a new address: ", description) 82 | var input string 83 | fmt.Scanln(&input) 84 | if "" == input{ 85 | err = errors.New("must input an address") 86 | return 87 | } 88 | var exists bool 89 | if address, exists = optionMap[input];!exists{ 90 | address = input 91 | } 92 | } 93 | if nil != net.ParseIP(address){ 94 | //valid ip format 95 | return address, nil 96 | } 97 | return "", fmt.Errorf("invalid address value '%s'", address) 98 | } 99 | 100 | func InputInteger(description string, defaultValue int) (value int, err error) { 101 | fmt.Printf("%s = %d (press enter to accept or input new value): ", description, defaultValue) 102 | var input string 103 | fmt.Scanln(&input) 104 | if "" == input{ 105 | //default value 106 | value = defaultValue 107 | return 108 | } 109 | value, err = strconv.Atoi(input) 110 | if err != nil{ 111 | err = fmt.Errorf("invalid input %s", input) 112 | return 113 | } 114 | return 115 | } 116 | 117 | func InputString(description, defaultValue string) (value string, err error){ 118 | fmt.Printf("%s = '%s' (press enter to accept or input new value): ", description, defaultValue) 119 | var input string 120 | fmt.Scanln(&input) 121 | if "" == input{ 122 | //default value 123 | value = defaultValue 124 | }else{ 125 | value = input 126 | } 127 | if "" == value{ 128 | err = errors.New("no empty string allowed") 129 | } 130 | return 131 | } 132 | 133 | func InputIPAddress(description, defaultValue string) (value string, err error){ 134 | fmt.Printf("%s = '%s' (press enter to accept or input new value): ", description, defaultValue) 135 | var input string 136 | fmt.Scanln(&input) 137 | if "" == input{ 138 | //default value 139 | value = defaultValue 140 | }else{ 141 | value = input 142 | } 143 | if nil == net.ParseIP(value){ 144 | err = fmt.Errorf("invalid address '%s'", value) 145 | return "", err 146 | } 147 | return 148 | } 149 | 150 | func InputMultiCastAddress(description, defaultValue string) (address string, err error) { 151 | fmt.Printf("%s = '%s' (press enter to accept or input new value): ", description, defaultValue) 152 | var input string 153 | fmt.Scanln(&input) 154 | if "" == input{ 155 | //default value 156 | address = defaultValue 157 | }else{ 158 | address = input 159 | } 160 | var ip = net.ParseIP(address) 161 | if nil == ip{ 162 | err = fmt.Errorf("invalid address '%s'", address) 163 | return 164 | } 165 | if !ip.IsMulticast(){ 166 | err = fmt.Errorf("'%s' not a multicast address", address) 167 | return 168 | } 169 | return address, nil 170 | } 171 | 172 | func InputNetworkPort(description string, defaultValue int) (port int, err error) { 173 | const ( 174 | MaxPort = 0xFFFF 175 | ) 176 | fmt.Printf("%s = %d (press enter to accept or input new value): ", description, defaultValue) 177 | var input string 178 | fmt.Scanln(&input) 179 | if "" == input{ 180 | //default value 181 | port = defaultValue 182 | }else{ 183 | port, err = strconv.Atoi(input) 184 | if err != nil{ 185 | err = fmt.Errorf("invalid input %s", input) 186 | return 187 | } 188 | } 189 | if port > MaxPort{ 190 | err = fmt.Errorf("invalid network port %d", port) 191 | } 192 | return 193 | } 194 | 195 | func searchIPv4Address() (addresses []string, err error) { 196 | interfaceList, err := net.Interfaces() 197 | if err != nil { 198 | return 199 | } 200 | var checkFlag = net.FlagMulticast | net.FlagPointToPoint | net.FlagUp 201 | for _, i := range interfaceList { 202 | if i.Flags&net.FlagLoopback != 0 { 203 | //ignore loopback 204 | continue 205 | } 206 | if i.Flags&checkFlag != 0 { 207 | addrs, err := i.Addrs() 208 | if err != nil { 209 | return nil, err 210 | } 211 | if len(addrs) == 0 { 212 | continue 213 | } 214 | for _, addr := range addrs { 215 | ip, _, err := net.ParseCIDR(addr.String()) 216 | if err != nil { 217 | return nil, err 218 | } 219 | if ip.To4() != nil { 220 | addresses = append(addresses, ip.String()) 221 | } 222 | } 223 | } 224 | } 225 | if 0 == len(addresses){ 226 | return nil, errors.New("no interface available") 227 | } 228 | return 229 | } -------------------------------------------------------------------------------- /daemon.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/sevlyar/go-daemon" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "syscall" 12 | ) 13 | 14 | type DaemonizedService interface { 15 | Start() (output string, err error) 16 | Stop() (output string, err error) 17 | Snapshot() (output string, err error) 18 | } 19 | 20 | type asyncOperateResult struct { 21 | Error string `json:"error,omitempty"` 22 | Output string `json:"output,omitempty"` 23 | } 24 | 25 | type ConfigGenerator func(workingPath string) (err error) 26 | type ServiceGenerator func(workingPath string) (service DaemonizedService, err error) 27 | 28 | var ( 29 | pipFileName string 30 | daemonizedService DaemonizedService 31 | ) 32 | 33 | func ProcessDaemon(executeName string, configGenerator ConfigGenerator, serviceGenerator ServiceGenerator) { 34 | const ( 35 | ValidArguesCount = 2 36 | StartCommand = "start" 37 | StopCommand = "stop" 38 | StatusCommand = "status" 39 | HaltCommand = "halt" 40 | SnapshotCommand = "snap" 41 | LogPathName = "log" 42 | ) 43 | if len(os.Args) != ValidArguesCount { 44 | printUsage(executeName) 45 | return 46 | } 47 | workingPath, err := getWorkingPath() 48 | if err != nil { 49 | fmt.Printf("get working path fail: %s\n", err.Error()) 50 | return 51 | } 52 | var command = os.Args[1] 53 | var pidFileName = filepath.Join(workingPath, fmt.Sprintf("%s.pid", executeName)) 54 | if "" == pipFileName { 55 | pipFileName = filepath.Join(workingPath, fmt.Sprintf("%s.pip", executeName)) 56 | } 57 | context := &daemon.Context{ 58 | PidFileName: pidFileName, 59 | PidFilePerm: daemon.FILE_PERM, 60 | } 61 | 62 | daemon.AddCommand(daemon.StringFlag(&command, StopCommand), syscall.SIGTERM, onStopDaemon) 63 | daemon.AddCommand(daemon.StringFlag(&command, SnapshotCommand), syscall.SIGUSR1, onDaemonSnapshot) 64 | daemon.AddCommand(daemon.StringFlag(&command, HaltCommand), syscall.SIGKILL, nil) 65 | 66 | switch command { 67 | case StopCommand: 68 | if process, err := context.Search(); err != nil || nil == process { 69 | fmt.Printf("%s is already stopped\n", executeName) 70 | return 71 | } else if !isRunning(process) { 72 | fmt.Printf("%s is already stopped (process %d not running)\n", executeName, process.Pid) 73 | } else { 74 | defer os.Remove(pipFileName) 75 | if err = createPipe(pipFileName); err != nil { 76 | fmt.Printf("open pipe fail: %s\n", err.Error()) 77 | return 78 | } 79 | go daemon.SendCommands(process) 80 | 81 | msg, err := readPipe(pipFileName) 82 | if err != nil { 83 | fmt.Printf("stop %s fail: %s\n", executeName, err.Error()) 84 | } else if "" != msg { 85 | fmt.Println(msg) 86 | fmt.Printf("stop %s success\n", executeName) 87 | } else { 88 | fmt.Printf("stop %s success\n", executeName) 89 | } 90 | } 91 | return 92 | case SnapshotCommand: 93 | var process *os.Process 94 | if process, err = context.Search(); err != nil || nil == process { 95 | fmt.Printf("%s is already stopped\n", executeName) 96 | return 97 | } else if !isRunning(process) { 98 | fmt.Printf("%s is already stopped (process %d not running)\n", executeName, process.Pid) 99 | } else { 100 | defer os.Remove(pipFileName) 101 | if err = createPipe(pipFileName); err != nil { 102 | fmt.Printf("open pipe fail: %s\n", err.Error()) 103 | return 104 | } 105 | go daemon.SendCommands(process) 106 | var msg string 107 | msg, err = readPipe(pipFileName) 108 | if err != nil { 109 | fmt.Printf("capture snapshot of %s fail: %s\n", executeName, err.Error()) 110 | } else if "" != msg { 111 | fmt.Println(msg) 112 | fmt.Printf("capture snapshot of %s success\n", executeName) 113 | } else { 114 | fmt.Printf("capture snapshot of %s success\n", executeName) 115 | } 116 | } 117 | return 118 | case HaltCommand: 119 | if process, err := context.Search(); err != nil || nil == process { 120 | fmt.Printf("%s is already stopped\n", executeName) 121 | } else if !isRunning(process) { 122 | fmt.Printf("%s is already stopped (process %d not running)\n", executeName, process.Pid) 123 | } else { 124 | daemon.SendCommands(process) 125 | } 126 | return 127 | case StatusCommand: 128 | process, err := context.Search() 129 | if err != nil || nil == process { 130 | fmt.Printf("%s is stopped\n", executeName) 131 | } else if !isRunning(process) { 132 | fmt.Printf("%s is stopped (pid %d)\n", executeName, process.Pid) 133 | } else { 134 | fmt.Printf("%s is running, current pid %d\n", executeName, process.Pid) 135 | } 136 | return 137 | case StartCommand: 138 | if err := configGenerator(workingPath); err != nil { 139 | fmt.Printf("generate config fail: %s\n", err.Error()) 140 | return 141 | } 142 | if process, err := context.Search(); err == nil && nil != process { 143 | if isRunning(process) { 144 | fmt.Printf("%s is already running\n", executeName) 145 | return 146 | } 147 | } 148 | if _, err = context.Reborn(); err != nil { 149 | fmt.Printf("create daemon fail:%s\n", err.Error()) 150 | return 151 | } 152 | //parent or child 153 | if !daemon.WasReborn() { 154 | //parent 155 | msg, err := readMessageFromPipe(pipFileName) 156 | if err != nil { 157 | fmt.Printf("start %s fail: %s\n", executeName, err.Error()) 158 | } else { 159 | fmt.Println(msg) 160 | fmt.Printf("%s started\n", executeName) 161 | } 162 | return 163 | 164 | } else { 165 | //child 166 | defer os.Remove(pidFileName) 167 | var logPath = filepath.Join(workingPath, LogPathName) 168 | if err = redirectLog(executeName, logPath); err != nil { 169 | notifyErrorToPipe(pipFileName, err.Error()) 170 | log.Printf("redirect log fail: %s", err.Error()) 171 | return 172 | } 173 | daemonizedService, err = serviceGenerator(workingPath) 174 | if err != nil { 175 | log.Printf("generate service fail: %s", err.Error()) 176 | notifyErrorToPipe(pipFileName, err.Error()) 177 | return 178 | } 179 | msg, err := daemonizedService.Start() 180 | if err != nil { 181 | log.Printf("start service fail: %s", err.Error()) 182 | notifyErrorToPipe(pipFileName, err.Error()) 183 | } else { 184 | notifyMessageToPipe(pipFileName, msg) 185 | daemon.ServeSignals() 186 | } 187 | return 188 | } 189 | default: 190 | printUsage(executeName) 191 | } 192 | } 193 | 194 | func onStopDaemon(sig os.Signal) error { 195 | if nil == daemonizedService { 196 | log.Println("invalid daemon service") 197 | return daemon.ErrStop 198 | } 199 | if "" == pipFileName { 200 | log.Println("invalid pipe file") 201 | return daemon.ErrStop 202 | } 203 | msg, err := daemonizedService.Stop() 204 | if err != nil { 205 | log.Printf("stop service fail: %s", err.Error()) 206 | notifyErrorToPipe(pipFileName, err.Error()) 207 | } else { 208 | notifyMessageToPipe(pipFileName, msg) 209 | } 210 | return daemon.ErrStop 211 | } 212 | 213 | func onDaemonSnapshot(sig os.Signal) error { 214 | if nil == daemonizedService { 215 | log.Println("invalid daemon service") 216 | return daemon.ErrStop 217 | } 218 | if "" == pipFileName { 219 | log.Println("invalid pipe file") 220 | return daemon.ErrStop 221 | } 222 | msg, err := daemonizedService.Snapshot() 223 | if err != nil { 224 | log.Printf("invoke snapshot fail: %s", err.Error()) 225 | notifyErrorToPipe(pipFileName, err.Error()) 226 | } else { 227 | notifyMessageToPipe(pipFileName, msg) 228 | } 229 | return nil 230 | } 231 | 232 | func readMessageFromPipe(pipeName string) (message string, err error) { 233 | defer os.Remove(pipeName) 234 | if err = createPipe(pipeName); err != nil { 235 | return 236 | } 237 | message, err = readPipe(pipeName) 238 | return 239 | } 240 | 241 | func createPipe(pipeName string) (err error) { 242 | const ( 243 | PipeFilePerm = 0600 244 | ) 245 | if _, err = os.Stat(pipeName); !os.IsNotExist(err) { 246 | os.Remove(pipeName) 247 | } 248 | if err = syscall.Mkfifo(pipeName, PipeFilePerm); err != nil { 249 | return 250 | } 251 | 252 | return 253 | } 254 | 255 | func readPipe(pipeName string) (message string, err error) { 256 | const ( 257 | PipeFilePerm = 0600 258 | ) 259 | var pipe *os.File 260 | pipe, err = os.OpenFile(pipeName, os.O_RDONLY, PipeFilePerm) 261 | if err != nil { 262 | return 263 | } 264 | defer pipe.Close() 265 | 266 | var data = make([]byte, 1<<10) 267 | var n int 268 | n, err = pipe.Read(data) 269 | if err != nil { 270 | return 271 | } 272 | var result asyncOperateResult 273 | if err = json.Unmarshal(data[:n], &result); err != nil { 274 | 275 | return "", fmt.Errorf("unmarshal fail: %s, data %s", err.Error(), data[:n]) 276 | } 277 | if result.Error != "" { 278 | err = errors.New(result.Error) 279 | } else { 280 | message = result.Output 281 | } 282 | return 283 | } 284 | 285 | func notifyMessageToPipe(pipeName, message string) (err error) { 286 | const ( 287 | PipeFilePerm = 0600 288 | ) 289 | pip, err := os.OpenFile(pipeName, os.O_RDWR, PipeFilePerm) 290 | if err != nil { 291 | return 292 | } 293 | defer pip.Close() 294 | var result = asyncOperateResult{Output: message} 295 | data, err := json.MarshalIndent(result, "", " ") 296 | if err != nil { 297 | return 298 | } 299 | _, err = pip.Write(data) 300 | return 301 | } 302 | 303 | func notifyErrorToPipe(pipeName, message string) (err error) { 304 | const ( 305 | PipeFilePerm = 0600 306 | ) 307 | pip, err := os.OpenFile(pipeName, os.O_RDWR, PipeFilePerm) 308 | if err != nil { 309 | return 310 | } 311 | defer pip.Close() 312 | var result = asyncOperateResult{Error: message} 313 | data, err := json.MarshalIndent(result, "", " ") 314 | if err != nil { 315 | return 316 | } 317 | _, err = pip.Write(data) 318 | return 319 | } 320 | 321 | func getWorkingPath() (path string, err error) { 322 | executable, err := os.Executable() 323 | if err != nil { 324 | return "", err 325 | } 326 | return filepath.Abs(filepath.Dir(executable)) 327 | } 328 | 329 | func isRunning(process *os.Process) bool { 330 | if nil == process { 331 | return false 332 | } 333 | if err := process.Signal(syscall.Signal(0)); err == nil { 334 | return true 335 | } else { 336 | return false 337 | } 338 | } 339 | 340 | func redirectLog(executeName, logPathName string) (err error) { 341 | const ( 342 | DefaultLogPathPerm = 0740 343 | ) 344 | if _, err = os.Stat(logPathName); os.IsNotExist(err) { 345 | //create path 346 | err = os.Mkdir(logPathName, DefaultLogPathPerm) 347 | if err != nil { 348 | return err 349 | } 350 | fmt.Printf("log path %s created\n", logPathName) 351 | } 352 | var filename = fmt.Sprintf("%s/%s.log", logPathName, executeName) 353 | output, err := os.Create(filename) 354 | if err != nil { 355 | return err 356 | } 357 | fmt.Printf("log redirected to '%s'\n", filename) 358 | log.SetOutput(output) 359 | return nil 360 | } 361 | 362 | func printUsage(executeName string) { 363 | fmt.Printf("Usage: %s [start|stop|status|halt|snap]\n", executeName) 364 | } 365 | -------------------------------------------------------------------------------- /endpoint.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "github.com/project-nano/sonar" 5 | "net" 6 | "fmt" 7 | "errors" 8 | "github.com/xtaci/kcp-go" 9 | "log" 10 | "time" 11 | ) 12 | 13 | //need overwriting 14 | type ServiceHandler interface { 15 | OnMessageReceived(msg Message) 16 | OnServiceConnected(name string, t ServiceType, address string) 17 | OnServiceDisconnected(name string, t ServiceType, gracefully bool) 18 | OnDependencyReady() 19 | InitialEndpoint() error 20 | OnEndpointStarted() error 21 | OnEndpointStopped() 22 | } 23 | 24 | type MessageSender interface { 25 | SendMessage(msg Message, target string) error 26 | SendToSelf(msg Message) error 27 | } 28 | 29 | type EndpointService struct { 30 | isPeer bool 31 | isReady bool 32 | groupListener *sonar.Listener 33 | groupPinger *sonar.Pinger 34 | fixedListenAddress string 35 | connectionListener *kcp.Listener 36 | connectionMap map[string]connEntry 37 | connEventChan chan connEvent 38 | incomingMessageChan chan Message 39 | guardianNotifyChan chan bool 40 | guardianFinishChan chan bool 41 | status serviceStatus 42 | submoduleChannel map[string]chan Message 43 | listenAddress string 44 | listenPort int 45 | name string 46 | serviceType ServiceType 47 | domain string 48 | groupAddress string 49 | groupPort int 50 | stubAvailable bool 51 | recoveringStub bool 52 | handler ServiceHandler 53 | } 54 | 55 | const ( 56 | ListenPortRangeStart = 5600 57 | ListenPortRangeEnd = ListenPortRangeStart + 200 58 | DefaultDataShards = 10 59 | DefaultParityShards = 3 60 | DefaultBufferSize = 2 << 20 //8KiB 61 | DefaultMessageQueueSize = 1 << 10 62 | ) 63 | 64 | type serviceStatus int 65 | 66 | const ( 67 | serviceStatusStopped = iota 68 | serviceStatusStopping 69 | serviceStatusRunning 70 | ) 71 | 72 | func CreateStubEndpoint(groupAddress string, groupPort int, domain, listenAddress string) (endpoint EndpointService, err error) { 73 | listenInterface, err := getInterfaceByAddress(listenAddress) 74 | if err != nil{ 75 | return 76 | } 77 | listener, err := sonar.CreateListener(groupAddress, groupPort, domain, listenInterface) 78 | if err != nil { 79 | return endpoint, err 80 | } 81 | return EndpointService{isPeer: false, groupListener: listener, fixedListenAddress: listenAddress, 82 | domain: domain, groupAddress: groupAddress, groupPort: groupPort, 83 | status: serviceStatusStopped, submoduleChannel:map[string]chan Message{}, stubAvailable: false, recoveringStub: false}, nil 84 | } 85 | 86 | func CreatePeerEndpoint(groupAddress string, groupPort int, domain string) (endpoint EndpointService, err error) { 87 | pinger, err := sonar.CreatePinger(groupAddress, groupPort, domain) 88 | if err != nil { 89 | return endpoint, err 90 | } 91 | return EndpointService{isPeer: true, groupPinger: pinger, status: serviceStatusStopped, submoduleChannel:map[string]chan Message{}, 92 | domain: domain, groupAddress: groupAddress, groupPort: groupPort, stubAvailable: false, recoveringStub: false}, nil 93 | } 94 | 95 | func (endpoint *EndpointService)RegisterSubmodule(name string, channel chan Message) error{ 96 | if _, exists := endpoint.submoduleChannel[name];exists{ 97 | return fmt.Errorf("submodule '%s' already exists", name) 98 | } 99 | endpoint.submoduleChannel[name] = channel 100 | return nil 101 | } 102 | 103 | func (endpoint *EndpointService) RegisterHandler(h ServiceHandler){ 104 | endpoint.handler = h 105 | } 106 | 107 | func getInterfaceByAddress(address string) (i *net.Interface, err error){ 108 | list, err := net.Interfaces() 109 | if err != nil{ 110 | return 111 | } 112 | if 0 == len(list){ 113 | err = errors.New("no interface available") 114 | return 115 | } 116 | for _, inf := range list{ 117 | if addreses, err := inf.Addrs();err == nil{ 118 | for _, target := range addreses{ 119 | if ip, _, err := net.ParseCIDR(target.String());err == nil{ 120 | if ip.String() == address{ 121 | i = &inf 122 | return i, nil 123 | } 124 | } 125 | } 126 | } 127 | } 128 | return nil, fmt.Errorf("no interface has address '%s'", address) 129 | } 130 | // 131 | //func selectDefaultInterface() (i net.Interface, err error) { 132 | // interfaceList, err := net.Interfaces() 133 | // if err != nil { 134 | // return i, err 135 | // } 136 | // var checkFlag = net.FlagMulticast | net.FlagPointToPoint | net.FlagUp 137 | // for _, i = range interfaceList { 138 | // if i.Flags&net.FlagLoopback != 0 { 139 | // //ignore loopback 140 | // continue 141 | // } 142 | // if i.Flags&checkFlag != 0 { 143 | // return i, nil 144 | // } 145 | // } 146 | // return i, errors.New("no interface available") 147 | //} 148 | 149 | func InterfaceByAddress(address string) (inf *net.Interface, err error){ 150 | interfaceList, err := net.Interfaces() 151 | if err != nil { 152 | return 153 | } 154 | var checkFlag = net.FlagMulticast | net.FlagPointToPoint | net.FlagUp 155 | var addressList []net.Addr 156 | var interfaceIP net.IP 157 | for _, currentInterface := range interfaceList { 158 | if currentInterface.Flags&net.FlagLoopback != 0 { 159 | //ignore 160 | continue 161 | } 162 | if currentInterface.Flags&checkFlag != 0 { 163 | addressList, err = currentInterface.Addrs() 164 | if err != nil { 165 | return 166 | } 167 | if len(addressList) == 0 { 168 | //no address available 169 | continue 170 | } 171 | 172 | for _, addr := range addressList { 173 | interfaceIP, _, err = net.ParseCIDR(addr.String()) 174 | if err != nil { 175 | return 176 | } 177 | if interfaceIP.To4() != nil { 178 | //v4 179 | if interfaceIP.String() == address{ 180 | //equal 181 | return ¤tInterface, nil 182 | } 183 | } 184 | } 185 | } 186 | } 187 | err = fmt.Errorf("no interface with address '%s'", address) 188 | return 189 | } 190 | 191 | func (endpoint *EndpointService) GenerateName(t ServiceType, i *net.Interface) error { 192 | const hexDigit = "0123456789abcdef" 193 | var prefix string 194 | switch t { 195 | case ServiceTypeCore: 196 | prefix = "Core" 197 | break 198 | case ServiceTypeCell: 199 | prefix = "Cell" 200 | break 201 | case ServiceTypeImage: 202 | prefix = "Image" 203 | break 204 | default: 205 | return fmt.Errorf("unsupported service type %d", t) 206 | } 207 | var buf []byte 208 | for _, b := range i.HardwareAddr { 209 | buf = append(buf, hexDigit[b>>4]) 210 | buf = append(buf, hexDigit[b&0xF]) 211 | } 212 | endpoint.serviceType = t 213 | endpoint.name = fmt.Sprintf("%s_%s", prefix, string(buf)) 214 | return nil 215 | 216 | } 217 | 218 | func (endpoint *EndpointService) isRunning() bool { 219 | return endpoint.status == serviceStatusRunning 220 | } 221 | 222 | func (endpoint *EndpointService) isStopped() bool { 223 | return endpoint.status == serviceStatusStopped 224 | } 225 | 226 | func (endpoint *EndpointService) Start() error { 227 | if !endpoint.isStopped() { 228 | return errors.New("endpoint not stopped") 229 | } 230 | if err := endpoint.handler.InitialEndpoint(); err != nil { 231 | return err 232 | } 233 | var err error 234 | if endpoint.isPeer { 235 | err = endpoint.startPeerService() 236 | } else { 237 | err = endpoint.startCoreService() 238 | } 239 | if err != nil { 240 | return err 241 | } 242 | endpoint.status = serviceStatusRunning 243 | return nil 244 | } 245 | 246 | func (endpoint *EndpointService) Stop() error { 247 | if !endpoint.isRunning() { 248 | return errors.New("endpoint not running") 249 | } 250 | endpoint.status = serviceStatusStopping 251 | endpoint.handler.OnEndpointStopped() 252 | if err := endpoint.connectionListener.Close(); err != nil { 253 | endpoint.status = serviceStatusStopped 254 | return err 255 | } 256 | endpoint.guardianNotifyChan <- true 257 | <-endpoint.guardianFinishChan 258 | close(endpoint.connEventChan) 259 | close(endpoint.incomingMessageChan) 260 | endpoint.status = serviceStatusStopped 261 | return nil 262 | } 263 | 264 | //all dependent service must ready before prepare local service 265 | func (endpoint *EndpointService) AddDependency(dependencies []ServiceType) { 266 | panic("not implement") 267 | } 268 | 269 | func (endpoint *EndpointService) SetServiceReady() { 270 | panic("not implement") 271 | } 272 | 273 | func (endpoint *EndpointService) GetListenAddress() string{ 274 | return endpoint.listenAddress 275 | } 276 | 277 | func (endpoint *EndpointService) GetListenPort() int{ 278 | return endpoint.listenPort 279 | } 280 | 281 | func (endpoint *EndpointService) GetName() string{ 282 | return endpoint.name 283 | } 284 | 285 | func (endpoint *EndpointService) GetDomain() string{ 286 | return endpoint.domain 287 | } 288 | 289 | func (endpoint *EndpointService) GetServiceType() ServiceType{ 290 | return endpoint.serviceType 291 | } 292 | 293 | func (endpoint *EndpointService) GetGroupAddress() string{ 294 | return endpoint.groupAddress 295 | } 296 | 297 | func (endpoint *EndpointService) GetGroupPort() int{ 298 | return endpoint.groupPort 299 | } 300 | //private functions 301 | func (endpoint *EndpointService) startCoreService() error { 302 | listener, listenPort, err := selectAvailablePort(endpoint.fixedListenAddress) 303 | if err != nil { 304 | return err 305 | } 306 | if err = endpoint.groupListener.AddService(ServiceTypeStringCore, "kcp", endpoint.fixedListenAddress, listenPort); err != nil { 307 | return err 308 | } 309 | log.Printf(" service %s published for %s:%d", endpoint.name, endpoint.fixedListenAddress, listenPort) 310 | if err = endpoint.groupListener.Start(); err != nil { 311 | return err 312 | } 313 | endpoint.listenAddress = endpoint.fixedListenAddress 314 | endpoint.listenPort = listenPort 315 | return endpoint.startRoutine(listener) 316 | } 317 | 318 | func (endpoint *EndpointService) startPeerService() error { 319 | const ( 320 | DefaultQueryDuration = 5*time.Second 321 | ) 322 | echo, err := endpoint.groupPinger.Query(DefaultQueryDuration) 323 | if err != nil { 324 | return err 325 | } 326 | //select first service 327 | for _, service := range echo.Services { 328 | if ServiceTypeStringCore != service.Type { 329 | log.Printf(" warning:invalid service type `%s` in echo response", service.Type) 330 | continue 331 | } 332 | //create listener 333 | listener, listenPort, err := selectAvailablePort(echo.LocalAddress) 334 | if err != nil { 335 | return err 336 | } 337 | //start routine 338 | log.Printf(" %s listen at %s:%d", endpoint.name, echo.LocalAddress, listenPort) 339 | endpoint.listenPort = listenPort 340 | endpoint.listenAddress = echo.LocalAddress 341 | if err = endpoint.startRoutine(listener); err != nil { 342 | return err 343 | } 344 | //connect service 345 | return endpoint.connectRemoteService(service.Address, service.Port) 346 | } 347 | return errors.New("no service available") 348 | } 349 | 350 | func (endpoint *EndpointService) SendMessage(msg Message, target string) error { 351 | if !endpoint.isRunning() { 352 | return errors.New("endpoint closed") 353 | } 354 | if target == endpoint.name{ 355 | return endpoint.SendToSelf(msg) 356 | } 357 | //inner submodule first 358 | channel, exists := endpoint.submoduleChannel[target] 359 | if exists{ 360 | channel <- msg 361 | return nil 362 | } 363 | entry, exists := endpoint.connectionMap[target] 364 | if !exists { 365 | return fmt.Errorf("invalid target '%s'", target) 366 | } 367 | entry.OutgoingChan <- msg 368 | return nil 369 | } 370 | 371 | func (endpoint *EndpointService) SendToSelf(msg Message) error { 372 | if "" == msg.GetSender(){ 373 | msg.SetSender(endpoint.name) 374 | } 375 | endpoint.incomingMessageChan <- msg 376 | return nil 377 | } 378 | 379 | func (endpoint *EndpointService) startRoutine(listener *kcp.Listener) error { 380 | endpoint.connectionListener = listener 381 | endpoint.connEventChan = make(chan connEvent, DefaultMessageQueueSize) 382 | endpoint.incomingMessageChan = make(chan Message, DefaultMessageQueueSize) 383 | endpoint.guardianNotifyChan = make(chan bool, 1) 384 | endpoint.guardianFinishChan = make(chan bool, 1) 385 | endpoint.connectionMap = map[string]connEntry{} 386 | go endpoint.listenRoutine() 387 | go endpoint.guardianRoutine() 388 | go endpoint.mainRoutine() 389 | return endpoint.handler.OnEndpointStarted() 390 | } 391 | 392 | func (endpoint *EndpointService) listenRoutine() { 393 | //listen&accept 394 | for { 395 | session, err := endpoint.connectionListener.AcceptKCP() 396 | if err != nil { 397 | break 398 | } 399 | go endpoint.handleIncomingConnection(session) 400 | } 401 | } 402 | 403 | type connEvent struct { 404 | Event connEventType 405 | Name string 406 | Service ServiceType 407 | Address string 408 | Port int 409 | Gracefully bool 410 | Conn *kcp.UDPSession 411 | OutgoingChan chan Message 412 | FinishChan chan bool 413 | } 414 | 415 | type connEntry struct { 416 | Name string //remote name 417 | Type ServiceType 418 | Status connectionStatus 419 | LastHeartBeat time.Time 420 | Session *kcp.UDPSession 421 | OutgoingChan chan Message 422 | FinishChan chan bool 423 | } 424 | 425 | type connEventType int 426 | 427 | const ( 428 | ConnEventOpen = iota 429 | ConnEventClose 430 | ConnEventHeartBeat 431 | ) 432 | 433 | type connectionStatus int 434 | 435 | //Service status 436 | const ( 437 | connStatusDisconnected = iota 438 | connStatusConnected 439 | connStatusReady 440 | connStatusLost 441 | ) 442 | 443 | func (endpoint *EndpointService) guardianRoutine() { 444 | var exitFlag = false 445 | const ( 446 | CheckInterval = time.Second * 5 447 | KeepAliveInterval = time.Second * 3 448 | LostThresholdInterval = time.Second * 9 449 | DisconnectThresholdInterval = time.Second * 15 450 | ) 451 | var checkTicker = time.NewTicker(CheckInterval) 452 | var keepAliveTicker = time.NewTicker(KeepAliveInterval) 453 | 454 | for !exitFlag { 455 | select { 456 | case <-endpoint.guardianNotifyChan: 457 | //log.Print("exit guardian routine...") 458 | exitFlag = true 459 | break 460 | case event := <-endpoint.connEventChan: 461 | switch event.Event { 462 | case ConnEventOpen: 463 | { 464 | if _, exists := endpoint.connectionMap[event.Name]; exists { 465 | log.Printf(" connection to service '%s' already opened", event.Name) 466 | continue 467 | } 468 | endpoint.connectionMap[event.Name] = connEntry{event.Name, event.Service, connStatusConnected, 469 | time.Now(), event.Conn, event.OutgoingChan, event.FinishChan} 470 | log.Printf(" new connection '%s' opened", event.Name) 471 | if !endpoint.stubAvailable && (ServiceTypeCore == event.Service){ 472 | endpoint.stubAvailable = true 473 | } 474 | msg, err := CreateJsonMessage(ServiceConnectedEvent) 475 | if err != nil { 476 | log.Printf(" create message fail:%s", err.Error()) 477 | continue 478 | } 479 | msg.SetString(ParamKeyName, event.Name) 480 | msg.SetUInt(ParamKeyType, uint(event.Service)) 481 | msg.SetString(ParamKeyAddress, event.Address) 482 | if err = endpoint.SendToSelf(msg); err != nil { 483 | log.Printf(" notify connected event fail: %s", err.Error()) 484 | continue 485 | } 486 | 487 | } 488 | case ConnEventClose: 489 | entry, exists := endpoint.connectionMap[event.Name] 490 | if !exists { 491 | log.Printf(" service '%s' not exists", event.Name) 492 | continue 493 | } 494 | var serviceType = entry.Type 495 | delete(endpoint.connectionMap, event.Name) 496 | log.Printf(" connection '%s' closed", event.Name) 497 | if endpoint.isRunning()&&(ServiceTypeCore == serviceType) && endpoint.isPeer { 498 | //todo: verify multiple stub 499 | endpoint.stubAvailable = false 500 | go endpoint.recoverStubService() 501 | } 502 | msg, err := CreateJsonMessage(ServiceDisconnectedEvent) 503 | if err != nil { 504 | log.Printf(" create message fail:%s", err.Error()) 505 | continue 506 | } 507 | msg.SetString(ParamKeyName, event.Name) 508 | msg.SetUInt(ParamKeyType, uint(serviceType)) 509 | msg.SetBoolean(ParamKeyFlag, event.Gracefully) 510 | if err = endpoint.SendToSelf(msg); err != nil { 511 | log.Printf(" notify disconnected event fail: %s", err.Error()) 512 | continue 513 | } 514 | 515 | case ConnEventHeartBeat: 516 | entry, exists := endpoint.connectionMap[event.Name] 517 | if !exists { 518 | log.Printf(" invalid service '%s' for heartbeat", event.Name) 519 | continue 520 | } 521 | entry.LastHeartBeat = time.Now() 522 | entry.Status = connStatusConnected 523 | endpoint.connectionMap[event.Name] = entry 524 | 525 | default: 526 | log.Printf(" warning: invalid connection event type %d", event.Event) 527 | } 528 | break 529 | //keep alive 530 | case <-keepAliveTicker.C: 531 | keepAlive, err := CreateJsonMessage(ConnectionKeepAliveEvent) 532 | if err != nil { 533 | log.Printf(" warning: build keep alive message fail: %s", err.Error()) 534 | break 535 | } 536 | for name, entry := range endpoint.connectionMap { 537 | if entry.Status == connStatusConnected { 538 | //only send keep alive to connected serivce 539 | if err = endpoint.SendMessage(keepAlive, name); err != nil { 540 | log.Printf(" warning: send keep alive to '%s' fail: %s", name, err.Error()) 541 | } 542 | } 543 | } 544 | break 545 | //check timeout 546 | case <-checkTicker.C: 547 | var current = time.Now() 548 | for name, entry := range endpoint.connectionMap { 549 | if connStatusConnected == entry.Status { 550 | if entry.LastHeartBeat.Add(LostThresholdInterval).Before(current) { 551 | //timeout 552 | entry.Status = connStatusLost 553 | endpoint.connectionMap[name] = entry 554 | log.Printf(" service '%s' marked to lost", name) 555 | } 556 | } else if connStatusLost == entry.Status { 557 | if entry.LastHeartBeat.Add(DisconnectThresholdInterval).Before(current) { 558 | //timeout 559 | entry.Status = connStatusDisconnected 560 | endpoint.connectionMap[name] = entry 561 | log.Printf(" service '%s' marked to disconnect", name) 562 | if err := endpoint.disconnectRemoteService(name, entry); err != nil { 563 | log.Printf(" try disconnect lost service '%s' fail: %s", name, err.Error()) 564 | } 565 | } 566 | } 567 | } 568 | break 569 | } 570 | 571 | } 572 | checkTicker.Stop() 573 | keepAliveTicker.Stop() 574 | for name, entry := range endpoint.connectionMap { 575 | if err := endpoint.disconnectRemoteService(name, entry);err != nil{ 576 | log.Printf(" disconnect service '%s' fail when stop: %s", name, err.Error()) 577 | } 578 | } 579 | endpoint.guardianFinishChan <- true 580 | 581 | } 582 | 583 | func (endpoint *EndpointService) mainRoutine() { 584 | //handle incoming message 585 | for msg := range endpoint.incomingMessageChan { 586 | if !endpoint.isRunning() { 587 | break 588 | } 589 | switch msg.GetID() { 590 | case ServiceAvailableEvent, ServiceReadyEvent, ServiceConnectedEvent, ServiceDisconnectedEvent: 591 | endpoint.handleSystemMessage(msg) 592 | default: 593 | endpoint.handler.OnMessageReceived(msg) 594 | } 595 | } 596 | } 597 | 598 | 599 | func (endpoint *EndpointService) handleSystemMessage(msg Message) { 600 | switch msg.GetID() { 601 | case ServiceConnectedEvent: 602 | serviceName, err := msg.GetString(ParamKeyName) 603 | if err != nil { 604 | log.Printf(" get name fail:%s", err.Error()) 605 | return 606 | } 607 | serviceType, err := msg.GetUInt(ParamKeyType) 608 | if err != nil { 609 | log.Printf(" get type fail:%s", err.Error()) 610 | return 611 | } 612 | remoteAddress, err := msg.GetString(ParamKeyAddress) 613 | if err != nil{ 614 | log.Printf(" get remote address fail: %s", err.Error()) 615 | return 616 | } 617 | endpoint.handler.OnServiceConnected(serviceName, ServiceType(serviceType), remoteAddress) 618 | return 619 | case ServiceDisconnectedEvent: 620 | serviceName, err := msg.GetString(ParamKeyName) 621 | if err != nil { 622 | log.Printf(" get name fail:%s", err.Error()) 623 | return 624 | } 625 | serviceType, err := msg.GetUInt(ParamKeyType) 626 | if err != nil { 627 | log.Printf(" get type fail:%s", err.Error()) 628 | return 629 | } 630 | gracefully, _ := msg.GetBoolean(ParamKeyFlag) 631 | endpoint.handler.OnServiceDisconnected(serviceName, ServiceType(serviceType), gracefully) 632 | return 633 | } 634 | } 635 | 636 | func (endpoint *EndpointService) handleIncomingConnection(session *kcp.UDPSession) { 637 | //receiver 638 | //read remote service info 639 | //send local service info 640 | var remoteAddress = session.RemoteAddr().(*net.UDPAddr) 641 | serviceName, serviceType, err := receiveRemoteServiceInfo(session) 642 | if err != nil { 643 | session.Close() 644 | log.Printf(" get service info fail:%s", err.Error()) 645 | return 646 | } 647 | var outgoingChan = make(chan Message, DefaultMessageQueueSize) 648 | var finishChan = make(chan bool, 1) 649 | var remoteIP = remoteAddress.IP.String() 650 | log.Printf(" new service '%s' (type %d) connected from %s:%d", serviceName, serviceType, remoteIP, remoteAddress.Port) 651 | endpoint.connEventChan <- connEvent{ConnEventOpen, serviceName, serviceType, 652 | remoteIP, remoteAddress.Port, false, session, outgoingChan, finishChan} 653 | //notify remote service 654 | if err = sendServiceInfo(session, endpoint.name, endpoint.serviceType); err != nil { 655 | session.Close() 656 | log.Printf(" notify service info fail:%s", err.Error()) 657 | return 658 | } 659 | //start routine 660 | go sessionServeRoutine(serviceName, session, endpoint.incomingMessageChan, outgoingChan, finishChan, endpoint.connEventChan) 661 | } 662 | 663 | func (endpoint *EndpointService) connectRemoteService(address string, port int) error { 664 | //sender: 665 | //send local service info 666 | //read remote service info 667 | var target = fmt.Sprintf("%s:%d", address, port) 668 | session, err := kcp.DialWithOptions(target, nil, DefaultDataShards, DefaultParityShards) 669 | if err != nil { 670 | return err 671 | } 672 | if err = sendServiceInfo(session, endpoint.name, endpoint.serviceType); err != nil { 673 | session.Close() 674 | return err 675 | } 676 | remoteName, remoteType, err := receiveRemoteServiceInfo(session) 677 | if err != nil { 678 | session.Close() 679 | return err 680 | } 681 | 682 | var outgoingChan = make(chan Message, DefaultMessageQueueSize) 683 | var finishChan = make(chan bool,1 ) 684 | log.Printf(" remote service '%s' (type %d/ address %s) connected", remoteName, remoteType, target) 685 | endpoint.connEventChan <- connEvent{ConnEventOpen, remoteName, remoteType, 686 | address, port, true, session, outgoingChan, finishChan} 687 | //start routine 688 | go sessionServeRoutine(remoteName, session, endpoint.incomingMessageChan, outgoingChan, finishChan, endpoint.connEventChan) 689 | return nil 690 | } 691 | 692 | func (endpoint *EndpointService) disconnectRemoteService(name string, entry connEntry) (err error) { 693 | event, err := CreateJsonMessage(ConnectionClosedEvent) 694 | if err != nil{ 695 | return err 696 | } 697 | data, err := event.Serialize() 698 | if err != nil{ 699 | return err 700 | } 701 | //send disconnect event 702 | if _, err = entry.Session.Write(data);err != nil{ 703 | return err 704 | } 705 | if err = entry.Session.Close(); err != nil { 706 | return err 707 | } 708 | const ( 709 | stopTimeout = 3*time.Second 710 | ) 711 | timer := time.NewTimer(stopTimeout) 712 | select { 713 | case <- timer.C: 714 | err = errors.New("wait session routine finish timeout") 715 | return err 716 | case <- entry.FinishChan: 717 | //finished 718 | } 719 | return nil 720 | } 721 | 722 | func (endpoint *EndpointService) recoverStubService(){ 723 | if endpoint.recoveringStub{ 724 | log.Println(" recovery already in processing") 725 | return 726 | } 727 | endpoint.recoveringStub = true 728 | const ( 729 | retryInterval = 3*time.Second 730 | queryTimeout = 5*time.Second 731 | ) 732 | defer func() {endpoint.recoveringStub = false}() 733 | var err error 734 | var echo sonar.Echo 735 | if err != nil{ 736 | log.Printf(" create recover pinger fail: %s", err.Error()) 737 | return 738 | } 739 | for endpoint.isRunning(){ 740 | time.Sleep(retryInterval) 741 | if endpoint.stubAvailable{ 742 | log.Println(" stub service already recovered") 743 | break 744 | } 745 | log.Println(" try recover stub service...") 746 | pinger, err := sonar.CreatePinger(endpoint.groupAddress, endpoint.groupPort, endpoint.domain) 747 | echo, err = pinger.Query(queryTimeout) 748 | if err != nil{ 749 | log.Printf(" recover fail: %s", err.Error()) 750 | continue 751 | } 752 | if 0 == len(echo.Services){ 753 | log.Println(" requery success, but no stub available") 754 | continue 755 | } 756 | var stub = echo.Services[0] 757 | err = endpoint.connectRemoteService(stub.Address, stub.Port) 758 | if err != nil{ 759 | log.Printf(" connect stub %s:%d fail: %s", stub.Address, stub.Port, err.Error()) 760 | continue 761 | } 762 | log.Printf(" new stub %s:%d recovered", stub.Address, stub.Port) 763 | break 764 | } 765 | 766 | } 767 | 768 | func selectAvailablePort(host string) (*kcp.Listener, int, error) { 769 | var address string 770 | for port := ListenPortRangeStart; port < ListenPortRangeEnd; port++ { 771 | address = fmt.Sprintf("%s:%d", host, port) 772 | listener, err := kcp.ListenWithOptions(address, nil, DefaultDataShards, DefaultParityShards) 773 | if err != nil { 774 | continue 775 | } 776 | return listener, port, nil 777 | } 778 | return nil, 0, fmt.Errorf("no port available in range %d ~ %d", ListenPortRangeStart, ListenPortRangeEnd) 779 | } 780 | 781 | func receiveRemoteServiceInfo(session *kcp.UDPSession) (string, ServiceType, error) { 782 | var buf = make([]byte, DefaultBufferSize) 783 | //recv connect open 784 | length, err := session.Read(buf) 785 | if err != nil { 786 | return "", 0, err 787 | } 788 | msg, err := MessageFromJson(buf[:length]) 789 | if err != nil { 790 | return "", 0, err 791 | } 792 | if msg.GetID() != ConnectionOpenedEvent { 793 | return "", 0, fmt.Errorf("invalid message %d", msg.GetID()) 794 | } 795 | serviceName, err := msg.GetString(ParamKeyName) 796 | if err != nil { 797 | return "", 0, errors.New("can not get service name") 798 | } 799 | serviceType, err := msg.GetUInt(ParamKeyType) 800 | if err != nil { 801 | return "", 0, errors.New("can not get service type") 802 | } 803 | return serviceName, ServiceType(serviceType), nil 804 | } 805 | 806 | func sendServiceInfo(session *kcp.UDPSession, serviceName string, serviceType ServiceType) error { 807 | notify, err := CreateJsonMessage(ConnectionOpenedEvent) 808 | if err != nil { 809 | return err 810 | } 811 | notify.SetString(ParamKeyName, serviceName) 812 | notify.SetUInt(ParamKeyType, uint(serviceType)) 813 | packet, err := notify.Serialize() 814 | if err != nil { 815 | return err 816 | } 817 | _, err = session.Write(packet) 818 | return err 819 | } 820 | 821 | func sessionServeRoutine(remote string, session *kcp.UDPSession, incomingChan chan Message, 822 | outgoingChan chan Message, finishChan chan bool, eventChan chan connEvent) { 823 | //log.Printf(" receive routine for '%s' started", remote) 824 | var gracefullyClose = false 825 | var buf = make([]byte, DefaultBufferSize) 826 | var sendStopChan = make(chan bool, 1) 827 | var sendExitChan = make(chan bool, 1) 828 | go sessionOutgoingRoutine(remote, session, outgoingChan, sendStopChan, sendExitChan) 829 | var bufStart, bufEnd = 0, 0 830 | for { 831 | //recv connect open 832 | count, err := session.Read(buf[bufStart:]) 833 | if err != nil { 834 | log.Printf(" warning: connection lost from %s : %s", remote, err.Error()) 835 | break 836 | } 837 | bufEnd = bufStart + count 838 | if bufEnd > DefaultBufferSize{ 839 | bufStart = 0 840 | log.Printf(" warning: discard cached data because buffer overflow from %s", remote) 841 | continue 842 | } 843 | msg, err := MessageFromJson(buf[:bufEnd]) 844 | if err != nil { 845 | //need cache 846 | bufStart = bufEnd 847 | log.Printf(" warning: cache %d byte(s) from %s", count, remote) 848 | //log.Printf(" warning: parse message fail: %s, data(%d byte(s)): %s", err.Error(), count, buf[:count]) 849 | continue 850 | } 851 | bufStart = 0 852 | if msg.GetID() == ConnectionKeepAliveEvent { 853 | eventChan <- connEvent{Event: ConnEventHeartBeat, Name: remote} 854 | continue 855 | }else if msg.GetID() == ConnectionClosedEvent{ 856 | gracefullyClose = true 857 | log.Printf(" connection closed by remote endpoint '%s'", remote) 858 | break 859 | } 860 | if "" == msg.GetSender() { 861 | msg.SetSender(remote) 862 | } 863 | incomingChan <- msg 864 | } 865 | //closing outgoing routine 866 | sendStopChan <- true 867 | <-sendExitChan 868 | //notify closed 869 | eventChan <- connEvent{Event: ConnEventClose, Name: remote, Gracefully: gracefullyClose} 870 | finishChan <- true 871 | //log.Printf(" receive routine for '%s' stopped", remote) 872 | } 873 | 874 | func sessionOutgoingRoutine(remote string, session *kcp.UDPSession, outgoingChan chan Message, 875 | notify, stopped chan bool) { 876 | //log.Printf(" send routine for '%s' started", remote) 877 | var exitFlag = false 878 | for !exitFlag { 879 | select { 880 | case msg := <-outgoingChan: 881 | data, err := msg.Serialize() 882 | if err != nil { 883 | log.Printf(" serial outgoing message fail: %s", err.Error()) 884 | break 885 | } 886 | if _, err = session.Write(data); err != nil { 887 | log.Printf(" outgoing message to '%s' fail: %s", remote, err.Error()) 888 | break 889 | } 890 | //log.Printf("debug:message send to '%s'", remote) 891 | case <-notify: 892 | exitFlag = true 893 | } 894 | } 895 | stopped <- true 896 | //log.Printf(" send routine for '%s' stopped", remote) 897 | } 898 | -------------------------------------------------------------------------------- /endpoint_test.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "testing" 5 | "github.com/project-nano/sonar" 6 | "time" 7 | "net" 8 | "errors" 9 | "log" 10 | ) 11 | 12 | type CoreEndpoint struct { 13 | EndpointService //base class 14 | } 15 | 16 | func (core *CoreEndpoint)OnMessageReceived(msg Message){ 17 | log.Printf(" received message %d from %s", msg.GetID(), msg.GetSender()) 18 | } 19 | 20 | func (core *CoreEndpoint)OnServiceConnected(name string, t ServiceType, remote string){ 21 | log.Printf(" service %s connected, type %d", name, t) 22 | } 23 | 24 | func (core *CoreEndpoint)OnServiceDisconnected(name string, t ServiceType, gracefully bool){ 25 | log.Printf(" service %s disconnected, type %d, gracefully: %t", name, t, gracefully) 26 | } 27 | 28 | func (core *CoreEndpoint)OnDependencyReady(){ 29 | core.SetServiceReady() 30 | } 31 | 32 | func (core *CoreEndpoint)InitialEndpoint() error{ 33 | log.Print(" initialed") 34 | return nil 35 | } 36 | func (core *CoreEndpoint)OnEndpointStarted() error{ 37 | log.Print(" started") 38 | return nil 39 | } 40 | func (core *CoreEndpoint)OnEndpointStopped(){ 41 | log.Print(" stopped") 42 | } 43 | 44 | type PeerEndpoint struct { 45 | EndpointService //base class 46 | EventChan chan bool 47 | } 48 | 49 | func (peer *PeerEndpoint)OnMessageReceived(msg Message){ 50 | log.Printf(" received message %d from %s", msg.GetID(), msg.GetSender()) 51 | } 52 | 53 | func (peer *PeerEndpoint)OnServiceConnected(name string, t ServiceType, remote string){ 54 | log.Printf(" service %s connected, type %d", name, t) 55 | peer.EventChan <- true 56 | } 57 | 58 | func (peer *PeerEndpoint)OnServiceDisconnected(name string, t ServiceType, gracefully bool){ 59 | log.Printf(" service %s disconnected, type %d, gracefully %t", name, t, gracefully) 60 | peer.EventChan <- true 61 | } 62 | 63 | func (peer *PeerEndpoint)OnDependencyReady(){ 64 | peer.SetServiceReady() 65 | } 66 | func (peer *PeerEndpoint)InitialEndpoint() error{ 67 | log.Print(" initialed") 68 | return nil 69 | } 70 | func (peer *PeerEndpoint)OnEndpointStarted() error{ 71 | log.Print(" started") 72 | return nil 73 | } 74 | func (peer *PeerEndpoint)OnEndpointStopped(){ 75 | log.Print(" stopped") 76 | } 77 | 78 | func discoverIPv4Address() (string, error){ 79 | interfaceList, err := net.Interfaces() 80 | if err != nil { 81 | return "", err 82 | } 83 | var checkFlag = net.FlagMulticast | net.FlagPointToPoint | net.FlagUp 84 | for _, i := range interfaceList { 85 | if i.Flags&net.FlagLoopback != 0 { 86 | //ignore loopback 87 | continue 88 | } 89 | if i.Flags&checkFlag != 0 { 90 | addrs, err := i.Addrs() 91 | if err != nil{ 92 | return "", err 93 | } 94 | if len(addrs) == 0{ 95 | continue 96 | } 97 | for _, addr := range addrs{ 98 | log.Printf("check %s", addr.String()) 99 | ip, _, err := net.ParseCIDR(addr.String()) 100 | if err != nil{ 101 | return "", err 102 | } 103 | if ip.To4() != nil{ 104 | return ip.String(), nil 105 | } 106 | } 107 | } 108 | } 109 | return "", errors.New("no interface available") 110 | } 111 | 112 | func Test_TwoEndpoint(t *testing.T){ 113 | ListenAddress, err := discoverIPv4Address() 114 | if err != nil{ 115 | t.Fatal(err) 116 | } 117 | t.Logf("local ip %s discovered", ListenAddress) 118 | endpoint1, err := CreateStubEndpoint(sonar.DefaultMulticastAddress, sonar.DefaultMulticastPort, sonar.DefaultDomain, ListenAddress) 119 | if err != nil{ 120 | t.Fatal(err) 121 | } 122 | t.Log("core created") 123 | inf, err := InterfaceByAddress(ListenAddress) 124 | if err != nil{ 125 | t.Fatal(err) 126 | } 127 | var core = CoreEndpoint{endpoint1} 128 | core.handler = &core 129 | if err = core.GenerateName(ServiceTypeCore, inf); err != nil{ 130 | t.Fatal(err) 131 | } 132 | t.Logf("core name generated:%s", core.name) 133 | if err = core.Start(); err != nil{ 134 | t.Fatal(err) 135 | } 136 | t.Log("core started") 137 | endpoint2, err := CreatePeerEndpoint(sonar.DefaultMulticastAddress, sonar.DefaultMulticastPort, sonar.DefaultDomain) 138 | if err != nil{ 139 | t.Fatal(err) 140 | } 141 | t.Log("peer created") 142 | var peerChan = make(chan bool, 1) 143 | var peer = PeerEndpoint{endpoint2, peerChan} 144 | peer.handler = &peer 145 | if err = peer.GenerateName(ServiceTypeCell, inf); err != nil{ 146 | t.Fatal(err) 147 | } 148 | t.Logf("peer name generated:%s", peer.name) 149 | if err = peer.Start();err != nil{ 150 | t.Fatal(err) 151 | } 152 | 153 | { 154 | const connectTimeout = 3*time.Second 155 | //wait connected 156 | var timer = time.NewTimer(connectTimeout) 157 | select { 158 | case <- timer.C: 159 | //timeout 160 | t.Fatal("wait peer connect timeout") 161 | case <- peerChan: 162 | t.Log("peer connected") 163 | } 164 | } 165 | time.Sleep( 1 * time.Second) 166 | if err = core.Stop(); err != nil{ 167 | t.Fatal(err) 168 | } 169 | t.Log("core stopped") 170 | { 171 | const disconnectTimeout = 30*time.Second 172 | //wait disconnect 173 | var timer = time.NewTimer(disconnectTimeout) 174 | select { 175 | case <- timer.C: 176 | //timeout 177 | t.Fatal("wait peer disconnect timeout") 178 | case <- peerChan: 179 | t.Log("peer disconnected") 180 | } 181 | } 182 | if err = peer.Stop(); err != nil{ 183 | t.Fatal(err) 184 | } 185 | t.Log("peer stopped") 186 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/project-nano/framework 2 | 3 | go 1.19 4 | 5 | 6 | require ( 7 | github.com/project-nano/sonar v0.0.0-20190628085230-df7942628d6f 8 | github.com/sevlyar/go-daemon v0.1.6 9 | github.com/xtaci/kcp-go v5.4.20+incompatible 10 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect 11 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect 12 | github.com/klauspost/reedsolomon v1.11.8 // indirect 13 | github.com/pkg/errors v0.9.1 // indirect 14 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect 15 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect 16 | github.com/tjfoc/gmsm v1.4.1 // indirect 17 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect 18 | golang.org/x/crypto v0.13.0 // indirect 19 | golang.org/x/net v0.15.0 // indirect 20 | golang.org/x/sys v0.12.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 6 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 7 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 8 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 10 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 14 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 15 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 16 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 17 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 18 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 19 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 20 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 21 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 22 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 23 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 24 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= 25 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= 26 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= 27 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 28 | github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= 29 | github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= 30 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 31 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 32 | github.com/project-nano/sonar v0.0.0-20190628085230-df7942628d6f h1:NorTDWkZl22V1v/2t0gG01ylSACOmjDmrGpN3R/Pbgg= 33 | github.com/project-nano/sonar v0.0.0-20190628085230-df7942628d6f/go.mod h1:VYPy/Adnn0NLwbDfa/7vv12vWbftUqTHZzwt83Q5QAo= 34 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 35 | github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQGs= 36 | github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= 37 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= 38 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= 39 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI= 40 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= 41 | github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= 42 | github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= 43 | github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg= 44 | github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 45 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= 46 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= 47 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 48 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 49 | golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 50 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 51 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 52 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 53 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 54 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 55 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 56 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 57 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 58 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 59 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 60 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 61 | golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 62 | golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= 63 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 64 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 65 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 66 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 67 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 68 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 69 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 70 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 71 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 72 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 73 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 74 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 76 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 77 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 78 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 79 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 80 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 81 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 82 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 83 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 84 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 85 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 86 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 87 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 88 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 89 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 90 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 91 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 92 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 93 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 94 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 95 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 96 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 97 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 98 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 99 | -------------------------------------------------------------------------------- /json_message.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | type JsonMessage struct { 9 | ID MessageID `json:"id"` 10 | Success bool `json:"success,omitempty"` 11 | Sender string `json:"-"` 12 | From SessionID `json:"from,omitempty"` 13 | To SessionID `json:"to,omitempty"` 14 | Transaction TransactionID `json:"transaction,omitempty"` 15 | Error string `json:"error,omitempty"` 16 | BoolParams map[ParamKey]bool `json:"bool_params,omitempty"` 17 | StringParams map[ParamKey]string `json:"string_params,omitempty"` 18 | UIntParams map[ParamKey]uint `json:"uint_params,omitempty"` 19 | IntParams map[ParamKey]int `json:"int_params,omitempty"` 20 | FloatParams map[ParamKey]float64 `json:"float_params,omitempty"` 21 | UIntArrayParams map[ParamKey][]uint64 `json:"uint_array_params,omitempty"` 22 | StringArrayParams map[ParamKey][]string `json:"string_array_params,omitempty"` 23 | } 24 | 25 | func CreateJsonMessage(msg MessageID) (*JsonMessage, error) { 26 | return &JsonMessage{ID: msg}, nil 27 | } 28 | 29 | func CloneJsonMessage(origin Message) (clone *JsonMessage) { 30 | clone, _ = CreateJsonMessage(origin.GetID()) 31 | clone.SetSuccess(origin.IsSuccess()) 32 | clone.SetFromSession(origin.GetFromSession()) 33 | clone.SetToSession(origin.GetToSession()) 34 | clone.SetTransactionID(origin.GetTransactionID()) 35 | if "" != origin.GetError(){ 36 | clone.SetError(origin.GetError()) 37 | } 38 | //clone params 39 | if 0 != len(origin.GetAllBoolean()){ 40 | for key, value := range origin.GetAllBoolean(){ 41 | clone.SetBoolean(key, value) 42 | } 43 | } 44 | if 0 != len(origin.GetAllString()){ 45 | for key, value := range origin.GetAllString(){ 46 | clone.SetString(key, value) 47 | } 48 | } 49 | if 0 != len(origin.GetAllUInt()){ 50 | for key, value := range origin.GetAllUInt(){ 51 | clone.SetUInt(key, value) 52 | } 53 | } 54 | if 0 != len(origin.GetAllInt()){ 55 | for key, value := range origin.GetAllInt(){ 56 | clone.SetInt(key, value) 57 | } 58 | } 59 | if 0 != len(origin.GetAllFloat()){ 60 | for key, value := range origin.GetAllFloat(){ 61 | clone.SetFloat(key, value) 62 | } 63 | } 64 | if 0 != len(origin.GetAllUIntArray()){ 65 | for key, value := range origin.GetAllUIntArray(){ 66 | clone.SetUIntArray(key, value) 67 | } 68 | } 69 | if 0 != len(origin.GetAllStringArray()){ 70 | for key, value := range origin.GetAllStringArray(){ 71 | clone.SetStringArray(key, value) 72 | } 73 | } 74 | return clone 75 | } 76 | 77 | func MessageFromJson(data []byte) (*JsonMessage, error){ 78 | var msg JsonMessage 79 | var err = json.Unmarshal(data, &msg) 80 | return &msg, err 81 | } 82 | 83 | func (msg *JsonMessage) GetID() MessageID{ 84 | return msg.ID 85 | } 86 | 87 | func (msg *JsonMessage) SetID(id MessageID){ 88 | msg.ID = id 89 | } 90 | 91 | func (msg *JsonMessage)IsSuccess() bool{ 92 | return msg.Success 93 | } 94 | func (msg *JsonMessage)SetSuccess(flag bool){ 95 | msg.Success = flag 96 | } 97 | func (msg *JsonMessage)SetSender(value string){ 98 | msg.Sender = value 99 | } 100 | func (msg *JsonMessage)GetSender() string{ 101 | return msg.Sender 102 | } 103 | 104 | func (msg *JsonMessage)GetFromSession() SessionID{ 105 | return msg.From 106 | } 107 | func (msg *JsonMessage)SetFromSession(session SessionID){ 108 | msg.From = session 109 | } 110 | func (msg *JsonMessage)GetToSession() SessionID{ 111 | return msg.To 112 | } 113 | func (msg *JsonMessage)SetToSession(session SessionID){ 114 | msg.To = session 115 | } 116 | 117 | func (msg *JsonMessage)SetTransactionID(id TransactionID){ 118 | msg.Transaction = id 119 | } 120 | func (msg *JsonMessage)GetTransactionID() TransactionID{ 121 | return msg.Transaction 122 | } 123 | 124 | func (msg *JsonMessage)SetError(err string){ 125 | msg.Error = err 126 | } 127 | func (msg *JsonMessage)GetError() string{ 128 | return msg.Error 129 | } 130 | 131 | 132 | func (msg *JsonMessage)GetString(key ParamKey) (string, error){ 133 | if msg.StringParams != nil{ 134 | if value, exists := msg.StringParams[key]; exists{ 135 | return value, nil 136 | } 137 | } 138 | return "", fmt.Errorf("no string param for key %d", key) 139 | } 140 | 141 | func (msg *JsonMessage)GetUInt(key ParamKey) (uint, error){ 142 | if msg.UIntParams != nil{ 143 | if value, exists := msg.UIntParams[key]; exists{ 144 | return value, nil 145 | } 146 | } 147 | return 0, fmt.Errorf("no uint param for key %d", key) 148 | } 149 | 150 | func (msg *JsonMessage)GetInt(key ParamKey) (int, error){ 151 | if msg.IntParams != nil{ 152 | if value, exists := msg.IntParams[key]; exists{ 153 | return value, nil 154 | } 155 | } 156 | return 0, fmt.Errorf("no int param for key %d", key) 157 | } 158 | 159 | func (msg *JsonMessage)GetFloat(key ParamKey) (float64, error){ 160 | if msg.FloatParams != nil{ 161 | if value, exists := msg.FloatParams[key]; exists{ 162 | return value, nil 163 | } 164 | } 165 | return 0.0, fmt.Errorf("no float param for key %d", key) 166 | } 167 | 168 | func (msg *JsonMessage)GetBoolean(key ParamKey) (bool, error){ 169 | if msg.BoolParams != nil{ 170 | if value, exists := msg.BoolParams[key]; exists{ 171 | return value, nil 172 | } 173 | } 174 | return false, fmt.Errorf("no bool param for key %d", key) 175 | } 176 | 177 | 178 | func (msg *JsonMessage)SetString(key ParamKey, value string){ 179 | if msg.StringParams != nil{ 180 | msg.StringParams[key] = value 181 | }else{ 182 | msg.StringParams = map[ParamKey]string{key:value} 183 | } 184 | } 185 | 186 | func (msg *JsonMessage)SetUInt(key ParamKey, value uint){ 187 | if msg.UIntParams != nil{ 188 | msg.UIntParams[key] = value 189 | }else{ 190 | msg.UIntParams = map[ParamKey]uint{key:value} 191 | } 192 | } 193 | 194 | func (msg *JsonMessage)SetInt(key ParamKey, value int){ 195 | if msg.IntParams != nil{ 196 | msg.IntParams[key] = value 197 | }else{ 198 | msg.IntParams = map[ParamKey]int{key:value} 199 | } 200 | } 201 | 202 | func (msg *JsonMessage)SetFloat(key ParamKey, value float64){ 203 | if msg.FloatParams != nil{ 204 | msg.FloatParams[key] = value 205 | }else{ 206 | msg.FloatParams = map[ParamKey]float64{key:value} 207 | } 208 | } 209 | 210 | func (msg *JsonMessage)SetBoolean(key ParamKey, value bool){ 211 | if msg.BoolParams != nil{ 212 | msg.BoolParams[key] = value 213 | }else{ 214 | msg.BoolParams = map[ParamKey]bool{key:value} 215 | } 216 | } 217 | 218 | func (msg *JsonMessage)SetUIntArray(key ParamKey, value []uint64){ 219 | if msg.UIntArrayParams != nil{ 220 | msg.UIntArrayParams[key] = value 221 | }else{ 222 | msg.UIntArrayParams = map[ParamKey][]uint64{key:value} 223 | } 224 | } 225 | func (msg *JsonMessage)GetUIntArray(key ParamKey) ([]uint64, error){ 226 | if msg.UIntArrayParams != nil{ 227 | if value, exists := msg.UIntArrayParams[key]; exists{ 228 | return value, nil 229 | } 230 | } 231 | return nil, fmt.Errorf("no uint array for key %d", key) 232 | } 233 | 234 | func (msg *JsonMessage)SetStringArray(key ParamKey, value []string){ 235 | if msg.StringArrayParams != nil{ 236 | msg.StringArrayParams[key] = value 237 | }else{ 238 | msg.StringArrayParams = map[ParamKey][]string{key:value} 239 | } 240 | } 241 | func (msg *JsonMessage)GetStringArray(key ParamKey) ([]string, error){ 242 | if msg.StringArrayParams != nil{ 243 | if value, exists := msg.StringArrayParams[key]; exists{ 244 | return value, nil 245 | } 246 | } 247 | return nil, fmt.Errorf("no string array for key %d", key) 248 | } 249 | 250 | func (msg *JsonMessage)Serialize() ([]byte, error){ 251 | return json.Marshal(msg) 252 | } 253 | 254 | var ( 255 | emptyFloatMap = map[ParamKey]float64{} 256 | emptyStringMap = map[ParamKey]string{} 257 | emptyUIntMap = map[ParamKey]uint{} 258 | emptyIntMap = map[ParamKey]int{} 259 | emptyBooleanMap = map[ParamKey]bool{} 260 | emptyStringArrayMap = map[ParamKey][]string{} 261 | emptyUIntArrayMap = map[ParamKey][]uint64{} 262 | ) 263 | 264 | func (msg *JsonMessage) GetAllString() (map[ParamKey]string){ 265 | if msg.StringParams != nil{ 266 | return msg.StringParams 267 | } 268 | return emptyStringMap 269 | } 270 | func (msg *JsonMessage) GetAllUInt() (map[ParamKey]uint){ 271 | if msg.UIntParams != nil{ 272 | return msg.UIntParams 273 | } 274 | return emptyUIntMap 275 | } 276 | 277 | func (msg *JsonMessage) GetAllInt() (map[ParamKey]int){ 278 | if msg.IntParams != nil{ 279 | return msg.IntParams 280 | } 281 | return emptyIntMap 282 | } 283 | 284 | func (msg *JsonMessage) GetAllFloat() (map[ParamKey]float64){ 285 | if msg.FloatParams != nil{ 286 | return msg.FloatParams 287 | } 288 | return emptyFloatMap 289 | } 290 | 291 | func (msg *JsonMessage) GetAllBoolean() (map[ParamKey]bool){ 292 | if msg.BoolParams != nil{ 293 | return msg.BoolParams 294 | } 295 | return emptyBooleanMap 296 | } 297 | 298 | func (msg *JsonMessage) GetAllUIntArray() (map[ParamKey][]uint64){ 299 | if msg.UIntArrayParams != nil{ 300 | return msg.UIntArrayParams 301 | } 302 | return emptyUIntArrayMap 303 | } 304 | 305 | func (msg *JsonMessage) GetAllStringArray() (map[ParamKey][]string) { 306 | if msg.StringArrayParams != nil{ 307 | return msg.StringArrayParams 308 | } 309 | return emptyStringArrayMap 310 | } -------------------------------------------------------------------------------- /json_message_test.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import "testing" 4 | import ( 5 | "math/rand" 6 | "time" 7 | "encoding/base64" 8 | "fmt" 9 | "errors" 10 | "strings" 11 | ) 12 | 13 | const ( 14 | testRepeat = 10 15 | ) 16 | 17 | var generator = rand.New(rand.NewSource(time.Now().UnixNano())) 18 | 19 | var checkConsistency = func(t *testing.T, f func(msg *JsonMessage)) { 20 | for i := 0; i < testRepeat; i++{ 21 | origin, err := generateMessage() 22 | if err != nil{ 23 | t.Fatalf("generate message fail: %s", err.Error()) 24 | } 25 | f(origin) 26 | data, err := origin.Serialize() 27 | if err != nil{ 28 | t.Fatalf("serialize fail: %s", err.Error()) 29 | } 30 | target, err := MessageFromJson(data) 31 | if err != nil{ 32 | t.Fatalf("parse message fail: %s", err.Error()) 33 | } 34 | result, err := isIdentical(origin, target, t) 35 | if err != nil{ 36 | t.Fatalf("identify fail: %s", err.Error()) 37 | } 38 | if !result{ 39 | t.Fatal("message corrupted") 40 | } 41 | t.Logf("%dth try success", i + 1) 42 | } 43 | } 44 | 45 | func Test_BaseMember(t *testing.T){ 46 | var empty = func(msg *JsonMessage) {} 47 | checkConsistency(t, empty) 48 | } 49 | 50 | func Test_BoolParam(t *testing.T){ 51 | const paramCount = 5 52 | var prepare = func(msg *JsonMessage) { 53 | generateBoolParam(msg, paramCount) 54 | } 55 | checkConsistency(t, prepare) 56 | } 57 | 58 | func Test_StringParam(t *testing.T){ 59 | const paramCount = 5 60 | var prepare = func(msg *JsonMessage) { 61 | generateStringParam(msg, paramCount) 62 | } 63 | checkConsistency(t, prepare) 64 | } 65 | 66 | func Test_UIntParam(t *testing.T){ 67 | const paramCount = 5 68 | var prepare = func(msg *JsonMessage) { 69 | generateUIntParam(msg, paramCount) 70 | } 71 | checkConsistency(t, prepare) 72 | } 73 | 74 | func Test_IntParam(t *testing.T){ 75 | const paramCount = 5 76 | var prepare = func(msg *JsonMessage) { 77 | generateIntParam(msg, paramCount) 78 | } 79 | checkConsistency(t, prepare) 80 | } 81 | 82 | func Test_FloatParam(t *testing.T){ 83 | const paramCount = 5 84 | var prepare = func(msg *JsonMessage) { 85 | generateFloatParam(msg, paramCount) 86 | } 87 | checkConsistency(t, prepare) 88 | } 89 | 90 | func Test_UIntArrayParam(t *testing.T){ 91 | const paramCount = 5 92 | var prepare = func(msg *JsonMessage) { 93 | generateUIntArrayParam(msg, paramCount) 94 | } 95 | checkConsistency(t, prepare) 96 | } 97 | 98 | func Test_StringArrayParam(t *testing.T){ 99 | const paramCount = 5 100 | var prepare = func(msg *JsonMessage) { 101 | generateStringArrayParam(msg, paramCount) 102 | } 103 | checkConsistency(t, prepare) 104 | } 105 | 106 | func Test_MixedParam(t *testing.T){ 107 | const paramCount = 3 108 | var prepare = func(msg *JsonMessage) { 109 | generateBoolParam(msg, paramCount) 110 | generateUIntParam(msg, paramCount) 111 | generateIntParam(msg, paramCount) 112 | generateStringParam(msg, paramCount) 113 | generateFloatParam(msg, paramCount) 114 | generateUIntArrayParam(msg, paramCount) 115 | generateStringArrayParam(msg, paramCount) 116 | } 117 | checkConsistency(t, prepare) 118 | } 119 | 120 | func Test_CloneMessage(t *testing.T) { 121 | const ( 122 | paramCount = 3 123 | ) 124 | for i := 0; i < testRepeat; i++{ 125 | origin, err := generateMessage() 126 | if err != nil{ 127 | t.Fatalf("generate message fail: %s", err.Error()) 128 | } 129 | generateBoolParam(origin, paramCount) 130 | generateUIntParam(origin, paramCount) 131 | generateIntParam(origin, paramCount) 132 | generateStringParam(origin, paramCount) 133 | generateFloatParam(origin, paramCount) 134 | generateUIntArrayParam(origin, paramCount) 135 | generateStringArrayParam(origin, paramCount) 136 | 137 | var clone = CloneJsonMessage(origin) 138 | if err != nil{ 139 | t.Fatalf("clone message fail: %s", err.Error()) 140 | } 141 | identical, err := isIdentical(origin, clone, t) 142 | if err != nil{ 143 | t.Fatalf("compare %d clone fail: %s", i, err.Error()) 144 | } 145 | if !identical { 146 | t.Fatalf("%dth clone is not identical", i) 147 | } 148 | t.Logf("%dth clone is identical", i) 149 | } 150 | 151 | } 152 | 153 | func generateMessage() (msg *JsonMessage, err error){ 154 | const ( 155 | ErrorLength = 32 156 | ) 157 | msg, err = CreateJsonMessage(MessageID(generator.Uint32())) 158 | msg.SetFromSession(SessionID(generator.Uint32())) 159 | msg.SetToSession(SessionID(generator.Uint32())) 160 | msg.SetTransactionID(TransactionID(generator.Uint32())) 161 | if generator.Intn(2) > 0 { 162 | msg.SetSuccess(true) 163 | }else{ 164 | var buf = make([]byte, ErrorLength) 165 | _, err = generator.Read(buf) 166 | if err != nil{ 167 | return 168 | } 169 | msg.SetError(base64.StdEncoding.EncodeToString(buf)) 170 | msg.SetSuccess(false) 171 | } 172 | return msg, nil 173 | } 174 | 175 | func generateBoolParam(msg Message, count int){ 176 | for i := 0; i < count; i++{ 177 | var key = ParamKey(generator.Uint32()) 178 | if generator.Intn(2) > 0 { 179 | msg.SetBoolean(key, true) 180 | }else{ 181 | msg.SetBoolean(key, false) 182 | } 183 | } 184 | } 185 | 186 | 187 | func generateStringParam(msg Message, count int){ 188 | const ( 189 | MinStringLength = 6 190 | MaxStringLength = 20 191 | ) 192 | var bufSize = MinStringLength + generator.Intn(MaxStringLength - MinStringLength) 193 | var buf = make([]byte, bufSize) 194 | for i := 0; i < count; i++{ 195 | generator.Read(buf) 196 | var key = ParamKey(generator.Uint32()) 197 | msg.SetString(key, base64.StdEncoding.EncodeToString(buf)) 198 | } 199 | } 200 | 201 | 202 | func generateUIntParam(msg Message, count int){ 203 | for i := 0; i < count; i++{ 204 | var key = ParamKey(generator.Uint32()) 205 | var value = uint(generator.Uint64()) 206 | msg.SetUInt(key, value) 207 | } 208 | } 209 | 210 | 211 | func generateIntParam(msg Message, count int){ 212 | for i := 0; i < count; i++{ 213 | var key = ParamKey(generator.Uint32()) 214 | var value = generator.Int() 215 | msg.SetInt(key, value) 216 | } 217 | } 218 | 219 | 220 | func generateFloatParam(msg Message, count int){ 221 | for i := 0; i < count; i++{ 222 | var key = ParamKey(generator.Uint32()) 223 | var value = generator.Float64() 224 | msg.SetFloat(key, value) 225 | } 226 | } 227 | 228 | func generateStringArrayParam(msg Message, count int){ 229 | const ( 230 | MinArrayLength = 1 231 | MaxArrayLength = 10 232 | MinStringLength = 6 233 | MaxStringLength = 20 234 | ) 235 | var arrayLength = MinArrayLength + generator.Intn(MaxArrayLength - MinArrayLength) 236 | var bufSize = MinStringLength + generator.Intn(MaxStringLength - MinStringLength) 237 | var buf = make([]byte, bufSize) 238 | for i := 0; i < count; i++{ 239 | var value = make([]string, arrayLength) 240 | for j := 0; j < arrayLength; j++{ 241 | generator.Read(buf) 242 | value[j] = base64.StdEncoding.EncodeToString(buf) 243 | } 244 | var key = ParamKey(generator.Uint32()) 245 | msg.SetStringArray(key, value) 246 | } 247 | } 248 | 249 | 250 | func generateUIntArrayParam(msg Message, count int){ 251 | const ( 252 | MinArrayLength = 1 253 | MaxArrayLength = 10 254 | ) 255 | var size = MinArrayLength + generator.Intn(MaxArrayLength - MinArrayLength) 256 | for i := 0; i < count; i++{ 257 | var value = make([]uint64, size) 258 | for j := 0; j < size; j++{ 259 | value[j] = generator.Uint64() 260 | } 261 | var key = ParamKey(generator.Uint32()) 262 | msg.SetUIntArray(key, value) 263 | } 264 | } 265 | 266 | 267 | 268 | func isIdentical(source, target *JsonMessage, t *testing.T) (bool, error){ 269 | if source.ID != target.ID{ 270 | return false, fmt.Errorf("different ID %d / %d", source.ID, target.ID) 271 | } 272 | 273 | if source.Success != target.Success{ 274 | return false, fmt.Errorf("different success flag %t / %t", source.Success, target.Success) 275 | } 276 | if source.From != target.From{ 277 | return false, fmt.Errorf("different from property %d / %d", source.From, target.From) 278 | } 279 | if source.To != target.To{ 280 | return false, fmt.Errorf("different to property %d / %d", source.To, target.To) 281 | } 282 | if source.Transaction != target.Transaction{ 283 | return false, fmt.Errorf("different transaction property %d / %d", source.Transaction, target.Transaction) 284 | } 285 | if source.Error != target.Error{ 286 | return false, fmt.Errorf("different errror message %s / %s", source.Error, target.Error) 287 | } 288 | if t != nil{ 289 | t.Logf("msg %08X from %08X to %08X, status %t, trans %08X", 290 | target.GetID(), target.GetFromSession(), target.GetToSession(), target.IsSuccess(), target.GetTransactionID()) 291 | } 292 | { 293 | //bool 294 | if source.BoolParams == nil && target.BoolParams != nil{ 295 | return false, errors.New("unexpected bool param") 296 | } else if source.BoolParams != nil{ 297 | if target.BoolParams == nil{ 298 | return false, errors.New("bool param lost") 299 | } 300 | for key, sourceValue := range source.BoolParams{ 301 | targetValue, exists := target.BoolParams[key] 302 | if !exists{ 303 | return false, fmt.Errorf("no bool param available for key %d", key) 304 | } 305 | if targetValue != sourceValue{ 306 | return false, fmt.Errorf("different bool param of key %d, %t => %t", key, sourceValue, targetValue) 307 | } 308 | if t != nil{ 309 | t.Logf("bool %d: %t", key, targetValue) 310 | } 311 | } 312 | } 313 | } 314 | { 315 | //string 316 | if source.StringParams == nil && target.StringParams != nil{ 317 | return false, errors.New("unexpected string param") 318 | } else if source.StringParams != nil{ 319 | if target.StringParams == nil{ 320 | return false, errors.New("string param lost") 321 | } 322 | for key, sourceValue := range source.StringParams{ 323 | targetValue, exists := target.StringParams[key] 324 | if !exists{ 325 | return false, fmt.Errorf("no string param available for key %d", key) 326 | } 327 | if targetValue != sourceValue{ 328 | return false, fmt.Errorf("different string param of key %d, %s => %s", key, sourceValue, targetValue) 329 | } 330 | if t != nil{ 331 | t.Logf("string %d: %s", key, targetValue) 332 | } 333 | 334 | } 335 | } 336 | } 337 | { 338 | //uint 339 | if source.UIntParams == nil && target.UIntParams != nil{ 340 | return false, errors.New("unexpected uint param") 341 | } else if source.UIntParams != nil{ 342 | if target.UIntParams == nil{ 343 | return false, errors.New("uint param lost") 344 | } 345 | for key, sourceValue := range source.UIntParams{ 346 | targetValue, exists := target.UIntParams[key] 347 | if !exists{ 348 | return false, fmt.Errorf("no uint param available for key %d", key) 349 | } 350 | if targetValue != sourceValue{ 351 | return false, fmt.Errorf("different uint param of key %d, %d => %d", key, sourceValue, targetValue) 352 | } 353 | if t != nil{ 354 | t.Logf("uint %d: %d", key, targetValue) 355 | } 356 | 357 | } 358 | } 359 | } 360 | { 361 | //int 362 | if source.IntParams == nil && target.IntParams != nil{ 363 | return false, errors.New("unexpected int param") 364 | } else if source.IntParams != nil{ 365 | if target.IntParams == nil{ 366 | return false, errors.New("int param lost") 367 | } 368 | for key, sourceValue := range source.IntParams{ 369 | targetValue, exists := target.IntParams[key] 370 | if !exists{ 371 | return false, fmt.Errorf("no int param available for key %d", key) 372 | } 373 | if targetValue != sourceValue{ 374 | return false, fmt.Errorf("different int param of key %d, %d => %d", key, sourceValue, targetValue) 375 | } 376 | if t != nil{ 377 | t.Logf("int %d: %d", key, targetValue) 378 | } 379 | 380 | } 381 | } 382 | } 383 | { 384 | //float 385 | if source.FloatParams == nil && target.FloatParams != nil{ 386 | return false, errors.New("unexpected float param") 387 | } else if source.FloatParams != nil{ 388 | if target.FloatParams == nil{ 389 | return false, errors.New("float param lost") 390 | } 391 | for key, sourceValue := range source.FloatParams{ 392 | targetValue, exists := target.FloatParams[key] 393 | if !exists{ 394 | return false, fmt.Errorf("no float param available for key %d", key) 395 | } 396 | if targetValue != sourceValue{ 397 | return false, fmt.Errorf("different float param of key %d, %f => %f", key, sourceValue, targetValue) 398 | } 399 | if t != nil{ 400 | t.Logf("float %d: %f", key, targetValue) 401 | } 402 | 403 | } 404 | } 405 | } 406 | { 407 | //uint array 408 | if source.UIntArrayParams == nil && target.UIntArrayParams != nil{ 409 | return false, errors.New("unexpected uint array param") 410 | } else if source.UIntArrayParams != nil{ 411 | if target.UIntArrayParams == nil{ 412 | return false, errors.New("uint array param lost") 413 | } 414 | for key, sourceArray := range source.UIntArrayParams{ 415 | targetArray, exists := target.UIntArrayParams[key] 416 | if !exists{ 417 | return false, fmt.Errorf("no uint array param available for key %d", key) 418 | } 419 | var arrayCount = len(sourceArray) 420 | if arrayCount != len(targetArray){ 421 | return false, fmt.Errorf("different uint array length for key %d, %d => %d", key, arrayCount, len(targetArray)) 422 | } 423 | for i := 0; i < arrayCount; i++{ 424 | if sourceArray[i] != targetArray[i]{ 425 | return false, fmt.Errorf("%dth element in uint array with key %d unmatched, %d => %d", i, key, sourceArray[i], targetArray[i]) 426 | } 427 | } 428 | if t != nil{ 429 | var array = make([]string, 0) 430 | for _, value := range targetArray{ 431 | array = append(array, fmt.Sprintf("%d", value)) 432 | } 433 | t.Logf("uint array %d: [%s]", key, strings.Join(array, ",")) 434 | } 435 | 436 | } 437 | } 438 | } 439 | { 440 | //string array 441 | if source.StringArrayParams == nil && target.StringArrayParams != nil{ 442 | return false, errors.New("unexpected string array param") 443 | } else if source.StringArrayParams != nil{ 444 | if target.StringArrayParams == nil{ 445 | return false, errors.New("string array param lost") 446 | } 447 | for key, sourceArray := range source.StringArrayParams{ 448 | targetArray, exists := target.StringArrayParams[key] 449 | if !exists{ 450 | return false, fmt.Errorf("no string array param available for key %d", key) 451 | } 452 | var arrayCount = len(sourceArray) 453 | if arrayCount != len(targetArray){ 454 | return false, fmt.Errorf("different string array length for key %d, %d => %d", key, arrayCount, len(targetArray)) 455 | } 456 | for i := 0; i < arrayCount; i++{ 457 | if sourceArray[i] != targetArray[i]{ 458 | return false, fmt.Errorf("%dth element in string array with key %d unmatched, %s => %s", i, key, sourceArray[i], targetArray[i]) 459 | } 460 | } 461 | if t != nil{ 462 | t.Logf("string array %d: [%s]", key, strings.Join(targetArray, ",")) 463 | } 464 | } 465 | } 466 | } 467 | 468 | return true, nil 469 | } 470 | 471 | -------------------------------------------------------------------------------- /key_define.go: -------------------------------------------------------------------------------- 1 | package framework 2 | // FFFF Operate: FF resource: FF type 3 | 4 | 5 | const ( 6 | ResourceOffset = 8 7 | OperateOffset = 8 + ResourceOffset 8 | ) 9 | //Offset 10 | const ( 11 | MessageRequest = iota 12 | MessageResponse 13 | MessageEvent 14 | ) 15 | 16 | type ServiceType uint 17 | //Service Type 18 | const ( 19 | ServiceTypeCore = iota 20 | ServiceTypeCell 21 | ServiceTypeImage 22 | ServiceTypeRouter 23 | ) 24 | 25 | const ( 26 | ServiceTypeStringCore = "core" 27 | ) 28 | 29 | //resource 30 | const ( 31 | ResourceService = iota 32 | ResourceConnection 33 | ResourceComputePool 34 | ResourceComputeCell 35 | ResourceHost 36 | ResourceGuest 37 | ResourceInstance 38 | ResourceZone 39 | ResourceMediaImage 40 | ResourceDiskImage 41 | ResourceImageServer 42 | ResourceAddress 43 | ResourceCore 44 | ResourceMemory 45 | ResourceDisk 46 | ResourceAuth 47 | ResourceSnapshot 48 | ResourceMedia 49 | ResourceStoragePool 50 | ResourceMigration 51 | ResourceAddressPool 52 | ResourceAddressRange 53 | ResourceSystem 54 | ResourceName 55 | ResourcePriority 56 | ResourceDiskThreshold 57 | ResourceNetworkThreshold 58 | ResourceCellStorage 59 | ResourceStorage 60 | ResourceTemplate 61 | ResourcePolicyGroup 62 | ResourcePolicyRule 63 | ResourceSecret 64 | ResourceGuestRule 65 | ResourceAutoStart 66 | ) 67 | 68 | 69 | //resource operate 70 | const ( 71 | OperateCreate = iota 72 | OperateDelete 73 | OperateModify 74 | OperateQuery 75 | OperateAdd 76 | OperateRemove 77 | OperateRegister 78 | OperateUnregister 79 | OperateQueryDetail 80 | OperateQueryUnallocated 81 | OperateGet 82 | OperateQueryStatus 83 | OperateStart 84 | OperateStop 85 | OperateGetStatus 86 | OperateResize 87 | OperateShrink 88 | OperateAttach 89 | OperateDetach 90 | OperateCommit 91 | OperatePull 92 | OperateRestore 93 | OperateEnable 94 | OperateDisable 95 | OperateMigrate 96 | OperatePurge 97 | OperateReset 98 | OperateStartBatchCreate 99 | OperateGetBatchCreate 100 | OperateStartBatchDelete 101 | OperateGetBatchDelete 102 | OperateStartBatchStop 103 | OperateGetBatchStop 104 | OperateSynchronize 105 | OperateChangeOrder 106 | OperateChangeDefault 107 | OperateSearch 108 | ) 109 | 110 | 111 | const ( 112 | RegisterServiceRequest = OperateRegister<cell 495 | ComputePoolReadyEvent = EventReady<core 498 | ComputeCellAvailableEvent = EventAvailable< no executor registered for message [%08X]", msg.GetID()) 115 | break 116 | } 117 | //allocate session 118 | seed := lastID 119 | var try SessionID 120 | var invoked = false 121 | for try = 0; try < sessionCount; try++ { 122 | id := (seed+try)%sessionCount + minSessionID 123 | session, exists := engine.sessions[id] 124 | if !exists{ 125 | log.Printf(" warning: unexpect session [%08X]", id) 126 | break 127 | } 128 | if session.Allocated{ 129 | continue 130 | } 131 | //unallocated 132 | lastID = id 133 | var pushChan = make(chan Message, sessionQueueLength) 134 | var tChan = make(chan bool, 1) 135 | invoked = true 136 | engine.sessions[id] = sessionChannel{true, pushChan, tChan} 137 | //log.Printf(" [%08X] session allocated", id) 138 | go executeTask(executor, id, msg, pushChan, tChan, engine.finishChan) 139 | break 140 | } 141 | if !invoked{ 142 | log.Println(" warning: no session available") 143 | } 144 | 145 | case msg := <- engine.pushChan: 146 | id := msg.GetToSession() 147 | if session, exists := engine.sessions[id]; exists { 148 | if session.Allocated { 149 | session.pushChan <- msg 150 | break 151 | }else{ 152 | log.Printf(" warning: push message %08X to deallocated session [%08X]", msg.GetID(), id) 153 | } 154 | }else{ 155 | log.Printf(" warning: message %08X push to invalid session[%08X]", msg.GetID(), id) 156 | } 157 | 158 | case id := <- engine.finishChan: 159 | //deallocate session 160 | if session, exists := engine.sessions[id]; exists { 161 | if session.Allocated { 162 | engine.sessions[id] = sessionChannel{Allocated:false} 163 | //log.Printf(" [%08X] session deallocated", id) 164 | }else{ 165 | log.Printf(" warning: session [%08X] already deallocated", id) 166 | } 167 | }else{ 168 | log.Printf(" warning: try deallocate invalid session[%08X]", id) 169 | } 170 | } 171 | } 172 | engine.exitChan <- true 173 | } 174 | 175 | func executeTask(executor TransactionExecutor, id SessionID, msg Message, pushChan chan Message, 176 | terminateChan chan bool, finished chan SessionID){ 177 | if err := executor.Execute(id, msg, pushChan, terminateChan); err != nil{ 178 | log.Printf(" [%08X] execute task(msg: %08X) fail: %s", id, msg.GetID(), err.Error()) 179 | //}else{ 180 | // log.Printf(" [%08X] execute finished", id) 181 | } 182 | finished <- id 183 | } 184 | 185 | 186 | -------------------------------------------------------------------------------- /transaction_engine_test.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "time" 5 | "testing" 6 | "fmt" 7 | ) 8 | 9 | const ( 10 | KeyTaskID = 1 11 | ) 12 | 13 | var resultChan = make(chan uint, 1 << 12) 14 | 15 | type WaitExecutor struct { 16 | t *testing.T 17 | } 18 | 19 | func (executor *WaitExecutor)Execute(id SessionID, message Message, incoming chan Message, terminate chan bool) error{ 20 | taskID, _ := message.GetUInt(KeyTaskID) 21 | //executor.t.Logf("[%08X] task %d begin wait", id, taskID) 22 | time.Sleep(1000 * time.Millisecond) 23 | //executor.t.Logf("[%08X] task %d wait finish", id, taskID) 24 | resultChan <- taskID 25 | return nil 26 | } 27 | 28 | func TestTransactionEngine_WaitExecutor(t *testing.T) { 29 | engine, err := CreateTransactionEngine() 30 | if err != nil{ 31 | t.Fatalf("create engine fail: %s", err.Error()) 32 | } 33 | const ( 34 | testMessage = 1 35 | id1 = 1 36 | id2 = 2 37 | ) 38 | engine.RegisterExecutor(testMessage, &WaitExecutor{t}) 39 | 40 | if err = engine.Start(); err != nil{ 41 | t.Fatalf("start engine fail: %s", err.Error()) 42 | } 43 | t.Log("engine started") 44 | 45 | task1, _ := CreateJsonMessage(testMessage) 46 | task1.SetUInt(KeyTaskID, id1) 47 | 48 | task2, _ := CreateJsonMessage(testMessage) 49 | task2.SetUInt(KeyTaskID, id2) 50 | 51 | engine.InvokeTask(task1) 52 | time.Sleep(50*time.Millisecond) 53 | engine.InvokeTask(task2) 54 | 55 | var expected = []uint{id1, id2} 56 | var executed = make([]uint, 0) 57 | var timer = time.NewTimer(5*time.Second) 58 | for len(executed) < 2{ 59 | select { 60 | case <- timer.C: 61 | t.Fatal("execute timeout") 62 | case id := <-resultChan: 63 | executed = append(executed, id) 64 | } 65 | } 66 | if len(executed) != len(expected){ 67 | t.Fatal("unexpect queue length") 68 | } 69 | for i := 0; i < len(expected); i++{ 70 | if expected[i] != executed[i]{ 71 | t.Fatalf("unexpect task %d at index %d", executed[i], i) 72 | } 73 | } 74 | if err = engine.Stop(); err != nil{ 75 | t.Fatalf("stop engine fail: %s", err.Error()) 76 | } 77 | t.Log("engine stopped") 78 | } 79 | 80 | type TimeoutExecutor struct { 81 | Timeout time.Duration 82 | } 83 | 84 | func (executor *TimeoutExecutor)Execute(id SessionID, message Message, incoming chan Message, terminate chan bool) error{ 85 | taskID, _ := message.GetUInt(KeyTaskID) 86 | var timer = time.NewTimer(executor.Timeout) 87 | select { 88 | case <- timer.C: 89 | resultChan <- taskID 90 | } 91 | return nil 92 | } 93 | 94 | func TestTransactionEngine_SessionTimeout(t *testing.T) { 95 | engine, err := CreateTransactionEngine() 96 | if err != nil{ 97 | t.Fatalf("create engine fail: %s", err.Error()) 98 | } 99 | const ( 100 | testMessage = 2 101 | timeout = 1000*time.Millisecond 102 | taskCount = 5 103 | ) 104 | engine.RegisterExecutor(testMessage, &TimeoutExecutor{Timeout:timeout}) 105 | 106 | if err = engine.Start(); err != nil{ 107 | t.Fatalf("start engine fail: %s", err.Error()) 108 | } 109 | var expected []uint 110 | t.Log("engine started") 111 | for taskID := 0; taskID < taskCount; taskID++{ 112 | msg, _ := CreateJsonMessage(testMessage) 113 | msg.SetUInt(KeyTaskID, uint(taskID)) 114 | expected = append(expected, uint(taskID)) 115 | engine.InvokeTask(msg) 116 | time.Sleep(50*time.Millisecond) 117 | } 118 | 119 | var executed = make([]uint, 0) 120 | var timer = time.NewTimer(6*time.Second) 121 | for len(executed) < taskCount{ 122 | select { 123 | case <- timer.C: 124 | t.Fatal("execute timeout") 125 | case id := <-resultChan: 126 | executed = append(executed, id) 127 | } 128 | } 129 | if len(executed) != len(expected){ 130 | t.Fatal("unexpect queue length") 131 | } 132 | for i := 0; i < len(expected); i++{ 133 | if expected[i] != executed[i]{ 134 | t.Fatalf("unexpect task %d at index %d", executed[i], i) 135 | } 136 | } 137 | if err = engine.Stop(); err != nil{ 138 | t.Fatalf("stop engine fail: %s", err.Error()) 139 | } 140 | t.Log("engine stopped") 141 | } 142 | 143 | 144 | type NopeExecutor struct { 145 | 146 | } 147 | 148 | func (executor *NopeExecutor)Execute(id SessionID, message Message, incoming chan Message, terminate chan bool) error{ 149 | taskID, _ := message.GetUInt(KeyTaskID) 150 | resultChan <- taskID 151 | return nil 152 | } 153 | 154 | func TestTransactionEngine_NopeExecutor(t *testing.T) { 155 | engine, err := CreateTransactionEngine() 156 | if err != nil{ 157 | t.Fatalf("create engine fail: %s", err.Error()) 158 | } 159 | const ( 160 | testMessage = 3 161 | taskCount = 1 << 11 162 | ) 163 | engine.RegisterExecutor(testMessage, &NopeExecutor{}) 164 | 165 | if err = engine.Start(); err != nil{ 166 | t.Fatalf("start engine fail: %s", err.Error()) 167 | } 168 | var expected []uint 169 | t.Log("engine started") 170 | for taskID := 0; taskID < taskCount; taskID++{ 171 | msg, _ := CreateJsonMessage(testMessage) 172 | msg.SetUInt(KeyTaskID, uint(taskID)) 173 | expected = append(expected, uint(taskID)) 174 | engine.InvokeTask(msg) 175 | time.Sleep(5*time.Millisecond) 176 | } 177 | 178 | var executed = make([]uint, 0) 179 | var timer = time.NewTimer(6*time.Second) 180 | for len(executed) < taskCount{ 181 | select { 182 | case <- timer.C: 183 | t.Fatal("execute timeout") 184 | case id := <-resultChan: 185 | executed = append(executed, id) 186 | } 187 | } 188 | if len(executed) != len(expected){ 189 | t.Fatal("unexpect queue length") 190 | } 191 | for i := 0; i < len(expected); i++{ 192 | if expected[i] != executed[i]{ 193 | t.Fatalf("unexpect task %d at index %d", executed[i], i) 194 | } 195 | } 196 | if err = engine.Stop(); err != nil{ 197 | t.Fatalf("stop engine fail: %s", err.Error()) 198 | } 199 | t.Logf("engine stopped, %d task executed", len(executed)) 200 | } 201 | 202 | type OneStepExecutor struct { 203 | 204 | } 205 | 206 | const ( 207 | FirstMessage = iota 208 | SecondMessage 209 | ) 210 | 211 | func (executor *OneStepExecutor)Execute(id SessionID, message Message, incoming chan Message, terminate chan bool) error{ 212 | taskID, _ := message.GetUInt(KeyTaskID) 213 | var timer = time.NewTimer(5*time.Second) 214 | select { 215 | case received := <- incoming: 216 | if received.GetID() != FirstMessage{ 217 | return fmt.Errorf("unexpected message %d", received.GetID()) 218 | } 219 | resultChan <- taskID 220 | case <- timer.C: 221 | return fmt.Errorf("wait message for task %d timeout", taskID) 222 | } 223 | return nil 224 | } --------------------------------------------------------------------------------