github.com/kubewharf/katalyst-core@v0.5.3/pkg/util/general/file.go (about) 1 /* 2 Copyright 2022 The Katalyst Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package general 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "path" 26 "path/filepath" 27 "strconv" 28 "strings" 29 "syscall" 30 "time" 31 32 "github.com/fsnotify/fsnotify" 33 "k8s.io/klog/v2" 34 utilfs "k8s.io/kubernetes/pkg/util/filesystem" 35 ) 36 37 const ( 38 FlockCoolingInterval = 6 * time.Second 39 FlockTryLockMaxTimes = 10 40 ) 41 42 type FileWatcherInfo struct { 43 // if Filename is empty, it means that we should watch all file events in all paths, 44 // otherwise, watch this specific file in all paths 45 Filename string 46 Path []string 47 Op fsnotify.Op 48 } 49 50 // RegisterFileEventWatcher inotify the given file and report the changed information 51 // to the caller through returned channel 52 func RegisterFileEventWatcher(stop <-chan struct{}, fileWatcherInfo FileWatcherInfo) (<-chan struct{}, error) { 53 watcherCh := make(chan struct{}) 54 55 watcher, err := fsnotify.NewWatcher() 56 if err != nil { 57 return nil, fmt.Errorf("new fsNotify watcher failed: %w", err) 58 } 59 60 go func() { 61 defer func() { 62 if err := recover(); err != nil { 63 klog.Errorf("RegisterFileEventWatcher panic: %v", err) 64 } 65 }() 66 67 defer func() { 68 close(watcherCh) 69 err = watcher.Close() 70 if err != nil { 71 klog.Errorf("failed close watcher: %v", err) 72 return 73 } 74 }() 75 76 for _, watcherInfoPath := range fileWatcherInfo.Path { 77 err = watcher.Add(watcherInfoPath) 78 if err != nil { 79 klog.Errorf("failed add event path %s: %s", watcherInfoPath, err) 80 continue 81 } 82 } 83 84 for { 85 select { 86 case event := <-watcher.Events: 87 filename := filepath.Base(event.Name) 88 if (fileWatcherInfo.Filename == "" || filename == fileWatcherInfo.Filename) && 89 (event.Op&fileWatcherInfo.Op) > 0 { 90 klog.Infof("fsNotify watcher notify %s", event) 91 watcherCh <- struct{}{} 92 } 93 case err = <-watcher.Errors: 94 klog.Warningf("%v watcher error: %v", fileWatcherInfo, err) 95 case <-stop: 96 klog.Infof("shutting down event watcher %v", fileWatcherInfo) 97 return 98 } 99 } 100 }() 101 102 return watcherCh, nil 103 } 104 105 // GetOneExistPath is to get one of exist paths 106 func GetOneExistPath(paths []string) string { 107 for _, path := range paths { 108 if IsPathExists(path) { 109 return path 110 } 111 } 112 return "" 113 } 114 115 // IsPathExists is to check this path whether exists 116 func IsPathExists(path string) bool { 117 _, err := os.Stat(path) 118 if err == nil { 119 return true 120 } 121 if os.IsNotExist(err) { 122 return false 123 } 124 return true 125 } 126 127 // ReadFileIntoLines read contents from the given file, and parse them into string slice; 128 // each string indicates a line in the file 129 func ReadFileIntoLines(filepath string) ([]string, error) { 130 lines, err := ioutil.ReadFile(filepath) 131 if err != nil { 132 return nil, fmt.Errorf("could not read file %s", filepath) 133 } 134 135 var contents []string 136 for _, line := range strings.Split(string(lines), "\n") { 137 if line == "" { 138 continue 139 } 140 contents = append(contents, line) 141 } 142 return contents, nil 143 } 144 145 // ReadFileIntoInt read contents from the given file, and parse them into integer 146 func ReadFileIntoInt(filepath string) (int, error) { 147 body, err := ioutil.ReadFile(filepath) 148 if err != nil { 149 return 0, fmt.Errorf("read file failed with error: %v", err) 150 } 151 152 i, err := strconv.Atoi(strings.TrimSpace(string(body))) 153 if err != nil { 154 return 0, fmt.Errorf("convert file content to int failed with error: %v", err) 155 } 156 157 return i, nil 158 } 159 160 func EnsureDirectory(dir string) error { 161 fs := utilfs.DefaultFs{} 162 if _, err := fs.Stat(dir); err != nil { 163 // MkdirAll returns nil if directory already exists. 164 return fs.MkdirAll(dir, 0o755) 165 } 166 return nil 167 } 168 169 type Flock struct { 170 LockFile string 171 lock *os.File 172 } 173 174 func createFlock(file string) (f *Flock, e error) { 175 if file == "" { 176 e = errors.New("cannot create flock on empty path") 177 return 178 } 179 lock, e := os.Create(file) 180 if e != nil { 181 return 182 } 183 return &Flock{ 184 LockFile: file, 185 lock: lock, 186 }, nil 187 } 188 189 func (f *Flock) Release() { 190 if f != nil && f.lock != nil { 191 _ = f.lock.Close() 192 } 193 } 194 195 func (f *Flock) Lock() (e error) { 196 if f == nil { 197 e = errors.New("cannot use lock on a nil flock") 198 return 199 } 200 return syscall.Flock(int(f.lock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) 201 } 202 203 func (f *Flock) Unlock() { 204 if f != nil { 205 _ = syscall.Flock(int(f.lock.Fd()), syscall.LOCK_UN) 206 } 207 } 208 209 // getUniqueLockWithTimeout try to acquire file lock 210 // returns the lock struct uf success; otherwise returns error 211 func getUniqueLockWithTimeout(filename string, duration time.Duration, tries int) (*Flock, error) { 212 lockDirPath := path.Dir(filename) 213 err := EnsureDirectory(lockDirPath) 214 if err != nil { 215 klog.Errorf("[GetUniqueLock] ensure lock directory: %s failed with error: %v", lockDirPath, err) 216 return nil, err 217 } 218 219 lock, err := createFlock(filename) 220 if err != nil { 221 klog.Errorf("[GetUniqueLock] create lock failed with error: %v", err) 222 return nil, err 223 } 224 225 tryCount := 0 226 for tryCount < tries { 227 err = lock.Lock() 228 if err == nil { 229 break 230 } 231 tryCount++ 232 klog.Infof("[GetUniqueLock] try to get unique lock, count: %d", tryCount) 233 time.Sleep(duration) 234 } 235 236 if err != nil { 237 return nil, err 238 } 239 240 klog.Infof("[GetUniqueLock] get lock successfully") 241 return lock, nil 242 } 243 244 // GetUniqueLock is a wrapper function for getUniqueLockWithTimeout with default configurations 245 func GetUniqueLock(filename string) (*Flock, error) { 246 return getUniqueLockWithTimeout(filename, FlockCoolingInterval, FlockTryLockMaxTimes) 247 } 248 249 // ReleaseUniqueLock release the given file lock 250 func ReleaseUniqueLock(lock *Flock) { 251 if lock == nil { 252 return 253 } 254 255 lock.Unlock() 256 lock.Release() 257 klog.Infof("[GetUniqueLock] release lock successfully") 258 } 259 260 func LoadJsonConfig(configAbsPath string, configObject interface{}) error { 261 configBytes, err := ioutil.ReadFile(configAbsPath) 262 if err != nil { 263 return err 264 } 265 266 err = json.Unmarshal(configBytes, configObject) 267 if err != nil { 268 return err 269 } 270 271 return nil 272 }