github.com/Jeffail/benthos/v3@v3.65.0/lib/output/broker_test.go (about)

     1  package output
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/Jeffail/benthos/v3/lib/log"
    10  	"github.com/Jeffail/benthos/v3/lib/message"
    11  	"github.com/Jeffail/benthos/v3/lib/metrics"
    12  	"github.com/Jeffail/benthos/v3/lib/processor"
    13  	"github.com/Jeffail/benthos/v3/lib/types"
    14  )
    15  
    16  func TestFanOutBroker(t *testing.T) {
    17  	dir := t.TempDir()
    18  
    19  	outOne, outTwo := NewConfig(), NewConfig()
    20  	outOne.Type, outTwo.Type = TypeFiles, TypeFiles
    21  	outOne.Files.Path = filepath.Join(dir, "one", `foo-${!count("1s")}.txt`)
    22  	outTwo.Files.Path = filepath.Join(dir, "two", `bar-${!count("2s")}.txt`)
    23  
    24  	procOne, procTwo := processor.NewConfig(), processor.NewConfig()
    25  	procOne.Type, procTwo.Type = processor.TypeText, processor.TypeText
    26  	procOne.Text.Operator = "prepend"
    27  	procOne.Text.Value = "one-"
    28  	procTwo.Text.Operator = "prepend"
    29  	procTwo.Text.Value = "two-"
    30  
    31  	outOne.Processors = append(outOne.Processors, procOne)
    32  	outTwo.Processors = append(outTwo.Processors, procTwo)
    33  
    34  	conf := NewConfig()
    35  	conf.Type = TypeBroker
    36  	conf.Broker.Pattern = "fan_out"
    37  	conf.Broker.Outputs = append(conf.Broker.Outputs, outOne, outTwo)
    38  
    39  	s, err := New(conf, nil, log.Noop(), metrics.Noop())
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  
    44  	sendChan := make(chan types.Transaction)
    45  	resChan := make(chan types.Response)
    46  	if err = s.Consume(sendChan); err != nil {
    47  		t.Fatal(err)
    48  	}
    49  
    50  	defer func() {
    51  		s.CloseAsync()
    52  		if err := s.WaitForClose(time.Second); err != nil {
    53  			t.Error(err)
    54  		}
    55  	}()
    56  
    57  	inputs := []string{
    58  		"first", "second", "third",
    59  	}
    60  	expFiles := map[string]string{
    61  		"./one/foo-1.txt": "one-first",
    62  		"./one/foo-2.txt": "one-second",
    63  		"./one/foo-3.txt": "one-third",
    64  		"./two/bar-1.txt": "two-first",
    65  		"./two/bar-2.txt": "two-second",
    66  		"./two/bar-3.txt": "two-third",
    67  	}
    68  
    69  	for _, input := range inputs {
    70  		testMsg := message.New([][]byte{[]byte(input)})
    71  		select {
    72  		case sendChan <- types.NewTransaction(testMsg, resChan):
    73  		case <-time.After(time.Second):
    74  			t.Fatal("Action timed out")
    75  		}
    76  
    77  		select {
    78  		case res := <-resChan:
    79  			if res.Error() != nil {
    80  				t.Fatal(res.Error())
    81  			}
    82  		case <-time.After(time.Second):
    83  			t.Fatal("Action timed out")
    84  		}
    85  	}
    86  
    87  	for k, exp := range expFiles {
    88  		k = filepath.Join(dir, k)
    89  		fileBytes, err := os.ReadFile(k)
    90  		if err != nil {
    91  			t.Errorf("Expected file '%v' could not be read: %v", k, err)
    92  			continue
    93  		}
    94  		if act := string(fileBytes); exp != act {
    95  			t.Errorf("Wrong contents for file '%v': %v != %v", k, act, exp)
    96  		}
    97  	}
    98  }
    99  
   100  func TestRoundRobinBroker(t *testing.T) {
   101  	dir := t.TempDir()
   102  
   103  	outOne, outTwo := NewConfig(), NewConfig()
   104  	outOne.Type, outTwo.Type = TypeFiles, TypeFiles
   105  	outOne.Files.Path = filepath.Join(dir, "one", `foo-${!count("rrfoo")}.txt`)
   106  	outTwo.Files.Path = filepath.Join(dir, "two", `bar-${!count("rrbar")}.txt`)
   107  
   108  	procOne, procTwo := processor.NewConfig(), processor.NewConfig()
   109  	procOne.Type, procTwo.Type = processor.TypeText, processor.TypeText
   110  	procOne.Text.Operator = "prepend"
   111  	procOne.Text.Value = "one-"
   112  	procTwo.Text.Operator = "prepend"
   113  	procTwo.Text.Value = "two-"
   114  
   115  	outOne.Processors = append(outOne.Processors, procOne)
   116  	outTwo.Processors = append(outTwo.Processors, procTwo)
   117  
   118  	conf := NewConfig()
   119  	conf.Type = TypeBroker
   120  	conf.Broker.Pattern = "round_robin"
   121  	conf.Broker.Outputs = append(conf.Broker.Outputs, outOne, outTwo)
   122  
   123  	s, err := New(conf, nil, log.Noop(), metrics.Noop())
   124  	if err != nil {
   125  		t.Fatal(err)
   126  	}
   127  
   128  	sendChan := make(chan types.Transaction)
   129  	resChan := make(chan types.Response)
   130  	if err = s.Consume(sendChan); err != nil {
   131  		t.Fatal(err)
   132  	}
   133  
   134  	t.Cleanup(func() {
   135  		s.CloseAsync()
   136  		if err := s.WaitForClose(time.Second); err != nil {
   137  			t.Error(err)
   138  		}
   139  	})
   140  
   141  	inputs := []string{
   142  		"first", "second", "third", "fourth",
   143  	}
   144  	expFiles := map[string]string{
   145  		"./one/foo-1.txt": "one-first",
   146  		"./one/foo-2.txt": "one-third",
   147  		"./two/bar-1.txt": "two-second",
   148  		"./two/bar-2.txt": "two-fourth",
   149  	}
   150  
   151  	for _, input := range inputs {
   152  		testMsg := message.New([][]byte{[]byte(input)})
   153  		select {
   154  		case sendChan <- types.NewTransaction(testMsg, resChan):
   155  		case <-time.After(time.Second):
   156  			t.Fatal("Action timed out")
   157  		}
   158  
   159  		select {
   160  		case res := <-resChan:
   161  			if res.Error() != nil {
   162  				t.Fatal(res.Error())
   163  			}
   164  		case <-time.After(time.Second):
   165  			t.Fatal("Action timed out")
   166  		}
   167  	}
   168  
   169  	for k, exp := range expFiles {
   170  		k = filepath.Join(dir, k)
   171  		fileBytes, err := os.ReadFile(k)
   172  		if err != nil {
   173  			t.Errorf("Expected file '%v' could not be read: %v", k, err)
   174  			continue
   175  		}
   176  		if act := string(fileBytes); exp != act {
   177  			t.Errorf("Wrong contents for file '%v': %v != %v", k, act, exp)
   178  		}
   179  	}
   180  }
   181  
   182  func TestGreedyBroker(t *testing.T) {
   183  	dir := t.TempDir()
   184  
   185  	outOne, outTwo := NewConfig(), NewConfig()
   186  	outOne.Type, outTwo.Type = TypeFiles, TypeFiles
   187  	outOne.Files.Path = filepath.Join(dir, "one", `foo-${!count("gfoo")}.txt`)
   188  	outTwo.Files.Path = filepath.Join(dir, "two", `bar-${!count("gbar")}.txt`)
   189  
   190  	procOne, procTwo := processor.NewConfig(), processor.NewConfig()
   191  	procOne.Type, procTwo.Type = processor.TypeText, processor.TypeText
   192  	procOne.Text.Operator = "prepend"
   193  	procOne.Text.Value = "one-"
   194  	procTwo.Text.Operator = "prepend"
   195  	procTwo.Text.Value = "two-"
   196  
   197  	outOne.Processors = append(outOne.Processors, procOne)
   198  	outTwo.Processors = append(outTwo.Processors, procTwo)
   199  
   200  	procOne, procTwo = processor.NewConfig(), processor.NewConfig()
   201  	procOne.Type, procTwo.Type = processor.TypeSleep, processor.TypeSleep
   202  	procOne.Sleep.Duration = "50ms"
   203  	procTwo.Sleep.Duration = "50ms"
   204  
   205  	outOne.Processors = append(outOne.Processors, procOne)
   206  	outTwo.Processors = append(outTwo.Processors, procTwo)
   207  
   208  	conf := NewConfig()
   209  	conf.Type = TypeBroker
   210  	conf.Broker.Pattern = "greedy"
   211  	conf.Broker.Outputs = append(conf.Broker.Outputs, outOne, outTwo)
   212  
   213  	s, err := New(conf, nil, log.Noop(), metrics.Noop())
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  
   218  	sendChan := make(chan types.Transaction)
   219  	resChan := make(chan types.Response)
   220  	if err = s.Consume(sendChan); err != nil {
   221  		t.Fatal(err)
   222  	}
   223  
   224  	defer func() {
   225  		s.CloseAsync()
   226  		if err := s.WaitForClose(time.Second); err != nil {
   227  			t.Error(err)
   228  		}
   229  	}()
   230  
   231  	inputs := []string{
   232  		"first", "second", "third", "fourth",
   233  	}
   234  	expFiles := map[string][2]string{
   235  		"./one/foo-1.txt": {"one-first", "one-second"},
   236  		"./one/foo-2.txt": {"one-third", "one-fourth"},
   237  		"./two/bar-1.txt": {"two-first", "two-second"},
   238  		"./two/bar-2.txt": {"two-third", "two-fourth"},
   239  	}
   240  
   241  	for _, input := range inputs {
   242  		testMsg := message.New([][]byte{[]byte(input)})
   243  		select {
   244  		case sendChan <- types.NewTransaction(testMsg, resChan):
   245  		case <-time.After(time.Second):
   246  			t.Fatal("Action timed out")
   247  		}
   248  
   249  		select {
   250  		case res := <-resChan:
   251  			if res.Error() != nil {
   252  				t.Fatal(res.Error())
   253  			}
   254  		case <-time.After(time.Second):
   255  			t.Fatal("Action timed out")
   256  		}
   257  	}
   258  
   259  	for k, exp := range expFiles {
   260  		k = filepath.Join(dir, k)
   261  		fileBytes, err := os.ReadFile(k)
   262  		if err != nil {
   263  			t.Errorf("Expected file '%v' could not be read: %v", k, err)
   264  			continue
   265  		}
   266  		if act := string(fileBytes); exp[0] != act && exp[1] != act {
   267  			t.Errorf("Wrong contents for file '%v': %v != (%v || %v)", k, act, exp[0], exp[1])
   268  		}
   269  	}
   270  }
   271  
   272  func TestTryBroker(t *testing.T) {
   273  	dir := t.TempDir()
   274  
   275  	outOne, outTwo, outThree := NewConfig(), NewConfig(), NewConfig()
   276  	outOne.Type, outTwo.Type, outThree.Type = TypeHTTPClient, TypeFiles, TypeFile
   277  	outOne.HTTPClient.URL = "http://localhost:11111111/badurl"
   278  	outOne.HTTPClient.NumRetries = 1
   279  	outOne.HTTPClient.Retry = "1ms"
   280  	outTwo.Files.Path = filepath.Join(dir, "two", `bar-${!count("tfoo")}-${!count("tbar")}.txt`)
   281  	outThree.File.Path = "/dev/null"
   282  
   283  	procOne, procTwo, procThree := processor.NewConfig(), processor.NewConfig(), processor.NewConfig()
   284  	procOne.Type, procTwo.Type, procThree.Type = processor.TypeText, processor.TypeText, processor.TypeText
   285  	procOne.Text.Operator = "prepend"
   286  	procOne.Text.Value = "this-should-never-appear ${!count(\"tfoo\")}"
   287  	procTwo.Text.Operator = "prepend"
   288  	procTwo.Text.Value = "two-"
   289  	procThree.Text.Operator = "prepend"
   290  	procThree.Text.Value = "this-should-never-appear ${!count(\"tbar\")}"
   291  
   292  	outOne.Processors = append(outOne.Processors, procOne)
   293  	outTwo.Processors = append(outTwo.Processors, procTwo)
   294  	outThree.Processors = append(outThree.Processors, procThree)
   295  
   296  	conf := NewConfig()
   297  	conf.Type = TypeBroker
   298  	conf.Broker.Pattern = "try"
   299  	conf.Broker.Outputs = append(conf.Broker.Outputs, outOne, outTwo, outThree)
   300  
   301  	s, err := New(conf, nil, log.Noop(), metrics.Noop())
   302  	if err != nil {
   303  		t.Fatal(err)
   304  	}
   305  
   306  	sendChan := make(chan types.Transaction)
   307  	resChan := make(chan types.Response)
   308  	if err = s.Consume(sendChan); err != nil {
   309  		t.Fatal(err)
   310  	}
   311  
   312  	defer func() {
   313  		s.CloseAsync()
   314  		if err := s.WaitForClose(time.Second); err != nil {
   315  			t.Error(err)
   316  		}
   317  	}()
   318  
   319  	inputs := []string{
   320  		"first", "second", "third", "fourth",
   321  	}
   322  	expFiles := map[string]string{
   323  		"./two/bar-2-1.txt": "two-first",
   324  		"./two/bar-4-2.txt": "two-second",
   325  		"./two/bar-6-3.txt": "two-third",
   326  		"./two/bar-8-4.txt": "two-fourth",
   327  	}
   328  
   329  	for _, input := range inputs {
   330  		testMsg := message.New([][]byte{[]byte(input)})
   331  		select {
   332  		case sendChan <- types.NewTransaction(testMsg, resChan):
   333  		case <-time.After(time.Second * 2):
   334  			t.Fatal("Action timed out")
   335  		}
   336  
   337  		select {
   338  		case res := <-resChan:
   339  			if res.Error() != nil {
   340  				t.Fatal(res.Error())
   341  			}
   342  		case <-time.After(time.Second * 2):
   343  			t.Fatal("Action timed out")
   344  		}
   345  	}
   346  
   347  	for k, exp := range expFiles {
   348  		k = filepath.Join(dir, k)
   349  		fileBytes, err := os.ReadFile(k)
   350  		if err != nil {
   351  			t.Errorf("Expected file '%v' could not be read: %v", k, err)
   352  			continue
   353  		}
   354  		if act := string(fileBytes); exp != act {
   355  			t.Errorf("Wrong contents for file '%v': %v != %v", k, act, exp)
   356  		}
   357  	}
   358  }