func ProcessDirectoryInputSpec(c gs.Context) { t := &pipeline_ts.SimpleT{} ctrl := gomock.NewController(t) defer ctrl.Finish() pConfig := NewPipelineConfig(nil) ith := new(plugins_ts.InputTestHelper) ith.Msg = pipeline_ts.GetTestMessage() ith.Pack = NewPipelinePack(pConfig.InputRecycleChan()) // set up mock helper, decoder set, and packSupply channel ith.MockHelper = pipelinemock.NewMockPluginHelper(ctrl) ith.MockInputRunner = pipelinemock.NewMockInputRunner(ctrl) ith.MockDeliverer = pipelinemock.NewMockDeliverer(ctrl) ith.MockSplitterRunner = pipelinemock.NewMockSplitterRunner(ctrl) ith.PackSupply = make(chan *PipelinePack, 1) ith.PackSupply <- ith.Pack err := pConfig.RegisterDefault("NullSplitter") c.Assume(err, gs.IsNil) c.Specify("A ProcessDirectoryInput", func() { pdiInput := ProcessDirectoryInput{} pdiInput.SetPipelineConfig(pConfig) config := pdiInput.ConfigStruct().(*ProcessDirectoryInputConfig) workingDir, err := os.Getwd() c.Assume(err, gs.IsNil) config.ProcessDir = filepath.Join(workingDir, "testsupport", "processes") // `Ticker` is the last thing called during the setup part of the // input's `Run` method, so it triggers a waitgroup that tests can // wait on when they need to ensure initialization has finished. var started sync.WaitGroup started.Add(1) tickChan := make(chan time.Time, 1) ith.MockInputRunner.EXPECT().Ticker().Return(tickChan).Do( func() { started.Done() }) // Similarly we use a waitgroup to signal when LogMessage has been // called to know when reloads have completed. Warning: If you call // expectLogMessage with a msg that is never passed to LogMessage and // then you call loaded.Wait() then your test will hang and never // complete. var loaded sync.WaitGroup expectLogMessage := func(msg string) { loaded.Add(1) ith.MockInputRunner.EXPECT().LogMessage(msg).Do( func(msg string) { loaded.Done() }) } // Same name => same content. paths := []string{ filepath.Join(config.ProcessDir, "100", "h0.toml"), filepath.Join(config.ProcessDir, "100", "h1.toml"), filepath.Join(config.ProcessDir, "200", "h0.toml"), filepath.Join(config.ProcessDir, "300", "h1.toml"), } copyFile := func(src, dest string) { inFile, err := os.Open(src) c.Assume(err, gs.IsNil) outFile, err := os.Create(dest) c.Assume(err, gs.IsNil) _, err = io.Copy(outFile, inFile) c.Assume(err, gs.IsNil) inFile.Close() outFile.Close() } err = pdiInput.Init(config) c.Expect(err, gs.IsNil) for _, p := range paths { expectLogMessage("Added: " + p) } go pdiInput.Run(ith.MockInputRunner, ith.MockHelper) defer func() { pdiInput.Stop() for _, entry := range pdiInput.inputs { entry.ir.Input().Stop() } }() started.Wait() c.Specify("loads scheduled jobs", func() { pathIndex := func(name string) (i int) { var p string for i, p = range paths { if name == p { return } } return -1 } for name, entry := range pdiInput.inputs { i := pathIndex(name) // Make sure each file path got registered. c.Expect(i, gs.Not(gs.Equals), -1) dirName := filepath.Base(filepath.Dir(name)) dirInt, err := strconv.Atoi(dirName) c.Expect(err, gs.IsNil) // And that the ticker interval was read correctly. c.Expect(uint(dirInt), gs.Equals, entry.config.TickerInterval) } }) c.Specify("discovers and adds a new job", func() { // Copy one of the files to register a new process. newPath := filepath.Join(config.ProcessDir, "300", "h0.toml") copyFile(paths[0], newPath) defer func() { err := os.Remove(newPath) c.Assume(err, gs.IsNil) }() // Set up expectations and trigger process dir reload. expectLogMessage("Added: " + newPath) tickChan <- time.Now() loaded.Wait() // Make sure our plugin was loaded. c.Expect(len(pdiInput.inputs), gs.Equals, 5) newEntry, ok := pdiInput.inputs[newPath] c.Expect(ok, gs.IsTrue) c.Expect(newEntry.config.TickerInterval, gs.Equals, uint(300)) }) c.Specify("removes a deleted job", func() { err := os.Remove(paths[3]) c.Assume(err, gs.IsNil) defer func() { copyFile(paths[1], paths[3]) }() // Set up expectations and trigger process dir reload. expectLogMessage("Removed: " + paths[3]) tickChan <- time.Now() loaded.Wait() // Make sure our plugin was deleted. c.Expect(len(pdiInput.inputs), gs.Equals, 3) }) c.Specify("notices a changed job", func() { // Overwrite one job w/ a slightly different one. copyFile(paths[0], paths[3]) defer copyFile(paths[1], paths[3]) // Set up expectations and trigger process dir reload. expectLogMessage("Removed: " + paths[3]) expectLogMessage("Added: " + paths[3]) tickChan <- time.Now() loaded.Wait() // Make sure the new config was loaded. c.Expect(pdiInput.inputs[paths[3]].config.Command["0"].Args[0], gs.Equals, "hello world\n") }) }) }
func TcpInputSpec(c gs.Context) { t := &pipeline_ts.SimpleT{} ctrl := gomock.NewController(t) defer ctrl.Finish() pConfig := NewPipelineConfig(nil) ith := new(plugins_ts.InputTestHelper) ith.Msg = pipeline_ts.GetTestMessage() ith.Pack = NewPipelinePack(pConfig.InputRecycleChan()) ith.AddrStr = "localhost:55565" ith.ResolvedAddrStr = "127.0.0.1:55565" ith.MockHelper = pipelinemock.NewMockPluginHelper(ctrl) ith.MockInputRunner = pipelinemock.NewMockInputRunner(ctrl) ith.MockDeliverer = pipelinemock.NewMockDeliverer(ctrl) ith.MockSplitterRunner = pipelinemock.NewMockSplitterRunner(ctrl) ith.PackSupply = make(chan *PipelinePack, 1) c.Specify("A TcpInput", func() { tcpInput := &TcpInput{} config := &TcpInputConfig{ Net: "tcp", Address: ith.AddrStr, } bytesChan := make(chan []byte, 1) errChan := make(chan error, 1) var srDoneWG sync.WaitGroup startServer := func() { srDoneWG.Add(1) ith.MockInputRunner.EXPECT().Name().Return("mock_name") ith.MockInputRunner.EXPECT().NewDeliverer(gomock.Any()).Return(ith.MockDeliverer) ith.MockDeliverer.EXPECT().Done() ith.MockInputRunner.EXPECT().NewSplitterRunner(gomock.Any()).Return( ith.MockSplitterRunner) ith.MockSplitterRunner.EXPECT().UseMsgBytes().Return(false) ith.MockSplitterRunner.EXPECT().SetPackDecorator(gomock.Any()) ith.MockSplitterRunner.EXPECT().Done().Do(func() { srDoneWG.Done() }) // splitCall gets called twice. The first time it returns nil, the // second time it returns io.EOF. splitCall := ith.MockSplitterRunner.EXPECT().SplitStream(gomock.Any(), ith.MockDeliverer).AnyTimes() splitCall.Do(func(conn net.Conn, del Deliverer) { recd, _ := ioutil.ReadAll(conn) bytesChan <- recd splitCall.Return(io.EOF) }) err := tcpInput.Run(ith.MockInputRunner, ith.MockHelper) errChan <- err } c.Specify("not using TLS", func() { err := tcpInput.Init(config) c.Assume(err, gs.IsNil) c.Expect(tcpInput.listener.Addr().String(), gs.Equals, ith.ResolvedAddrStr) c.Specify("accepts connections and passes them to the splitter", func() { go startServer() data := []byte("THIS IS THE DATA") outConn, err := net.Dial("tcp", ith.AddrStr) c.Assume(err, gs.IsNil) _, err = outConn.Write(data) c.Expect(err, gs.IsNil) outConn.Close() recd := <-bytesChan c.Expect(err, gs.IsNil) c.Expect(string(recd), gs.Equals, string(data)) tcpInput.Stop() err = <-errChan c.Expect(err, gs.IsNil) srDoneWG.Wait() }) }) c.Specify("using TLS", func() { config.UseTls = true c.Specify("fails to init w/ missing key or cert file", func() { config.Tls = TlsConfig{} err := tcpInput.Init(config) c.Expect(err, gs.Not(gs.IsNil)) c.Expect(err.Error(), gs.Equals, "TLS config requires both cert_file and key_file value.") }) config.Tls = TlsConfig{ CertFile: "./testsupport/cert.pem", KeyFile: "./testsupport/key.pem", } c.Specify("accepts connections and passes them to the splitter", func() { err := tcpInput.Init(config) c.Expect(err, gs.IsNil) go startServer() data := []byte("This is a test.") clientConfig := new(tls.Config) clientConfig.InsecureSkipVerify = true outConn, err := tls.Dial("tcp", ith.AddrStr, clientConfig) c.Assume(err, gs.IsNil) outConn.SetWriteDeadline(time.Now().Add(time.Duration(10000))) n, err := outConn.Write(data) c.Expect(err, gs.IsNil) c.Expect(n, gs.Equals, len(data)) outConn.Close() recd := <-bytesChan c.Expect(err, gs.IsNil) c.Expect(string(recd), gs.Equals, string(data)) tcpInput.Stop() err = <-errChan c.Expect(err, gs.IsNil) srDoneWG.Wait() }) c.Specify("doesn't accept connections below specified min TLS version", func() { config.Tls.MinVersion = "TLS12" err := tcpInput.Init(config) c.Expect(err, gs.IsNil) go startServer() clientConfig := &tls.Config{ InsecureSkipVerify: true, MaxVersion: tls.VersionTLS11, } conn, err := tls.Dial("tcp", ith.AddrStr, clientConfig) c.Expect(conn, gs.IsNil) c.Expect(err, gs.Not(gs.IsNil)) <-bytesChan tcpInput.Stop() err = <-errChan c.Expect(err, gs.IsNil) srDoneWG.Wait() }) }) }) }
func LogstreamerInputSpec(c gs.Context) { t := &pipeline_ts.SimpleT{} ctrl := gomock.NewController(t) defer ctrl.Finish() here, _ := os.Getwd() dirPath := filepath.Join(here, "../../logstreamer", "testdir", "filehandling/subdir") tmpDir, tmpErr := ioutil.TempDir("", "hekad-tests") c.Expect(tmpErr, gs.Equals, nil) defer func() { tmpErr = os.RemoveAll(tmpDir) c.Expect(tmpErr, gs.IsNil) }() globals := DefaultGlobals() globals.BaseDir = tmpDir pConfig := NewPipelineConfig(globals) ith := new(plugins_ts.InputTestHelper) ith.Msg = pipeline_ts.GetTestMessage() ith.Pack = NewPipelinePack(pConfig.InputRecycleChan()) // Specify localhost, but we're not really going to use the network. ith.AddrStr = "localhost:55565" ith.ResolvedAddrStr = "127.0.0.1:55565" // Set up mock helper, runner, and pack supply channel. ith.MockHelper = pipelinemock.NewMockPluginHelper(ctrl) ith.MockInputRunner = pipelinemock.NewMockInputRunner(ctrl) ith.MockDeliverer = pipelinemock.NewMockDeliverer(ctrl) ith.MockSplitterRunner = pipelinemock.NewMockSplitterRunner(ctrl) ith.PackSupply = make(chan *PipelinePack, 1) c.Specify("A LogstreamerInput", func() { lsInput := &LogstreamerInput{pConfig: pConfig} lsiConfig := lsInput.ConfigStruct().(*LogstreamerInputConfig) lsiConfig.LogDirectory = dirPath lsiConfig.FileMatch = `file.log(\.?)(?P<Seq>\d+)?` lsiConfig.Differentiator = []string{"logfile"} lsiConfig.Priority = []string{"^Seq"} c.Specify("w/ no translation map", func() { err := lsInput.Init(lsiConfig) c.Expect(err, gs.IsNil) c.Expect(len(lsInput.plugins), gs.Equals, 1) // Create pool of packs. numLines := 5 // # of lines in the log file we're parsing. packs := make([]*PipelinePack, numLines) ith.PackSupply = make(chan *PipelinePack, numLines) for i := 0; i < numLines; i++ { packs[i] = NewPipelinePack(ith.PackSupply) ith.PackSupply <- packs[i] } c.Specify("reads a log file", func() { // Expect InputRunner calls to get InChan and inject outgoing msgs. ith.MockInputRunner.EXPECT().LogError(gomock.Any()).AnyTimes() ith.MockInputRunner.EXPECT().LogMessage(gomock.Any()).AnyTimes() ith.MockInputRunner.EXPECT().NewDeliverer("1").Return(ith.MockDeliverer) ith.MockInputRunner.EXPECT().NewSplitterRunner("1").Return( ith.MockSplitterRunner) ith.MockSplitterRunner.EXPECT().UseMsgBytes().Return(false) ith.MockSplitterRunner.EXPECT().IncompleteFinal().Return(false) ith.MockSplitterRunner.EXPECT().SetPackDecorator(gomock.Any()) getRecCall := ith.MockSplitterRunner.EXPECT().GetRecordFromStream( gomock.Any()).Times(numLines) line := "boo hoo foo foo" getRecCall.Return(len(line), []byte(line), nil) getRecCall = ith.MockSplitterRunner.EXPECT().GetRecordFromStream(gomock.Any()) getRecCall.Return(0, make([]byte, 0), io.EOF) deliverChan := make(chan []byte, 1) deliverCall := ith.MockSplitterRunner.EXPECT().DeliverRecord(gomock.Any(), ith.MockDeliverer).Times(numLines) deliverCall.Do(func(record []byte, del Deliverer) { deliverChan <- record }) ith.MockDeliverer.EXPECT().Done() runOutChan := make(chan error, 1) go func() { err = lsInput.Run(ith.MockInputRunner, ith.MockHelper) runOutChan <- err }() dur, _ := time.ParseDuration("5s") timeout := time.After(dur) timed := false for x := 0; x < numLines; x++ { select { case record := <-deliverChan: c.Expect(string(record), gs.Equals, line) case <-timeout: timed = true x += numLines } // Free up the scheduler while we wait for the log file lines // to be processed. runtime.Gosched() } lsInput.Stop() c.Expect(timed, gs.Equals, false) c.Expect(<-runOutChan, gs.Equals, nil) }) }) c.Specify("with a translation map", func() { lsiConfig.Translation = make(ls.SubmatchTranslationMap) lsiConfig.Translation["Seq"] = make(ls.MatchTranslationMap) c.Specify("allows len 1 translation map for 'missing'", func() { lsiConfig.Translation["Seq"]["missing"] = 9999 err := lsInput.Init(lsiConfig) c.Expect(err, gs.IsNil) }) c.Specify("doesn't allow len 1 map for other keys", func() { lsiConfig.Translation["Seq"]["missin"] = 9999 err := lsInput.Init(lsiConfig) c.Expect(err, gs.Not(gs.IsNil)) c.Expect(err.Error(), gs.Equals, "A translation map with one entry ('Seq') must be specifying a "+ "'missing' key.") }) }) }) }
func ProcessInputSpec(c gs.Context) { t := &pipeline_ts.SimpleT{} ctrl := gomock.NewController(t) defer ctrl.Finish() pConfig := NewPipelineConfig(nil) ith := new(plugins_ts.InputTestHelper) ith.Msg = pipeline_ts.GetTestMessage() ith.MockHelper = pipelinemock.NewMockPluginHelper(ctrl) ith.MockInputRunner = pipelinemock.NewMockInputRunner(ctrl) ith.MockDeliverer = pipelinemock.NewMockDeliverer(ctrl) ith.MockSplitterRunner = pipelinemock.NewMockSplitterRunner(ctrl) ith.Pack = NewPipelinePack(pConfig.InputRecycleChan()) c.Specify("A ProcessInput", func() { pInput := ProcessInput{} config := pInput.ConfigStruct().(*ProcessInputConfig) config.Command = make(map[string]cmdConfig) ith.MockHelper.EXPECT().Hostname().Return(pConfig.Hostname()) tickChan := make(chan time.Time) ith.MockInputRunner.EXPECT().Ticker().Return(tickChan) errChan := make(chan error) ith.MockSplitterRunner.EXPECT().UseMsgBytes().Return(false) decChan := make(chan func(*PipelinePack), 1) setDecCall := ith.MockSplitterRunner.EXPECT().SetPackDecorator(gomock.Any()) setDecCall.Do(func(dec func(*PipelinePack)) { decChan <- dec }) bytesChan := make(chan []byte, 1) splitCall := ith.MockSplitterRunner.EXPECT().SplitStream(gomock.Any(), ith.MockDeliverer).Return(nil) splitCall.Do(func(r io.Reader, del Deliverer) { bytes, err := ioutil.ReadAll(r) c.Assume(err, gs.IsNil) bytesChan <- bytes }) ith.MockDeliverer.EXPECT().Done() c.Specify("using stdout", func() { ith.MockInputRunner.EXPECT().NewDeliverer("stdout").Return(ith.MockDeliverer) ith.MockInputRunner.EXPECT().NewSplitterRunner("stdout").Return( ith.MockSplitterRunner) c.Specify("reads a message from ProcessInput", func() { pInput.SetName("SimpleTest") // Note that no working directory is explicitly specified. config.Command["0"] = cmdConfig{ Bin: PROCESSINPUT_TEST1_CMD, Args: PROCESSINPUT_TEST1_CMD_ARGS, } err := pInput.Init(config) c.Assume(err, gs.IsNil) go func() { errChan <- pInput.Run(ith.MockInputRunner, ith.MockHelper) }() tickChan <- time.Now() actual := <-bytesChan c.Expect(string(actual), gs.Equals, PROCESSINPUT_TEST1_OUTPUT+"\n") dec := <-decChan dec(ith.Pack) fPInputName := ith.Pack.Message.FindFirstField("ProcessInputName") c.Expect(fPInputName.ValueString[0], gs.Equals, "SimpleTest.stdout") pInput.Stop() err = <-errChan c.Expect(err, gs.IsNil) }) c.Specify("can pipe multiple commands together", func() { pInput.SetName("PipedCmd") // Note that no working directory is explicitly specified. config.Command["0"] = cmdConfig{ Bin: PROCESSINPUT_PIPE_CMD1, Args: PROCESSINPUT_PIPE_CMD1_ARGS, } config.Command["1"] = cmdConfig{ Bin: PROCESSINPUT_PIPE_CMD2, Args: PROCESSINPUT_PIPE_CMD2_ARGS, } err := pInput.Init(config) c.Assume(err, gs.IsNil) go func() { errChan <- pInput.Run(ith.MockInputRunner, ith.MockHelper) }() tickChan <- time.Now() actual := <-bytesChan c.Expect(string(actual), gs.Equals, PROCESSINPUT_PIPE_OUTPUT+"\n") dec := <-decChan dec(ith.Pack) fPInputName := ith.Pack.Message.FindFirstField("ProcessInputName") c.Expect(fPInputName.ValueString[0], gs.Equals, "PipedCmd.stdout") pInput.Stop() err = <-errChan c.Expect(err, gs.IsNil) }) }) c.Specify("using stderr", func() { ith.MockInputRunner.EXPECT().NewDeliverer("stderr").Return(ith.MockDeliverer) ith.MockInputRunner.EXPECT().NewSplitterRunner("stderr").Return( ith.MockSplitterRunner) c.Specify("handles bad arguments", func() { pInput.SetName("BadArgs") config.ParseStdout = false config.ParseStderr = true // Note that no working directory is explicitly specified. config.Command["0"] = cmdConfig{Bin: STDERR_CMD, Args: STDERR_CMD_ARGS} err := pInput.Init(config) c.Assume(err, gs.IsNil) expectedErr := fmt.Errorf( "BadArgs CommandChain::Wait() error: [Subcommand returned an error: [exit status 1]]") ith.MockInputRunner.EXPECT().LogError(expectedErr) go func() { errChan <- pInput.Run(ith.MockInputRunner, ith.MockHelper) }() tickChan <- time.Now() // Error message differs by platform, but we at least wait // until we get it. <-bytesChan pInput.Stop() err = <-errChan c.Expect(err, gs.IsNil) }) }) }) }