github.com/fafucoder/cilium@v1.6.11/pkg/clustermesh/config.go (about) 1 // Copyright 2018 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package clustermesh 16 17 import ( 18 "io/ioutil" 19 "path" 20 "path/filepath" 21 "strings" 22 23 fsnotify "gopkg.in/fsnotify.v1" 24 ) 25 26 // clusterLifecycle is the interface to implement in order to receive cluster 27 // configuration lifecycle events. This is implemented by the ClusterMesh. 28 type clusterLifecycle interface { 29 add(clusterName, clusterConfigPath string) 30 remove(clusterName string) 31 } 32 33 type configDirectoryWatcher struct { 34 watcher *fsnotify.Watcher 35 lifecycle clusterLifecycle 36 path string 37 stop chan struct{} 38 } 39 40 func createConfigDirectoryWatcher(path string, lifecycle clusterLifecycle) (*configDirectoryWatcher, error) { 41 watcher, err := fsnotify.NewWatcher() 42 if err != nil { 43 return nil, err 44 } 45 46 if err := watcher.Add(path); err != nil { 47 watcher.Close() 48 return nil, err 49 } 50 51 return &configDirectoryWatcher{ 52 watcher: watcher, 53 path: path, 54 lifecycle: lifecycle, 55 stop: make(chan struct{}), 56 }, nil 57 } 58 59 func isEtcdConfigFile(path string) bool { 60 b, err := ioutil.ReadFile(path) 61 if err != nil { 62 return false 63 } 64 65 // search for the "endpoints:" string 66 return strings.Contains(string(b), "endpoints:") 67 } 68 69 func (cdw *configDirectoryWatcher) watch() error { 70 log.WithField(fieldConfig, cdw.path).Debug("Starting config directory watcher") 71 72 files, err := ioutil.ReadDir(cdw.path) 73 if err != nil { 74 return err 75 } 76 77 for _, f := range files { 78 // A typical directory will look like this: 79 // lrwxrwxrwx. 1 root root 12 Jul 21 16:32 test5 -> ..data/test5 80 // lrwxrwxrwx. 1 root root 12 Jul 21 16:32 test7 -> ..data/test7 81 // 82 // Ignore all backing files and only read the symlinks 83 if strings.HasPrefix(f.Name(), "..") { 84 continue 85 } 86 87 absolutePath := path.Join(cdw.path, f.Name()) 88 if !isEtcdConfigFile(absolutePath) { 89 continue 90 } 91 92 log.WithField(fieldClusterName, f.Name()).WithField("mode", f.Mode()).Debugf("Found configuration in initial scan") 93 cdw.lifecycle.add(f.Name(), absolutePath) 94 } 95 96 go func() { 97 for { 98 select { 99 case event := <-cdw.watcher.Events: 100 name := filepath.Base(event.Name) 101 log.WithField(fieldClusterName, name).Debugf("Received fsnotify event: %+v", event) 102 switch event.Op { 103 case fsnotify.Create, fsnotify.Write, fsnotify.Chmod: 104 cdw.lifecycle.add(name, event.Name) 105 case fsnotify.Remove, fsnotify.Rename: 106 cdw.lifecycle.remove(name) 107 } 108 109 case err := <-cdw.watcher.Errors: 110 log.WithError(err).WithField("path", cdw.path).Warning("error encountered while watching directory with fsnotify") 111 112 case <-cdw.stop: 113 return 114 } 115 } 116 }() 117 118 return nil 119 } 120 121 func (cdw *configDirectoryWatcher) close() { 122 close(cdw.stop) 123 cdw.watcher.Close() 124 }