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