~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/wrench/wrench.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.Debugf("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
}