diff options
Diffstat (limited to 'service/reload')
-rw-r--r-- | service/reload/config.go | 21 | ||||
-rw-r--r-- | service/reload/config_test.go | 58 | ||||
-rw-r--r-- | service/reload/service.go | 34 | ||||
-rw-r--r-- | service/reload/watcher.go | 37 | ||||
-rw-r--r-- | service/reload/watcher_test.go | 440 |
5 files changed, 541 insertions, 49 deletions
diff --git a/service/reload/config.go b/service/reload/config.go index 551fb71b..930f4dff 100644 --- a/service/reload/config.go +++ b/service/reload/config.go @@ -1,6 +1,7 @@ package reload import ( + "errors" "github.com/spiral/roadrunner" "github.com/spiral/roadrunner/service" "time" @@ -41,6 +42,24 @@ func (c *Config) Hydrate(cfg service.Config) error { // InitDefaults sets missing values to their default values. func (c *Config) InitDefaults() error { - c.Enabled = false + return c.Valid() +} + +// Valid validates the configuration. +func (c *Config) Valid() error { + if c.Enabled == true && c.Interval < time.Second { + return errors.New("too short interval") + } + + if c.Enabled { + if c.Services == nil { + return errors.New("should add at least 1 service") + } + + if len(c.Services) == 0 { + return errors.New("should add initialized config") + } + } + return nil } diff --git a/service/reload/config_test.go b/service/reload/config_test.go index 7cad4a5d..dd9a2797 100644 --- a/service/reload/config_test.go +++ b/service/reload/config_test.go @@ -1 +1,59 @@ package reload + +import ( + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func Test_Config_Valid(t *testing.T) { + services := make(map[string]ServiceConfig) + services["test"] = ServiceConfig{ + Recursive: false, + Patterns: nil, + Dirs: nil, + Ignore: nil, + service: nil, + } + + cfg := &Config{ + Enabled: true, + Interval: time.Second, + Patterns: nil, + Services: services, + } + assert.NoError(t, cfg.Valid()) +} + +func Test_Fake_ServiceConfig(t *testing.T) { + services := make(map[string]ServiceConfig) + cfg := &Config{ + Enabled: true, + Interval: time.Second, + Patterns: nil, + Services: services, + } + assert.Error(t, cfg.Valid()) +} + +func Test_Interval(t *testing.T) { + services := make(map[string]ServiceConfig) + cfg := &Config{ + Enabled: true, + Interval: time.Millisecond, + Patterns: nil, + Services: services, + } + assert.Error(t, cfg.Valid()) +} + +func Test_NoServiceConfig(t *testing.T) { + services := make(map[string]ServiceConfig) + cfg := &Config{ + Enabled: true, + Interval: time.Millisecond, + Patterns: nil, + Services: services, + } + assert.Error(t, cfg.Valid()) +}
\ No newline at end of file diff --git a/service/reload/service.go b/service/reload/service.go index bb85e15d..ab249c41 100644 --- a/service/reload/service.go +++ b/service/reload/service.go @@ -28,7 +28,7 @@ func (s *Service) Init(cfg *Config, c service.Container) (bool, error) { var configs []WatcherConfig // mount Services to designated services - for serviceName, _ := range cfg.Services { + for serviceName := range cfg.Services { svc, _ := c.Get(serviceName) if ctrl, ok := svc.(roadrunner.Controllable); ok { tmp := cfg.Services[serviceName] @@ -50,7 +50,6 @@ func (s *Service) Init(cfg *Config, c service.Container) (bool, error) { recursive: config.Recursive, directories: config.Dirs, filterHooks: func(filename string, patterns []string) error { - for i := 0; i < len(patterns); i++ { if strings.Contains(filename, patterns[i]) { return nil @@ -83,24 +82,21 @@ func (s *Service) Serve() error { } go func() { - for { - select { - case e := <-s.watcher.Event: - println(fmt.Sprintf("[UPDATE] Service: %s, path to file: %s, filename: %s", e.service, e.path, e.info.Name())) - - srv := s.reloadConfig.Services[e.service] - - if srv.service != nil { - s := *srv.service - err := s.Server().Reset() - if err != nil { - fmt.Println(err) - } - } else { - s.watcher.mu.Lock() - delete(s.watcher.watcherConfigs, e.service) - s.watcher.mu.Unlock() + for e := range s.watcher.Event { + println(fmt.Sprintf("[UPDATE] Service: %s, path to file: %s, filename: %s", e.service, e.path, e.info.Name())) + + srv := s.reloadConfig.Services[e.service] + + if srv.service != nil { + s := *srv.service + err := s.Server().Reset() + if err != nil { + fmt.Println(err) } + } else { + s.watcher.mu.Lock() + delete(s.watcher.watcherConfigs, e.service) + s.watcher.mu.Unlock() } } }() diff --git a/service/reload/watcher.go b/service/reload/watcher.go index b466fc91..612964c5 100644 --- a/service/reload/watcher.go +++ b/service/reload/watcher.go @@ -78,14 +78,10 @@ func NewWatcher(configs []WatcherConfig, options ...Options) (*Watcher, error) { w.watcherConfigs[v.serviceName] = v } + // apply options for _, option := range options { option(w) } - - if w.watcherConfigs == nil { - return nil, NoWalkerConfig - } - err := w.initFs() if err != nil { return nil, err @@ -205,10 +201,6 @@ outer: } func (w *Watcher) StartPolling(duration time.Duration) error { - if duration < time.Second { - return errors.New("too short duration, please use at least 1 second") - } - w.mu.Lock() if w.started { w.mu.Unlock() @@ -218,8 +210,6 @@ func (w *Watcher) StartPolling(duration time.Duration) error { w.started = true w.mu.Unlock() - //w.wg.Done() - return w.waitEvent(duration) } @@ -267,8 +257,8 @@ func (w *Watcher) retrieveFileList(serviceName string, config WatcherConfig) (ma for k, v := range list { fileList[k] = v } - return fileList, nil } + return fileList, nil } for _, dir := range config.directories { @@ -280,6 +270,9 @@ func (w *Watcher) retrieveFileList(serviceName string, config WatcherConfig) (ma // list is pathToFiles with files list, err := w.retrieveFilesSingle(serviceName, fullPath) + if err != nil { + return nil, err + } for pathToFile, file := range list { fileList[pathToFile] = file @@ -348,22 +341,18 @@ func (w *Watcher) pollEvents(serviceName string, files map[string]os.FileInfo) { } if oldInfo.ModTime() != info.ModTime() { w.watcherConfigs[serviceName].files[pth] = info - select { - case w.Event <- Event{ + w.Event <- Event{ path: pth, info: info, service: serviceName, - }: } } if oldInfo.Mode() != info.Mode() { w.watcherConfigs[serviceName].files[pth] = info - select { - case w.Event <- Event{ + w.Event <- Event{ path: pth, info: info, service: serviceName, - }: } } } @@ -381,30 +370,24 @@ func (w *Watcher) pollEvents(serviceName string, files map[string]os.FileInfo) { delete(removes, path1) delete(creates, path2) - select { - case w.Event <- e: - } + w.Event <- e } } } //Send all the remaining create and remove events. for pth, info := range creates { - select { - case w.Event <- Event{ + w.Event <- Event{ path: pth, info: info, service: serviceName, - }: } } for pth, info := range removes { - select { - case w.Event <- Event{ + w.Event <- Event{ path: pth, info: info, service: serviceName, - }: } } } diff --git a/service/reload/watcher_test.go b/service/reload/watcher_test.go index 4e5e3210..b298a82c 100644 --- a/service/reload/watcher_test.go +++ b/service/reload/watcher_test.go @@ -1,8 +1,444 @@ package reload -import "testing" +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + "time" +) -func Test_Watcher(t *testing.T) { +var testServiceName = "test" +// scenario +// Create walker instance, init with default config, check that Watcher found all files from config +func Test_Correct_Watcher_Init(t *testing.T) { + tempDir, err := ioutil.TempDir(".", "") + defer func() { + err = freeResources(tempDir) + if err != nil { + t.Fatal(err) + } + }() + if err != nil { + t.Fatal(err) + } + err = ioutil.WriteFile(filepath.Join(tempDir, "file.txt"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + wc := WatcherConfig{ + serviceName: testServiceName, + recursive: false, + directories: []string{tempDir}, + filterHooks: nil, + files: make(map[string]os.FileInfo), + ignored: nil, + filePatterns: nil, + } + + w, err := NewWatcher([]WatcherConfig{wc}) + if err != nil { + t.Fatal(err) + } + + if len(w.GetAllFiles(testServiceName)) != 2 { + t.Fatal("incorrect directories len") + } +} + +// scenario +// create 3 files, create walker instance +// Start poll events +// change file and see, if event had come to handler +func Test_Get_FileEvent(t *testing.T) { + tempDir, err := ioutil.TempDir(".", "") + defer func() { + err = freeResources(tempDir) + if err != nil { + t.Fatal(err) + } + }() + + if err != nil { + t.Fatal(err) + } + err = ioutil.WriteFile(filepath.Join(tempDir, "file1.txt"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(tempDir, "file2.txt"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(tempDir, "file3.txt"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + wc := WatcherConfig{ + serviceName: testServiceName, + recursive: false, + directories: []string{tempDir}, + filterHooks: nil, + files: make(map[string]os.FileInfo), + ignored: nil, + filePatterns: nil, + } + + w, err := NewWatcher([]WatcherConfig{wc}) + if err != nil { + t.Fatal(err) + } + + // should be 3 files and directory + if len(w.GetAllFiles(testServiceName)) != 4 { + t.Fatal("incorrect directories len") + } + + go func() { + // time sleep is used here because StartPolling is blocking operation + time.Sleep(time.Second * 5) + err = ioutil.WriteFile(filepath.Join(tempDir, "file2.txt"), + []byte{1, 1, 1}, 0755) + if err != nil { + panic(err) + } + go func() { + for e := range w.Event { + if e.path != "file2.txt" { + panic("didn't handle event when write file2") + } + w.Stop() + } + }() + }() + + err = w.StartPolling(time.Second) + if err != nil { + t.Fatal(err) + } +} + +// scenario +// create 3 files with different extensions, create walker instance +// Start poll events +// change file with txt extension, and see, if event had not come to handler because it was filtered +func Test_FileExtensionFilter(t *testing.T) { + tempDir, err := ioutil.TempDir(".", "") + defer func() { + err = freeResources(tempDir) + if err != nil { + t.Fatal(err) + } + }() + + if err != nil { + t.Fatal(err) + } + err = ioutil.WriteFile(filepath.Join(tempDir, "file1.aaa"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(tempDir, "file2.bbb"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(tempDir, "file3.txt"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + wc := WatcherConfig{ + serviceName: testServiceName, + recursive: false, + directories: []string{tempDir}, + filterHooks: func(filename string, patterns []string) error { + for i := 0; i < len(patterns); i++ { + if strings.Contains(filename, patterns[i]) { + return nil + } + } + return ErrorSkip + }, + files: make(map[string]os.FileInfo), + ignored: nil, + filePatterns: []string{"aaa", "bbb"}, + } + + w, err := NewWatcher([]WatcherConfig{wc}) + if err != nil { + t.Fatal(err) + } + + dirLen := len(w.GetAllFiles(testServiceName)) + // should be 2 files (one filtered) and directory + if dirLen != 3 { + t.Fatalf("incorrect directories len, len is: %d", dirLen) + } + + go func() { + err = ioutil.WriteFile(filepath.Join(tempDir, "file3.txt"), + []byte{1, 1, 1}, 0755) + if err != nil { + panic(err) + } + go func() { + for e := range w.Event { + fmt.Println(e.info.Name()) + panic("handled event from filtered file") + } + }() + + // time sleep is used here because StartPolling is blocking operation + time.Sleep(time.Second * 5) + w.Stop() + }() + + err = w.StartPolling(time.Second) + if err != nil { + t.Fatal(err) + } +} + +// nested +// scenario +// create dir and nested dir +// make files with aaa, bbb and txt extensions, filter txt +// change not filtered file, handle event +func Test_Recursive_Support(t *testing.T) { + tempDir, err := ioutil.TempDir(".", "") + defer func() { + err = freeResources(tempDir) + if err != nil { + t.Fatal(err) + } + }() + + nestedDir, err := ioutil.TempDir(tempDir, "/nested") + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(tempDir, "file1.aaa"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(tempDir, "file2.bbb"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(nestedDir, "file3.txt"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + err = ioutil.WriteFile(filepath.Join(nestedDir, "file4.aaa"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + wc := WatcherConfig{ + serviceName: testServiceName, + recursive: true, + directories: []string{tempDir}, + filterHooks: func(filename string, patterns []string) error { + for i := 0; i < len(patterns); i++ { + if strings.Contains(filename, patterns[i]) { + return nil + } + } + return ErrorSkip + }, + files: make(map[string]os.FileInfo), + ignored: nil, + filePatterns: []string{"aaa", "bbb"}, + } + + w, err := NewWatcher([]WatcherConfig{wc}) + if err != nil { + t.Fatal(err) + } + + dirLen := len(w.GetAllFiles(testServiceName)) + // should be 3 files (2 from root dir, and 1 from nested), filtered txt + if dirLen != 3 { + t.Fatalf("incorrect directories len, len is: %d", dirLen) + } + + go func() { + // time sleep is used here because StartPolling is blocking operation + time.Sleep(time.Second * 5) + // change file in nested directory + err = ioutil.WriteFile(filepath.Join(nestedDir, "file4.aaa"), + []byte{1, 1, 1}, 0755) + if err != nil { + panic(err) + } + go func() { + for e := range w.Event { + if e.info.Name() != "file4.aaa" { + panic("wrong handled event from watcher in nested dir") + } + w.Stop() + } + }() + }() + + err = w.StartPolling(time.Second) + if err != nil { + t.Fatal(err) + } } +func Test_Wrong_Dir(t *testing.T) { + // no such file or directory + wrongDir := "askdjfhaksdlfksdf" + + wc := WatcherConfig{ + serviceName: testServiceName, + recursive: true, + directories: []string{wrongDir}, + filterHooks: func(filename string, patterns []string) error { + for i := 0; i < len(patterns); i++ { + if strings.Contains(filename, patterns[i]) { + return nil + } + } + return ErrorSkip + }, + files: make(map[string]os.FileInfo), + ignored: nil, + filePatterns: []string{"aaa", "bbb"}, + } + + _, err := NewWatcher([]WatcherConfig{wc}) + if err == nil { + t.Fatal(err) + } +} + +func Test_NoServiceConfigAttached(t *testing.T) { + _, err := NewWatcher(nil) + if err == nil { + t.Fatal(err) + } +} + +func Test_Filter_Directory(t *testing.T) { + tempDir, err := ioutil.TempDir(".", "") + defer func() { + err = freeResources(tempDir) + if err != nil { + t.Fatal(err) + } + }() + + nestedDir, err := ioutil.TempDir(tempDir, "/nested") + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(tempDir, "file1.aaa"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(tempDir, "file2.bbb"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + err = ioutil.WriteFile(filepath.Join(nestedDir, "file3.txt"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + err = ioutil.WriteFile(filepath.Join(nestedDir, "file4.aaa"), + []byte{}, 0755) + if err != nil { + t.Fatal(err) + } + + ignored, err := ConvertIgnored([]string{nestedDir}) + if err != nil { + t.Fatal(err) + } + wc := WatcherConfig{ + serviceName: testServiceName, + recursive: true, + directories: []string{tempDir}, + filterHooks: func(filename string, patterns []string) error { + for i := 0; i < len(patterns); i++ { + if strings.Contains(filename, patterns[i]) { + return nil + } + } + return ErrorSkip + }, + files: make(map[string]os.FileInfo), + ignored: ignored, + filePatterns: []string{"aaa", "bbb", "txt"}, + } + + w, err := NewWatcher([]WatcherConfig{wc}) + if err != nil { + t.Fatal(err) + } + + dirLen := len(w.GetAllFiles(testServiceName)) + // should be 2 files (2 from root dir), filtered other + if dirLen != 2 { + t.Fatalf("incorrect directories len, len is: %d", dirLen) + } + + go func() { + // time sleep is used here because StartPolling is blocking operation + time.Sleep(time.Second * 5) + // change file in nested directory + err = ioutil.WriteFile(filepath.Join(nestedDir, "file4.aaa"), + []byte{1, 1, 1}, 0755) + if err != nil { + panic(err) + } + go func() { + for range w.Event { + panic("handled event from watcher in nested dir") + } + }() + + // time sleep is used here because StartPolling is blocking operation + time.Sleep(time.Second * 5) + w.Stop() + + }() + + err = w.StartPolling(time.Second) + if err != nil { + t.Fatal(err) + } +} + +func freeResources(path string) error { + return os.RemoveAll(path) +} |