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  }