github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/wrench/wrench.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package wrench 5 6 import ( 7 "bufio" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "sync" 13 14 "github.com/juju/loggo" 15 16 "github.com/juju/juju/core/paths" 17 ) 18 19 var ( 20 enabledMu sync.Mutex 21 enabled = true 22 23 dataDir = paths.DataDir(paths.CurrentOS()) 24 wrenchDir = filepath.Join(dataDir, "wrench") 25 jujuUid = os.Getuid() 26 ) 27 28 var logger = loggo.GetLogger("juju.wrench") 29 30 // IsActive returns true if a "wrench" of a certain category and 31 // feature should be "dropped in the works". 32 // 33 // This function may be called at specific points in the Juju codebase 34 // to introduce otherwise hard to induce failure modes for the 35 // purposes of manual or CI testing. The "<juju_datadir>/wrench/" 36 // directory will be checked for "wrench files" which this function 37 // looks for. 38 // 39 // Wrench files are line-based, with each line indicating some 40 // (mis-)feature to enable for a given part of the code. The should be 41 // created on the host where the fault should be triggered. 42 // 43 // For example, /var/lib/juju/wrench/machine-agent could contain: 44 // 45 // refuse-upgrade 46 // fail-api-server-start 47 // 48 // The caller need not worry about errors. Any errors that occur will 49 // be logged and false will be returned. 50 func IsActive(category, feature string) bool { 51 if !IsEnabled() { 52 return false 53 } 54 if !checkWrenchDir(wrenchDir) { 55 return false 56 } 57 fileName := filepath.Join(wrenchDir, category) 58 if !checkWrenchFile(category, feature, fileName) { 59 return false 60 } 61 62 wrenchFile, err := os.Open(fileName) 63 if err != nil { 64 logger.Errorf("unable to read wrench data for %s/%s (ignored): %v", 65 category, feature, err) 66 return false 67 } 68 defer wrenchFile.Close() 69 lines := bufio.NewScanner(wrenchFile) 70 for lines.Scan() { 71 line := strings.TrimSpace(lines.Text()) 72 if line == feature { 73 logger.Tracef("wrench for %s/%s is active", category, feature) 74 return true 75 } 76 } 77 if err := lines.Err(); err != nil { 78 logger.Errorf("error while reading wrench data for %s/%s (ignored): %v", 79 category, feature, err) 80 } 81 return false 82 } 83 84 // SetEnabled turns the wrench feature on or off globally. 85 // 86 // If false is given, all future IsActive calls will unconditionally 87 // return false. If true is given, all future IsActive calls will 88 // return true for active wrenches. 89 // 90 // The previous value for the global wrench enable flag is returned. 91 func SetEnabled(next bool) bool { 92 enabledMu.Lock() 93 defer enabledMu.Unlock() 94 previous := enabled 95 enabled = next 96 return previous 97 } 98 99 // IsEnabled returns true if the wrench feature is turned on globally. 100 func IsEnabled() bool { 101 enabledMu.Lock() 102 defer enabledMu.Unlock() 103 return enabled 104 } 105 106 var stat = os.Stat // To support patching 107 108 func checkWrenchDir(dirName string) bool { 109 dirinfo, err := stat(dirName) 110 if err != nil { 111 logger.Tracef("couldn't read wrench directory: %v", err) 112 return false 113 } 114 if !isOwnedByJujuUser(dirinfo) { 115 logger.Errorf("wrench directory has incorrect ownership - wrench "+ 116 "functionality disabled (%s)", wrenchDir) 117 return false 118 } 119 return true 120 } 121 122 func checkWrenchFile(category, feature, fileName string) bool { 123 fileinfo, err := stat(fileName) 124 if err != nil { 125 logger.Tracef("no wrench data for %s/%s (ignored): %v", 126 category, feature, err) 127 return false 128 } 129 if !isOwnedByJujuUser(fileinfo) { 130 logger.Errorf("wrench file for %s/%s has incorrect ownership "+ 131 "- ignoring %s", category, feature, fileName) 132 return false 133 } 134 // Windows is not fully POSIX compliant 135 if runtime.GOOS != "windows" { 136 if fileinfo.Mode()&0022 != 0 { 137 logger.Errorf("wrench file for %s/%s should only be writable by "+ 138 "owner - ignoring %s", category, feature, fileName) 139 return false 140 } 141 } 142 return true 143 }