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