github.com/ergo-services/ergo@v1.999.224/tests/saga_cancel_test.go (about) 1 package tests 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "github.com/ergo-services/ergo" 9 "github.com/ergo-services/ergo/etf" 10 "github.com/ergo-services/ergo/gen" 11 "github.com/ergo-services/ergo/node" 12 ) 13 14 // this test implements cases below 15 // 1. Single saga cancels Tx 16 // 2. Saga1 -> Tx -> Saga2 -> Tx -> Saga3 17 // a) Saga1 cancels Tx 18 // Saga1 -> cancel -> Saga2 -> cancel -> Saga3 19 // b) Saga2 cancels Tx 20 // Saga1 <- cancel <- Saga2 -> cancel -> Saga3 21 // c) Saga3 cancels Tx 22 // Saga1 <- cancel <- Saga2 <- cancel <- Saga3 23 // d) Saga1 sets TrapCancel, Saga2 process/node is going down, Saga1 sends Tx to the Saga4 24 // -> Tx -> Saga4 -> Tx -> Saga3 25 // / 26 // Saga1 <- signal Down <- Saga2 (terminates) -> signal Down -> Saga3 27 28 // 29 // Case 1 30 // 31 32 type taskSagaCancelCase1 struct { 33 workerRes chan interface{} 34 sagaRes chan interface{} 35 } 36 37 type testSagaCancelWorker1 struct { 38 gen.SagaWorker 39 } 40 41 func (w *testSagaCancelWorker1) HandleJobStart(process *gen.SagaWorkerProcess, job gen.SagaJob) error { 42 process.State = job.Value 43 return nil 44 } 45 func (w *testSagaCancelWorker1) HandleJobCancel(process *gen.SagaWorkerProcess, reason string) { 46 if err := process.SendInterim(1); err != gen.ErrSagaTxCanceled { 47 panic("shouldn't be able to send interim result") 48 } 49 if err := process.SendResult(1); err != gen.ErrSagaTxCanceled { 50 panic("shouldn't be able to send the result") 51 } 52 task := process.State.(taskSagaCancelCase1) 53 task.workerRes <- "ok" 54 return 55 } 56 57 type testSagaCancel1 struct { 58 gen.Saga 59 } 60 61 func (gs *testSagaCancel1) InitSaga(process *gen.SagaProcess, args ...etf.Term) (gen.SagaOptions, error) { 62 worker := &testSagaCancelWorker1{} 63 opts := gen.SagaOptions{ 64 Worker: worker, 65 } 66 return opts, nil 67 } 68 69 func (gs *testSagaCancel1) HandleTxNew(process *gen.SagaProcess, id gen.SagaTransactionID, value interface{}) gen.SagaStatus { 70 process.State = value 71 task := process.State.(taskSagaCancelCase1) 72 task.sagaRes <- "startTX" 73 74 _, err := process.StartJob(id, gen.SagaJobOptions{}, value) 75 if err != nil { 76 panic(err) 77 } 78 task.workerRes <- "startWorker" 79 if err := process.CancelTransaction(id, "test cancel"); err != nil { 80 panic(err) 81 } 82 83 // try to cancel unknown TX 84 if err := process.CancelTransaction(gen.SagaTransactionID{}, "bla bla"); err != gen.ErrSagaTxUnknown { 85 panic("must be ErrSagaTxUnknown") 86 } 87 task.sagaRes <- "cancelTX" 88 return gen.SagaStatusOK 89 } 90 91 func (gs *testSagaCancel1) HandleTxCancel(process *gen.SagaProcess, id gen.SagaTransactionID, reason string) gen.SagaStatus { 92 task := process.State.(taskSagaCancelCase1) 93 if reason == "test cancel" { 94 task.sagaRes <- "ok" 95 } 96 return gen.SagaStatusOK 97 } 98 99 func (gs *testSagaCancel1) HandleTxResult(process *gen.SagaProcess, id gen.SagaTransactionID, from gen.SagaNextID, result interface{}) gen.SagaStatus { 100 return gen.SagaStatusOK 101 } 102 103 func (gs *testSagaCancel1) HandleSagaDirect(process *gen.SagaProcess, ref etf.Ref, message interface{}) (interface{}, gen.DirectStatus) { 104 105 process.StartTransaction(gen.SagaTransactionOptions{}, message) 106 return nil, nil 107 } 108 109 func TestSagaCancelSimple(t *testing.T) { 110 111 fmt.Printf("\n=== Test GenSagaCancelSimple\n") 112 fmt.Printf("Starting node: nodeGenSagaCancelSimple01@localhost...") 113 114 node, _ := ergo.StartNode("nodeGenSagaCancelSimple01@localhost", "cookies", node.Options{}) 115 if node == nil { 116 t.Fatal("can't start node") 117 return 118 } 119 fmt.Println("OK") 120 defer node.Stop() 121 122 fmt.Printf("... Starting Saga processes: ") 123 saga := &testSagaCancel1{} 124 saga_process, err := node.Spawn("saga", gen.ProcessOptions{MailboxSize: 10000}, saga) 125 if err != nil { 126 t.Fatal(err) 127 } 128 fmt.Println("OK") 129 130 task := taskSagaCancelCase1{ 131 workerRes: make(chan interface{}, 2), 132 sagaRes: make(chan interface{}, 2), 133 } 134 _, err = saga_process.Direct(task) 135 if err != nil { 136 t.Fatal(err) 137 } 138 fmt.Printf("... Start new TX on saga: ") 139 waitForResultWithValue(t, task.sagaRes, "startTX") 140 fmt.Printf("... Start new worker on saga: ") 141 waitForResultWithValue(t, task.workerRes, "startWorker") 142 fmt.Printf("... Cancel TX on saga: ") 143 waitForResultWithValue(t, task.sagaRes, "cancelTX") 144 fmt.Printf("... Saga worker handled TX cancelation: ") 145 waitForResultWithValue(t, task.workerRes, "ok") 146 fmt.Printf("... Saga handled TX cancelation: ") 147 waitForResultWithValue(t, task.sagaRes, "ok") 148 } 149 150 // 151 // Case 2.a 152 // Node1.Saga1 -> Tx -> Node2.Saga2 -> Tx -> Node3.Saga3 153 // Node1.Saga1 cancels Tx 154 // Node1.Saga1 -> cancel -> Node2.Saga2 -> cancel -> Node3Saga3 155 // 156 157 type testSagaCancelWorker2 struct { 158 gen.SagaWorker 159 } 160 161 func (w *testSagaCancelWorker2) HandleJobStart(process *gen.SagaWorkerProcess, job gen.SagaJob) error { 162 process.State = job.Value 163 return nil 164 } 165 func (w *testSagaCancelWorker2) HandleJobCancel(process *gen.SagaWorkerProcess, reason string) { 166 if err := process.SendInterim(1); err != gen.ErrSagaTxCanceled { 167 panic("shouldn't be able to send interim result") 168 } 169 if err := process.SendResult(1); err != gen.ErrSagaTxCanceled { 170 panic("shouldn't be able to send the result") 171 } 172 args := process.State.(testSagaCancel2Args) 173 args.workerRes <- reason 174 return 175 } 176 177 type testSagaCancel2 struct { 178 gen.Saga 179 } 180 181 type testSagaCancel2Args struct { 182 workerRes chan interface{} 183 sagaRes chan interface{} 184 } 185 186 func (gs *testSagaCancel2) InitSaga(process *gen.SagaProcess, args ...etf.Term) (gen.SagaOptions, error) { 187 worker := &testSagaCancelWorker2{} 188 opts := gen.SagaOptions{ 189 Worker: worker, 190 } 191 process.State = args[0] // testSagaCancel2Args 192 return opts, nil 193 } 194 195 func (gs *testSagaCancel2) HandleTxNew(process *gen.SagaProcess, id gen.SagaTransactionID, value interface{}) gen.SagaStatus { 196 args := process.State.(testSagaCancel2Args) 197 args.sagaRes <- id 198 199 _, err := process.StartJob(id, gen.SagaJobOptions{}, process.State) 200 if err != nil { 201 panic(err) 202 } 203 args.workerRes <- id 204 205 next := gen.SagaNext{} 206 switch process.Name() { 207 case "saga1": 208 trapCancel, _ := value.(bool) 209 if trapCancel { 210 // case 2.D 211 next.TrapCancel = true 212 } 213 next.Saga = gen.ProcessID{Name: "saga2", Node: "node2GenSagaCancelCases@localhost"} 214 case "saga2": 215 next.Saga = gen.ProcessID{Name: "saga3", Node: "node3GenSagaCancelCases@localhost"} 216 default: 217 return gen.SagaStatusOK 218 } 219 process.Next(id, next) 220 221 return gen.SagaStatusOK 222 } 223 224 func (gs *testSagaCancel2) HandleTxCancel(process *gen.SagaProcess, id gen.SagaTransactionID, reason string) gen.SagaStatus { 225 args := process.State.(testSagaCancel2Args) 226 args.sagaRes <- reason 227 return gen.SagaStatusOK 228 } 229 230 func (gs *testSagaCancel2) HandleTxResult(process *gen.SagaProcess, id gen.SagaTransactionID, from gen.SagaNextID, result interface{}) gen.SagaStatus { 231 return gen.SagaStatusOK 232 } 233 234 type testSagaStartTX struct { 235 TrapCancel bool 236 } 237 type testSagaCancelTX struct { 238 ID gen.SagaTransactionID 239 Reason string 240 } 241 242 func (gs *testSagaCancel2) HandleSagaInfo(process *gen.SagaProcess, message etf.Term) gen.ServerStatus { 243 args := process.State.(testSagaCancel2Args) 244 switch m := message.(type) { 245 case gen.MessageSagaCancel: 246 args.sagaRes <- m.Reason 247 next := gen.SagaNext{} 248 next.Saga = gen.ProcessID{Name: "saga4", Node: "node4GenSagaCancelCases@localhost"} 249 process.Next(m.TransactionID, next) 250 args.sagaRes <- m.TransactionID 251 } 252 return gen.ServerStatusOK 253 } 254 255 func (gs *testSagaCancel2) HandleSagaDirect(process *gen.SagaProcess, ref etf.Ref, message interface{}) (interface{}, gen.DirectStatus) { 256 257 switch m := message.(type) { 258 case testSagaStartTX: 259 return process.StartTransaction(gen.SagaTransactionOptions{}, m.TrapCancel), nil 260 case testSagaCancelTX: 261 return nil, process.CancelTransaction(m.ID, m.Reason) 262 } 263 return nil, nil 264 } 265 266 func TestSagaCancelCases(t *testing.T) { 267 fmt.Printf("\n=== Test GenSagaCancelCases\n") 268 269 fmt.Printf("Starting node: node1GenSagaCancelCases@localhost...") 270 node1, _ := ergo.StartNode("node1GenSagaCancelCases@localhost", "cookies", node.Options{}) 271 272 if node1 == nil { 273 t.Fatal("can't start node") 274 return 275 } 276 fmt.Println("OK") 277 defer node1.Stop() 278 279 fmt.Printf("Starting node: node2GenSagaCancelCases@localhost...") 280 node2, _ := ergo.StartNode("node2GenSagaCancelCases@localhost", "cookies", node.Options{}) 281 282 if node2 == nil { 283 t.Fatal("can't start node") 284 return 285 } 286 fmt.Println("OK") 287 defer node2.Stop() 288 289 fmt.Printf("Starting node: node3GenSagaCancelCases@localhost...") 290 node3, _ := ergo.StartNode("node3GenSagaCancelCases@localhost", "cookies", node.Options{}) 291 292 if node3 == nil { 293 t.Fatal("can't start node") 294 return 295 } 296 fmt.Println("OK") 297 defer node3.Stop() 298 299 args1 := testSagaCancel2Args{ 300 workerRes: make(chan interface{}, 2), 301 sagaRes: make(chan interface{}, 2), 302 } 303 304 fmt.Printf("Starting saga1 on node1GenSagaCancelCases@localhost...") 305 saga1 := &testSagaCancel2{} 306 saga1_process, err := node1.Spawn("saga1", gen.ProcessOptions{MailboxSize: 10000}, saga1, args1) 307 if err != nil { 308 t.Fatal(err) 309 } 310 fmt.Println("OK") 311 312 args2 := testSagaCancel2Args{ 313 workerRes: make(chan interface{}, 2), 314 sagaRes: make(chan interface{}, 2), 315 } 316 fmt.Printf("Starting saga2 on node2GenSagaCancelCases@localhost...") 317 saga2 := &testSagaCancel2{} 318 saga2_process, err := node2.Spawn("saga2", gen.ProcessOptions{MailboxSize: 10000}, saga2, args2) 319 if err != nil { 320 t.Fatal(err) 321 } 322 fmt.Println("OK") 323 324 args3 := testSagaCancel2Args{ 325 workerRes: make(chan interface{}, 2), 326 sagaRes: make(chan interface{}, 2), 327 } 328 fmt.Printf("Starting saga3 on node3GenSagaCancelCases@localhost...") 329 saga3 := &testSagaCancel2{} 330 saga3_process, err := node3.Spawn("saga3", gen.ProcessOptions{MailboxSize: 10000}, saga3, args3) 331 if err != nil { 332 t.Fatal(err) 333 } 334 fmt.Println("OK") 335 336 // 337 // case 2.A 338 // 339 fmt.Println(" Case A (cancel TX on Node1.Saga1): Node1.Saga1 -> cancel -> Node2.Saga2 -> cancel -> Node3.Saga3") 340 341 ValueTXID, err := saga1_process.Direct(testSagaStartTX{}) 342 if err != nil { 343 t.Fatal(err) 344 } 345 TXID, ok := ValueTXID.(gen.SagaTransactionID) 346 if !ok { 347 t.Fatal("not a gen.SagaTransactionID") 348 } 349 350 fmt.Printf("... Start new TX %v on saga1: ", TXID) 351 waitForResultWithValue(t, args1.sagaRes, TXID) 352 fmt.Printf("... Start new worker on saga1 with TX %v: ", TXID) 353 waitForResultWithValue(t, args1.workerRes, TXID) 354 355 fmt.Printf("... Start new TX %v on saga2: ", TXID) 356 waitForResultWithValue(t, args2.sagaRes, TXID) 357 fmt.Printf("... Start new worker on saga2 with TX %v: ", TXID) 358 waitForResultWithValue(t, args2.workerRes, TXID) 359 360 fmt.Printf("... Start new TX %v on saga3: ", TXID) 361 waitForResultWithValue(t, args3.sagaRes, TXID) 362 fmt.Printf("... Start new worker on saga3 with TX %v: ", TXID) 363 waitForResultWithValue(t, args3.workerRes, TXID) 364 365 fmt.Printf("... Cancel TX %v on saga1: ", TXID) 366 cancelReason := "cancel case1" 367 _, err = saga1_process.Direct(testSagaCancelTX{ID: TXID, Reason: cancelReason}) 368 if err != nil { 369 t.Fatal(err) 370 } 371 waitForResultWithValue(t, args1.sagaRes, cancelReason) 372 fmt.Printf("... saga1 cancels TX %v on its worker: ", TXID) 373 waitForResultWithValue(t, args1.workerRes, cancelReason) 374 fmt.Printf("... cancels TX %v on saga2: ", TXID) 375 waitForResultWithValue(t, args2.sagaRes, cancelReason) 376 fmt.Printf("... saga2 cancels TX %v on its worker: ", TXID) 377 waitForResultWithValue(t, args2.workerRes, cancelReason) 378 fmt.Printf("... cancels TX %v on saga3: ", TXID) 379 waitForResultWithValue(t, args3.sagaRes, cancelReason) 380 fmt.Printf("... saga3 cancels TX %v on its worker: ", TXID) 381 waitForResultWithValue(t, args3.workerRes, cancelReason) 382 // 383 // case 2.B 384 // 385 386 fmt.Println(" Case B (cancel TX on Node.Saga2): Node1.Saga1 <- cancel <- Node2.Saga2 -> cancel -> Node3.Saga3") 387 388 ValueTXID, err = saga1_process.Direct(testSagaStartTX{}) 389 if err != nil { 390 t.Fatal(err) 391 } 392 TXID, ok = ValueTXID.(gen.SagaTransactionID) 393 if !ok { 394 t.Fatal("not a gen.SagaTransactionID") 395 } 396 397 fmt.Printf("... Start new TX %v on saga1: ", TXID) 398 waitForResultWithValue(t, args1.sagaRes, TXID) 399 fmt.Printf("... Start new worker on saga1 with TX %v: ", TXID) 400 waitForResultWithValue(t, args1.workerRes, TXID) 401 402 fmt.Printf("... Start new TX %v on saga2: ", TXID) 403 waitForResultWithValue(t, args2.sagaRes, TXID) 404 fmt.Printf("... Start new worker on saga2 with TX %v: ", TXID) 405 waitForResultWithValue(t, args2.workerRes, TXID) 406 407 fmt.Printf("... Start new TX %v on saga3: ", TXID) 408 waitForResultWithValue(t, args3.sagaRes, TXID) 409 fmt.Printf("... Start new worker on saga3 with TX %v: ", TXID) 410 waitForResultWithValue(t, args3.workerRes, TXID) 411 412 fmt.Printf("... Cancel TX %v on saga2: ", TXID) 413 cancelReason = "cancel case2" 414 _, err = saga2_process.Direct(testSagaCancelTX{ID: TXID, Reason: cancelReason}) 415 if err != nil { 416 t.Fatal(err) 417 } 418 waitForResultWithValue(t, args2.sagaRes, cancelReason) 419 fmt.Printf("... saga2 cancels TX %v on its worker: ", TXID) 420 waitForResultWithValue(t, args2.workerRes, cancelReason) 421 fmt.Printf("... cancels TX %v on saga1: ", TXID) 422 waitForResultWithValue(t, args1.sagaRes, cancelReason) 423 fmt.Printf("... saga1 cancels TX %v on its worker: ", TXID) 424 waitForResultWithValue(t, args1.workerRes, cancelReason) 425 fmt.Printf("... cancels TX %v on saga3: ", TXID) 426 waitForResultWithValue(t, args3.sagaRes, cancelReason) 427 fmt.Printf("... saga3 cancels TX %v on its worker: ", TXID) 428 waitForResultWithValue(t, args3.workerRes, cancelReason) 429 // 430 // case 2.C 431 // 432 fmt.Println(" Case C (cancel TX on Node.Saga3): Node1.Saga1 <- cancel <- Node2.Saga2 <- cancel <- Node3.Saga3") 433 434 ValueTXID, err = saga1_process.Direct(testSagaStartTX{}) 435 if err != nil { 436 t.Fatal(err) 437 } 438 TXID, ok = ValueTXID.(gen.SagaTransactionID) 439 if !ok { 440 t.Fatal("not a gen.SagaTransactionID") 441 } 442 443 fmt.Printf("... Start new TX %v on saga1: ", TXID) 444 waitForResultWithValue(t, args1.sagaRes, TXID) 445 fmt.Printf("... Start new worker on saga1 with TX %v: ", TXID) 446 waitForResultWithValue(t, args1.workerRes, TXID) 447 448 fmt.Printf("... Start new TX %v on saga2: ", TXID) 449 waitForResultWithValue(t, args2.sagaRes, TXID) 450 fmt.Printf("... Start new worker on saga2 with TX %v: ", TXID) 451 waitForResultWithValue(t, args2.workerRes, TXID) 452 453 fmt.Printf("... Start new TX %v on saga3: ", TXID) 454 waitForResultWithValue(t, args3.sagaRes, TXID) 455 fmt.Printf("... Start new worker on saga3 with TX %v: ", TXID) 456 waitForResultWithValue(t, args3.workerRes, TXID) 457 458 fmt.Printf("... Cancel TX %v on saga3: ", TXID) 459 cancelReason = "cancel case3" 460 _, err = saga3_process.Direct(testSagaCancelTX{ID: TXID, Reason: cancelReason}) 461 if err != nil { 462 t.Fatal(err) 463 } 464 waitForResultWithValue(t, args3.sagaRes, cancelReason) 465 fmt.Printf("... saga3 cancels TX %v on its worker: ", TXID) 466 waitForResultWithValue(t, args3.workerRes, cancelReason) 467 fmt.Printf("... cancels TX %v on saga2: ", TXID) 468 waitForResultWithValue(t, args2.sagaRes, cancelReason) 469 fmt.Printf("... saga2 cancels TX %v on its worker: ", TXID) 470 waitForResultWithValue(t, args2.workerRes, cancelReason) 471 fmt.Printf("... cancels TX %v on saga1: ", TXID) 472 waitForResultWithValue(t, args1.sagaRes, cancelReason) 473 fmt.Printf("... saga1 cancels TX %v on its worker: ", TXID) 474 waitForResultWithValue(t, args1.workerRes, cancelReason) 475 // 476 // Case 2.D 477 // 478 fmt.Println(" Case D: Saga1 sets TrapCancel, Saga2 process/node is going down, Saga1 sends Tx to the Saga4:") 479 480 fmt.Printf("Starting node: node4GenSagaCancelCases@localhost...") 481 node4, _ := ergo.StartNode("node4GenSagaCancelCases@localhost", "cookies", node.Options{}) 482 483 if node4 == nil { 484 t.Fatal("can't start node") 485 return 486 } 487 fmt.Println("OK") 488 defer node4.Stop() 489 490 args4 := testSagaCancel2Args{ 491 workerRes: make(chan interface{}, 2), 492 sagaRes: make(chan interface{}, 2), 493 } 494 fmt.Printf("Starting saga4 on node4GenSagaCancelCases@localhost...") 495 saga4 := &testSagaCancel2{} 496 saga4_process, err := node4.Spawn("saga4", gen.ProcessOptions{MailboxSize: 10000}, saga4, args4) 497 if err != nil { 498 t.Fatal(err) 499 } 500 fmt.Println("OK", saga4_process.Self()) 501 502 // TrapCancel will be enabled on the Saga1 only 503 ValueTXID, err = saga1_process.Direct(testSagaStartTX{TrapCancel: true}) 504 if err != nil { 505 t.Fatal(err) 506 } 507 TXID, ok = ValueTXID.(gen.SagaTransactionID) 508 if !ok { 509 t.Fatal("not a gen.SagaTransactionID") 510 } 511 512 fmt.Printf("... Start new TX %v on saga1: ", TXID) 513 waitForResultWithValue(t, args1.sagaRes, TXID) 514 fmt.Printf("... Start new worker on saga1 with TX %v: ", TXID) 515 waitForResultWithValue(t, args1.workerRes, TXID) 516 517 fmt.Printf("... Start new TX %v on saga2: ", TXID) 518 waitForResultWithValue(t, args2.sagaRes, TXID) 519 fmt.Printf("... Start new worker on saga2 with TX %v: ", TXID) 520 waitForResultWithValue(t, args2.workerRes, TXID) 521 522 fmt.Printf("... Start new TX %v on saga3: ", TXID) 523 waitForResultWithValue(t, args3.sagaRes, TXID) 524 fmt.Printf("... Start new worker on saga3 with TX %v: ", TXID) 525 waitForResultWithValue(t, args3.workerRes, TXID) 526 527 fmt.Printf("... Terminate saga2 process: ") 528 time.Sleep(200 * time.Millisecond) 529 saga2_process.Kill() 530 if err := saga2_process.WaitWithTimeout(2 * time.Second); err != nil { 531 t.Fatal(err) 532 } 533 fmt.Println("OK") 534 535 fmt.Printf("... handle trapped cancelation TX %v on saga1: ", TXID) 536 cancelReason = fmt.Sprintf("next saga %s is down", gen.ProcessID{Name: saga2_process.Name(), Node: node2.Name()}) 537 waitForResultWithValue(t, args1.sagaRes, cancelReason) 538 fmt.Printf("... cancels TX %v on saga3: ", TXID) 539 cancelReason = fmt.Sprintf("parent saga %s is down", saga2_process.Self()) 540 waitForResultWithValue(t, args3.sagaRes, cancelReason) 541 fmt.Printf("... saga3 cancels TX %v on its worker: ", TXID) 542 waitForResultWithValue(t, args3.workerRes, cancelReason) 543 544 fmt.Printf("... forward (trapped) canceled TX %v on saga1 to Saga4: ", TXID) 545 waitForResultWithValue(t, args1.sagaRes, TXID) 546 fmt.Printf("... Start new TX %v on saga4: ", TXID) 547 waitForResultWithValue(t, args4.sagaRes, TXID) 548 fmt.Printf("... Start new worker on saga4 with TX %v: ", TXID) 549 waitForResultWithValue(t, args4.workerRes, TXID) 550 }