Răsfoiți Sursa

* api tests
* changed api Config field to pointer
* log reopen event is not fired if log file not changed
* fix regressions found by automated tests

flashmob 8 ani în urmă
părinte
comite
7d6d1fddcf
7 a modificat fișierele cu 323 adăugiri și 17 ștergeri
  1. 5 3
      api.go
  2. 313 8
      api_test.go
  3. 1 1
      cmd/guerrillad/serve.go
  4. 3 1
      cmd/guerrillad/serve_test.go
  5. 0 3
      config.go
  6. 1 0
      guerrilla.go
  7. 0 1
      server.go

+ 5 - 3
api.go

@@ -89,7 +89,8 @@ func (d *Daemon) LoadConfig(path string) (AppConfig, error) {
 }
 
 // SetConfig is same as LoadConfig, except you can pass AppConfig directly
-func (d *Daemon) SetConfig(c AppConfig) error {
+// does not emit any change events, instead use ReloadConfig after daemon has started
+func (d *Daemon) SetConfig(c *AppConfig) error {
 	// Config.Load takes []byte so we need to serialize
 	data, err := json.Marshal(c)
 	if err != nil {
@@ -105,7 +106,7 @@ func (d *Daemon) SetConfig(c AppConfig) error {
 }
 
 // Reload a config using the passed in AppConfig and emit config change events
-func (d *Daemon) ReloadConfig(c AppConfig) error {
+func (d *Daemon) ReloadConfig(c *AppConfig) error {
 	if d.Config == nil {
 		return errors.New("d.Config nil")
 	}
@@ -139,7 +140,8 @@ func (d *Daemon) ReloadConfigFile(path string) error {
 	return nil
 }
 
-// ReopenLogs re-opens all log files. Typically, one would call this after rotating logs
+// ReopenLogs send events to re-opens all log files.
+// Typically, one would call this after rotating logs
 func (d *Daemon) ReopenLogs() {
 	d.Config.EmitLogReopenEvents(d.g)
 }

+ 313 - 8
api_test.go

@@ -1,9 +1,15 @@
 package guerrilla
 
 import (
+	"bufio"
+	"fmt"
 	"github.com/flashmob/go-guerrilla/backends"
 	"github.com/flashmob/go-guerrilla/log"
+	"github.com/flashmob/go-guerrilla/mail"
 	"io/ioutil"
+	"net"
+	"os"
+	"strings"
 	"testing"
 	"time"
 )
@@ -54,39 +60,39 @@ func TestSMTPNoLog(t *testing.T) {
 
 	// configure a default server with no log output
 	cfg := &AppConfig{LogFile: log.OutputOff.String()}
-	smtp := Daemon{Config: cfg}
+	d := Daemon{Config: cfg}
 
-	err := smtp.Start()
+	err := d.Start()
 	if err != nil {
 		t.Error(err)
 	}
 	time.Sleep(time.Second * 2)
-	smtp.Shutdown()
+	d.Shutdown()
 }
 
 // our custom server
 func TestSMTPCustomServer(t *testing.T) {
-	cfg := &AppConfig{LogFile: log.OutputStdout.String()}
+	cfg := &AppConfig{LogFile: log.OutputOff.String()}
 	sc := ServerConfig{
 		ListenInterface: "127.0.0.1:2526",
 		IsEnabled:       true,
 	}
 	cfg.Servers = append(cfg.Servers, sc)
-	smtp := Daemon{Config: cfg}
+	d := Daemon{Config: cfg}
 
-	err := smtp.Start()
+	err := d.Start()
 	if err != nil {
 		t.Error("start error", err)
 	} else {
 		time.Sleep(time.Second * 2)
-		smtp.Shutdown()
+		d.Shutdown()
 	}
 
 }
 
 // with a backend config
 func TestSMTPCustomBackend(t *testing.T) {
-	cfg := &AppConfig{LogFile: log.OutputStdout.String()}
+	cfg := &AppConfig{LogFile: log.OutputOff.String()}
 	sc := ServerConfig{
 		ListenInterface: "127.0.0.1:2526",
 		IsEnabled:       true,
@@ -214,3 +220,302 @@ func TestSMTPLoadFile(t *testing.T) {
 		d.Shutdown()
 	}
 }
+
+func TestReopenLog(t *testing.T) {
+	os.Truncate("test/testlog", 0)
+	cfg := &AppConfig{LogFile: "tests/testlog"}
+	sc := ServerConfig{
+		ListenInterface: "127.0.0.1:2526",
+		IsEnabled:       true,
+	}
+	cfg.Servers = append(cfg.Servers, sc)
+	d := Daemon{Config: cfg}
+
+	err := d.Start()
+	if err != nil {
+		t.Error("start error", err)
+	} else {
+		d.ReopenLogs()
+		time.Sleep(time.Second * 2)
+
+		d.Shutdown()
+	}
+
+	b, err := ioutil.ReadFile("tests/testlog")
+	if err != nil {
+		t.Error("could not read logfile")
+		return
+	}
+	if strings.Index(string(b), "re-opened log file") < 0 {
+		t.Error("Server log did not re-opened, expecting \"re-opened log file\"")
+	}
+	if strings.Index(string(b), "re-opened main log file") < 0 {
+		t.Error("Main log did not re-opened, expecting \"re-opened main log file\"")
+	}
+}
+
+func TestSetConfig(t *testing.T) {
+
+	os.Truncate("test/testlog", 0)
+	cfg := &AppConfig{LogFile: "tests/testlog"}
+	sc := ServerConfig{
+		ListenInterface: "127.0.0.1:2526",
+		IsEnabled:       true,
+	}
+	cfg.Servers = append(cfg.Servers, sc)
+	d := Daemon{Config: cfg}
+
+	// lets add a new server
+	sc.ListenInterface = "127.0.0.1:2527"
+	cfg.Servers = append(cfg.Servers, sc)
+
+	err := d.SetConfig(cfg)
+	if err != nil {
+		t.Error("SetConfig returned an error:", err)
+		return
+	}
+
+	err = d.Start()
+	if err != nil {
+		t.Error("start error", err)
+	} else {
+
+		time.Sleep(time.Second * 2)
+
+		d.Shutdown()
+	}
+
+	b, err := ioutil.ReadFile("tests/testlog")
+	if err != nil {
+		t.Error("could not read logfile")
+		return
+	}
+	//fmt.Println(string(b))
+	// has 127.0.0.1:2527 started?
+	if strings.Index(string(b), "127.0.0.1:2527") < 0 {
+		t.Error("expecting 127.0.0.1:2527 to start")
+	}
+
+}
+
+func TestSetConfigError(t *testing.T) {
+
+	os.Truncate("tests/testlog", 0)
+	cfg := &AppConfig{LogFile: "tests/testlog"}
+	sc := ServerConfig{
+		ListenInterface: "127.0.0.1:2526",
+		IsEnabled:       true,
+	}
+	cfg.Servers = append(cfg.Servers, sc)
+	d := Daemon{Config: cfg}
+
+	// lets add a new server with bad TLS
+	sc.ListenInterface = "127.0.0.1:2527"
+	sc.StartTLSOn = true
+	sc.PublicKeyFile = "tests/testlog" // totally wrong :->
+	sc.PublicKeyFile = "tests/testlog" // totally wrong :->
+
+	cfg.Servers = append(cfg.Servers, sc)
+
+	err := d.SetConfig(cfg)
+	if err == nil {
+		t.Error("SetConfig should have returned an error compalning about bad tls settings")
+		return
+	}
+}
+
+var funkyLogger = func() backends.Decorator {
+
+	backends.Svc.AddInitializer(
+		backends.InitializeWith(
+			func(backendConfig backends.BackendConfig) error {
+				backends.Log().Info("Funky logger is up & down to funk!")
+				return nil
+			}),
+	)
+
+	backends.Svc.AddShutdowner(
+		backends.ShutdownWith(
+			func() error {
+				backends.Log().Info("The funk has been stopped!")
+				return nil
+			}),
+	)
+
+	return func(c backends.Processor) backends.Processor {
+		return backends.ProcessWith(
+			func(e *mail.Envelope, task backends.SelectTask) (backends.Result, error) {
+				if task == backends.TaskValidateRcpt {
+					// validate the last recipient appended to e.Rcpt
+					backends.Log().Infof(
+						"another funky recipient [%s]",
+						e.RcptTo[len(e.RcptTo)-1])
+					// if valid then forward call to the next processor in the chain
+					return c.Process(e, task)
+					// if invalid, return a backend result
+					//return backends.NewResult(response.Canned.FailRcptCmd), nil
+				} else if task == backends.TaskSaveMail {
+					backends.Log().Info("Another funky email!")
+				}
+				return c.Process(e, task)
+			})
+	}
+}
+
+// How about a custom processor?
+func TestSetAddProcessor(t *testing.T) {
+	os.Truncate("tests/testlog", 0)
+	cfg := &AppConfig{
+		LogFile:       "tests/testlog",
+		AllowedHosts:  []string{"grr.la"},
+		BackendConfig: backends.BackendConfig{"process_stack": "HeadersParser|Debugger|FunkyLogger"},
+	}
+	d := Daemon{Config: cfg}
+	d.AddProcessor("FunkyLogger", funkyLogger)
+
+	d.Start()
+	// lets have a talk with the server
+	talkToServer("127.0.0.1:2525")
+
+	d.Shutdown()
+
+	b, err := ioutil.ReadFile("tests/testlog")
+	if err != nil {
+		t.Error("could not read logfile")
+		return
+	}
+	// lets check for fingerprints
+	if strings.Index(string(b), "another funky recipient") < 0 {
+		t.Error("did not log: another funky recipient")
+	}
+
+	if strings.Index(string(b), "Another funky email!") < 0 {
+		t.Error("Did not log: Another funky email!")
+	}
+
+	if strings.Index(string(b), "Funky logger is up & down to funk") < 0 {
+		t.Error("Did not log: Funky logger is up & down to funk")
+	}
+	if strings.Index(string(b), "The funk has been stopped!") < 0 {
+		t.Error("Did not log:The funk has been stopped!")
+	}
+
+}
+
+func talkToServer(address string) {
+
+	conn, err := net.Dial("tcp", address)
+	if err != nil {
+
+		return
+	}
+	in := bufio.NewReader(conn)
+	str, err := in.ReadString('\n')
+	fmt.Fprint(conn, "HELO maildiranasaurustester\r\n")
+	str, err = in.ReadString('\n')
+	fmt.Fprint(conn, "MAIL FROM:<[email protected]>r\r\n")
+	str, err = in.ReadString('\n')
+	fmt.Fprint(conn, "RCPT TO:[email protected]\r\n")
+	str, err = in.ReadString('\n')
+	fmt.Fprint(conn, "DATA\r\n")
+	str, err = in.ReadString('\n')
+	fmt.Fprint(conn, "Subject: Test subject\r\n")
+	fmt.Fprint(conn, "\r\n")
+	fmt.Fprint(conn, "A an email body\r\n")
+	fmt.Fprint(conn, ".\r\n")
+	str, err = in.ReadString('\n')
+	_ = str
+}
+
+// Test hot config reload
+// Here we forgot to add FunkyLogger so backend will fail to init
+
+func TestReloadConfig(t *testing.T) {
+	os.Truncate("tests/testlog", 0)
+	d := Daemon{}
+	d.Start()
+
+	cfg := &AppConfig{
+		LogFile:       "tests/testlog",
+		AllowedHosts:  []string{"grr.la"},
+		BackendConfig: backends.BackendConfig{"process_stack": "HeadersParser|Debugger|FunkyLogger"},
+	}
+	// Look mom, reloading the config without shutting down!
+	d.ReloadConfig(cfg)
+
+	d.Shutdown()
+}
+
+func TestPubSubAPI(t *testing.T) {
+
+	os.Truncate("tests/testlog", 0)
+
+	d := Daemon{Config: &AppConfig{LogFile: "tests/testlog"}}
+	d.Start()
+
+	// new config
+	cfg := &AppConfig{
+		PidFile:       "tests/pidfile`.pid",
+		LogFile:       "tests/testlog",
+		AllowedHosts:  []string{"grr.la"},
+		BackendConfig: backends.BackendConfig{"process_stack": "HeadersParser|Debugger|FunkyLogger"},
+	}
+
+	var i = 0
+	pidEvHandler := func(c *AppConfig) {
+		i++
+		if i > 1 {
+			t.Error("number > 1, it means d.Unsubscribe didn't work")
+		}
+		d.Logger.Info("number", i)
+	}
+	d.Subscribe(EventConfigPidFile, pidEvHandler)
+
+	d.ReloadConfig(cfg)
+
+	d.Unsubscribe(EventConfigPidFile, pidEvHandler)
+	cfg.PidFile = "tests/pidfile2.pid"
+	d.Publish(EventConfigPidFile, cfg)
+	d.ReloadConfig(cfg)
+
+	b, err := ioutil.ReadFile("tests/testlog")
+	if err != nil {
+		t.Error("could not read logfile")
+		return
+	}
+	// lets interrogate the log
+	if strings.Index(string(b), "number1") < 0 {
+		t.Error("it lools like d.ReloadConfig(cfg) did not fire EventConfigPidFile, pidEvHandler not called")
+	}
+
+}
+
+func TestAPILog(t *testing.T) {
+	os.Truncate("tests/testlog", 0)
+	d := Daemon{}
+	l := d.log()
+	l.Info("hai") // to stderr
+	if l.GetLevel() != "info" {
+		t.Error("Log level does not eq info, it is ", l.GetLevel())
+	}
+	d.Logger = nil
+	d.Config = &AppConfig{LogFile: "tests/testlog"}
+	l = d.log()
+	l.Info("hai") // to tests/testlog
+
+	//
+	l = d.log()
+	if l.GetLogDest() != "tests/testlog" {
+		t.Error("log dest is not tests/testlog, it was ", l.GetLogDest())
+	}
+
+	b, err := ioutil.ReadFile("tests/testlog")
+	if err != nil {
+		t.Error("could not read logfile")
+		return
+	}
+	// lets interrogate the log
+	if strings.Index(string(b), "hai") < 0 {
+		t.Error("hai was not found in the log, it should have been in tests/testlog")
+	}
+}

+ 1 - 1
cmd/guerrillad/serve.go

@@ -147,7 +147,7 @@ func readConfig(path string, pidFile string) error {
 	} else if len(appConfig.PidFile) == 0 {
 		appConfig.PidFile = defaultPidFile
 	}
-	d.SetConfig(appConfig)
+	d.SetConfig(&appConfig)
 	return nil
 }
 

+ 3 - 1
cmd/guerrillad/serve_test.go

@@ -382,6 +382,7 @@ func TestCmdConfigChangeEvents(t *testing.T) {
 
 // start server, change config, send SIG HUP, confirm that the pidfile changed & backend reloaded
 func TestServe(t *testing.T) {
+	os.Truncate("../../tests/testlog", 0)
 	testcert.GenerateCert("mail2.guerrillamail.com", "", 365*24*time.Hour, false, 2048, "P256", "../../tests/")
 
 	mainlog, _ = log.GetLogger("../../tests/testlog")
@@ -424,6 +425,7 @@ func TestServe(t *testing.T) {
 	}
 	// send kill signal and wait for exit
 	sigKill()
+	// wait for exit
 	serveWG.Wait()
 
 	// did backend started as expected?
@@ -439,7 +441,7 @@ func TestServe(t *testing.T) {
 	}
 
 	// cleanup
-	os.Truncate("../../tests/testlog", 0)
+
 	os.Remove("configJsonA.json")
 	os.Remove("./pidfile.pid")
 	os.Remove("./pidfile2.pid")

+ 0 - 3
config.go

@@ -116,9 +116,6 @@ func (c *AppConfig) EmitChangeEvents(oldConfig *AppConfig, app Guerrilla) {
 	// has mainlog log changed?
 	if strings.Compare(oldConfig.LogFile, c.LogFile) != 0 {
 		app.Publish(EventConfigLogFile, c)
-	} else {
-		// since config file has not changed, we reload it
-		app.Publish(EventConfigLogReopen, c)
 	}
 	// has log level changed?
 	if strings.Compare(oldConfig.LogLevel, c.LogLevel) != 0 {

+ 1 - 0
guerrilla.go

@@ -373,6 +373,7 @@ func (g *guerrilla) subscribeEvents() {
 			if err := newBackend.Start(); err != nil {
 				logger.WithError(err).Error("backend could not start")
 			}
+			logger.Info("new backend started")
 			g.storeBackend(newBackend)
 		}
 	})

+ 0 - 1
server.go

@@ -423,7 +423,6 @@ func (server *server) handleClient(client *client) {
 						client.sendResponse(response.Canned.ErrorRelayDenied, to.Host)
 					} else {
 						client.PushRcpt(to)
-						server.log.Info("Server backend is: ", server.backend)
 						rcptError := server.backend().ValidateRcpt(client.Envelope)
 						if rcptError != nil {
 							client.PopRcpt()