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  }