github.com/ergo-services/ergo@v1.999.224/tests/saga_dist_test.go (about) 1 package tests 2 3 import ( 4 "fmt" 5 "math/rand" 6 "testing" 7 "time" 8 9 "github.com/ergo-services/ergo" 10 "github.com/ergo-services/ergo/etf" 11 "github.com/ergo-services/ergo/gen" 12 "github.com/ergo-services/ergo/node" 13 "github.com/ergo-services/ergo/proto/dist" 14 ) 15 16 // this test implemets distributed case of computing sum for the given 17 // slice of int numbers 18 // 19 // -> Saga2 (run workers 1..n) 20 // / 21 // Saga1 -> 22 // \ 23 // -> Saga3 (run workers 1..n) 24 // 25 // Saga1 creates a slice of int numbers, split it and sends to the Saga2 and Saga3. 26 // Each saga runs on a separated node. 27 28 // 29 // Saga1 30 // 31 type testSaga1 struct { 32 gen.Saga 33 res chan interface{} 34 result int 35 } 36 37 type testSaga1State struct { 38 txs map[gen.SagaTransactionID]*txvalue 39 } 40 type txvalue struct { 41 res int 42 nexts map[gen.SagaNextID]bool 43 } 44 45 func (gs *testSaga1) InitSaga(process *gen.SagaProcess, args ...etf.Term) (gen.SagaOptions, error) { 46 opts := gen.SagaOptions{} 47 gs.res = make(chan interface{}, 2) 48 process.State = &testSaga1State{ 49 txs: make(map[gen.SagaTransactionID]*txvalue), 50 } 51 return opts, nil 52 } 53 54 func (gs *testSaga1) HandleTxNew(process *gen.SagaProcess, id gen.SagaTransactionID, value interface{}) gen.SagaStatus { 55 state := process.State.(*testSaga1State) 56 task := value.(taskTX) 57 58 txval := &txvalue{ 59 nexts: make(map[gen.SagaNextID]bool), 60 } 61 // split it into two parts (two sagas) 62 values := splitSlice(task.value, len(task.value)/2+1) 63 64 // send to the saga2 65 next := gen.SagaNext{ 66 Saga: saga2_process, 67 Value: values[0], 68 } 69 next_id, err := process.Next(id, next) 70 if err != nil { 71 panic(err) 72 } 73 txval.nexts[next_id] = true 74 75 // send to the saga3 76 if len(values) > 1 { 77 next = gen.SagaNext{ 78 Saga: saga3_process, 79 Value: values[1], 80 } 81 next_id, err := process.Next(id, next) 82 if err != nil { 83 panic(err) 84 } 85 txval.nexts[next_id] = true 86 } 87 88 state.txs[id] = txval 89 return gen.SagaStatusOK 90 } 91 92 func (gs *testSaga1) HandleTxCancel(process *gen.SagaProcess, id gen.SagaTransactionID, reason string) gen.SagaStatus { 93 return gen.SagaStatusOK 94 } 95 96 func (gs *testSaga1) HandleTxResult(process *gen.SagaProcess, id gen.SagaTransactionID, from gen.SagaNextID, result interface{}) gen.SagaStatus { 97 state := process.State.(*testSaga1State) 98 txval := state.txs[id] 99 switch r := result.(type) { 100 case int: 101 txval.res += r 102 case int64: 103 txval.res += int(r) 104 } 105 delete(txval.nexts, from) 106 if len(txval.nexts) == 0 { 107 process.SendResult(id, txval.res) 108 } 109 return gen.SagaStatusOK 110 } 111 112 func (gs *testSaga1) HandleTxDone(process *gen.SagaProcess, id gen.SagaTransactionID, result interface{}) (interface{}, gen.SagaStatus) { 113 state := process.State.(*testSaga1State) 114 txval := state.txs[id] 115 delete(state.txs, id) 116 gs.result += txval.res 117 if len(state.txs) == 0 { 118 gs.res <- gs.result 119 } 120 121 return nil, gen.SagaStatusOK 122 } 123 func (gs *testSaga1) HandleSagaDirect(process *gen.SagaProcess, ref etf.Ref, message interface{}) (interface{}, gen.DirectStatus) { 124 switch m := message.(type) { 125 case task: 126 values := splitSlice(m.value, m.split) 127 fmt.Printf(" process %d txs with %v value(s) each distributing them on Saga2 and Saga3: ", len(values), m.split) 128 for i := range values { 129 txValue := taskTX{ 130 value: values[i], 131 chunks: m.chunks, 132 } 133 process.StartTransaction(gen.SagaTransactionOptions{}, txValue) 134 } 135 136 return nil, nil 137 } 138 139 return nil, fmt.Errorf("unknown request %#v", message) 140 } 141 142 // 143 // Saga2/Saga3 144 // 145 146 var ( 147 saga2_process = gen.ProcessID{ 148 Name: "saga2", 149 Node: "nodeGenSagaDist02@localhost", 150 } 151 152 saga3_process = gen.ProcessID{ 153 Name: "saga3", 154 Node: "nodeGenSagaDist03@localhost", 155 } 156 ) 157 158 type testSagaN struct { 159 gen.Saga 160 } 161 162 type testSagaNState struct { 163 txs map[gen.SagaTransactionID]*txjobs 164 } 165 166 func (gs *testSagaN) InitSaga(process *gen.SagaProcess, args ...etf.Term) (gen.SagaOptions, error) { 167 opts := gen.SagaOptions{ 168 Worker: &testSagaWorkerN{}, 169 } 170 process.State = &testSagaState{ 171 txs: make(map[gen.SagaTransactionID]*txjobs), 172 } 173 return opts, nil 174 } 175 func (gs *testSagaN) HandleTxNew(process *gen.SagaProcess, id gen.SagaTransactionID, value interface{}) gen.SagaStatus { 176 var vv []int 177 if err := etf.TermIntoStruct(value, &vv); err != nil { 178 panic(err) 179 } 180 state := process.State.(*testSagaState) 181 j := txjobs{ 182 jobs: make(map[gen.SagaJobID]bool), 183 } 184 185 values := splitSlice(vv, 5) 186 for i := range values { 187 job_id, err := process.StartJob(id, gen.SagaJobOptions{}, values[i]) 188 if err != nil { 189 return err 190 } 191 j.jobs[job_id] = true 192 } 193 state.txs[id] = &j 194 return gen.SagaStatusOK 195 } 196 197 func (gs *testSagaN) HandleTxCancel(process *gen.SagaProcess, id gen.SagaTransactionID, reason string) gen.SagaStatus { 198 return gen.SagaStatusOK 199 } 200 201 func (gs *testSagaN) HandleTxResult(process *gen.SagaProcess, id gen.SagaTransactionID, from gen.SagaNextID, result interface{}) gen.SagaStatus { 202 return gen.SagaStatusOK 203 } 204 205 func (gs *testSagaN) HandleJobResult(process *gen.SagaProcess, id gen.SagaTransactionID, from gen.SagaJobID, result interface{}) gen.SagaStatus { 206 state := process.State.(*testSagaState) 207 j := state.txs[id] 208 j.result += result.(int) 209 delete(j.jobs, from) 210 211 if len(j.jobs) == 0 { 212 process.SendResult(id, j.result) 213 } 214 215 return gen.SagaStatusOK 216 } 217 218 // 219 // SagaWorkerN 220 // 221 type testSagaWorkerN struct { 222 gen.SagaWorker 223 } 224 225 func (w *testSagaWorkerN) HandleJobStart(process *gen.SagaWorkerProcess, job gen.SagaJob) error { 226 values := job.Value.([]int) 227 result := sumSlice(values) 228 if err := process.SendResult(result); err != nil { 229 panic(err) 230 } 231 return nil 232 } 233 234 func (w *testSagaWorkerN) HandleJobCancel(process *gen.SagaWorkerProcess, reason string) { 235 return 236 } 237 238 func TestSagaDist(t *testing.T) { 239 fmt.Printf("\n=== Test GenSagaDist\n") 240 241 fmt.Printf("Starting node: nodeGenSagaDist01@localhost...") 242 opts1 := node.Options{} 243 protoOptions := node.DefaultProtoOptions() 244 protoOptions.NumHandlers = 2 245 opts1.Proto = dist.CreateProto(protoOptions) 246 node1, _ := ergo.StartNode("nodeGenSagaDist01@localhost", "cookies", opts1) 247 if node1 == nil { 248 t.Fatal("can't start node") 249 return 250 } 251 fmt.Println("OK") 252 fmt.Printf("Starting node: nodeGenSagaDist02@localhost...") 253 opts2 := node.Options{} 254 opts2.Proto = dist.CreateProto(protoOptions) 255 node2, _ := ergo.StartNode("nodeGenSagaDist02@localhost", "cookies", opts2) 256 if node2 == nil { 257 t.Fatal("can't start node") 258 return 259 } 260 fmt.Println("OK") 261 fmt.Printf("Starting node: nodeGenSagaDist03@localhost...") 262 opts3 := node.Options{} 263 opts3.Proto = dist.CreateProto(protoOptions) 264 fmt.Printf("Starting node: nodeGenSagaDist02@localhost...") 265 node3, _ := ergo.StartNode("nodeGenSagaDist03@localhost", "cookies", opts3) 266 if node3 == nil { 267 t.Fatal("can't start node") 268 return 269 } 270 fmt.Println("OK") 271 272 fmt.Printf("... Starting Saga1 processes (on node1): ") 273 saga1 := &testSaga1{} 274 saga1_process, err := node1.Spawn("saga1", gen.ProcessOptions{MailboxSize: 10000}, saga1) 275 if err != nil { 276 t.Fatal(err) 277 } 278 fmt.Println("OK") 279 280 fmt.Printf("... Starting Saga2 processes (on node2): ") 281 saga2 := &testSagaN{} 282 _, err = node2.Spawn("saga2", gen.ProcessOptions{MailboxSize: 10000}, saga2) 283 if err != nil { 284 t.Fatal(err) 285 } 286 fmt.Println("OK") 287 288 fmt.Printf("... Starting Saga3 processes (on node3): ") 289 saga3 := &testSagaN{} 290 _, err = node3.Spawn("saga3", gen.ProcessOptions{MailboxSize: 10000}, saga3) 291 if err != nil { 292 t.Fatal(err) 293 } 294 fmt.Println("OK") 295 296 rand.Seed(time.Now().Unix()) 297 298 slice1 := rand.Perm(1000) 299 sum1 := sumSlice(slice1) 300 saga1.result = 0 301 startTask1 := task{ 302 value: slice1, 303 split: 1, 304 } 305 saga1_process.Direct(startTask1) 306 waitForResultWithValue(t, saga1.res, sum1) 307 308 slice2 := rand.Perm(1000) 309 sum2 := sumSlice(slice2) 310 saga1.result = 0 311 startTask2 := task{ 312 value: slice2, 313 split: 100, 314 } 315 saga1_process.Direct(startTask2) 316 waitForResultWithValue(t, saga1.res, sum2) 317 318 slice3 := rand.Perm(1000) 319 sum3 := sumSlice(slice3) 320 saga1.result = 0 321 startTask3 := task{ 322 value: slice3, 323 split: 1000, 324 } 325 saga1_process.Direct(startTask3) 326 waitForResultWithValue(t, saga1.res, sum3) 327 328 // stop all nodes 329 node3.Stop() 330 node2.Stop() 331 node1.Stop() 332 }