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  }