~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/worker/uniter/runner/debug/server_test.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 2013 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package debug
 
5
 
 
6
import (
 
7
        "fmt"
 
8
        "io/ioutil"
 
9
        "os"
 
10
        "os/exec"
 
11
        "path/filepath"
 
12
        "regexp"
 
13
        "runtime"
 
14
        "time"
 
15
 
 
16
        jc "github.com/juju/testing/checkers"
 
17
        gc "gopkg.in/check.v1"
 
18
 
 
19
        "github.com/juju/juju/testing"
 
20
)
 
21
 
 
22
type DebugHooksServerSuite struct {
 
23
        testing.BaseSuite
 
24
        ctx     *HooksContext
 
25
        fakebin string
 
26
        tmpdir  string
 
27
}
 
28
 
 
29
var _ = gc.Suite(&DebugHooksServerSuite{})
 
30
 
 
31
// echocommand outputs its name and arguments to stdout for verification,
 
32
// and exits with the value of $EXIT_CODE
 
33
var echocommand = `#!/bin/bash --norc
 
34
echo $(basename $0) $@
 
35
exit $EXIT_CODE
 
36
`
 
37
 
 
38
var fakecommands = []string{"tmux"}
 
39
 
 
40
func (s *DebugHooksServerSuite) SetUpTest(c *gc.C) {
 
41
        if runtime.GOOS == "windows" {
 
42
                c.Skip("bug 1403084: Currently debug does not work on windows")
 
43
        }
 
44
        s.fakebin = c.MkDir()
 
45
        s.tmpdir = c.MkDir()
 
46
        s.PatchEnvPathPrepend(s.fakebin)
 
47
        s.PatchEnvironment("TMPDIR", s.tmpdir)
 
48
        s.PatchEnvironment("TEST_RESULT", "")
 
49
        for _, name := range fakecommands {
 
50
                err := ioutil.WriteFile(filepath.Join(s.fakebin, name), []byte(echocommand), 0777)
 
51
                c.Assert(err, jc.ErrorIsNil)
 
52
        }
 
53
        s.ctx = NewHooksContext("foo/8")
 
54
        s.ctx.FlockDir = s.tmpdir
 
55
        s.PatchEnvironment("JUJU_UNIT_NAME", s.ctx.Unit)
 
56
}
 
57
 
 
58
func (s *DebugHooksServerSuite) TestFindSession(c *gc.C) {
 
59
        // Test "tmux has-session" failure. The error
 
60
        // message is the output of tmux has-session.
 
61
        os.Setenv("EXIT_CODE", "1")
 
62
        session, err := s.ctx.FindSession()
 
63
        c.Assert(session, gc.IsNil)
 
64
        c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta("tmux has-session -t "+s.ctx.Unit+"\n"))
 
65
        os.Setenv("EXIT_CODE", "")
 
66
 
 
67
        // tmux session exists, but missing debug-hooks file: error.
 
68
        session, err = s.ctx.FindSession()
 
69
        c.Assert(session, gc.IsNil)
 
70
        c.Assert(err, gc.NotNil)
 
71
        c.Assert(err, jc.Satisfies, os.IsNotExist)
 
72
 
 
73
        // Hooks file is present, empty.
 
74
        err = ioutil.WriteFile(s.ctx.ClientFileLock(), []byte{}, 0777)
 
75
        c.Assert(err, jc.ErrorIsNil)
 
76
        session, err = s.ctx.FindSession()
 
77
        c.Assert(session, gc.NotNil)
 
78
        c.Assert(err, jc.ErrorIsNil)
 
79
        // If session.hooks is empty, it'll match anything.
 
80
        c.Assert(session.MatchHook(""), jc.IsTrue)
 
81
        c.Assert(session.MatchHook("something"), jc.IsTrue)
 
82
 
 
83
        // Hooks file is present, non-empty
 
84
        err = ioutil.WriteFile(s.ctx.ClientFileLock(), []byte(`hooks: [foo, bar, baz]`), 0777)
 
85
        c.Assert(err, jc.ErrorIsNil)
 
86
        session, err = s.ctx.FindSession()
 
87
        c.Assert(session, gc.NotNil)
 
88
        c.Assert(err, jc.ErrorIsNil)
 
89
        // session should only match "foo", "bar" or "baz".
 
90
        c.Assert(session.MatchHook(""), jc.IsFalse)
 
91
        c.Assert(session.MatchHook("something"), jc.IsFalse)
 
92
        c.Assert(session.MatchHook("foo"), jc.IsTrue)
 
93
        c.Assert(session.MatchHook("bar"), jc.IsTrue)
 
94
        c.Assert(session.MatchHook("baz"), jc.IsTrue)
 
95
        c.Assert(session.MatchHook("foo bar baz"), jc.IsFalse)
 
96
}
 
97
 
 
98
func (s *DebugHooksServerSuite) TestRunHookExceptional(c *gc.C) {
 
99
        err := ioutil.WriteFile(s.ctx.ClientFileLock(), []byte{}, 0777)
 
100
        c.Assert(err, jc.ErrorIsNil)
 
101
        session, err := s.ctx.FindSession()
 
102
        c.Assert(session, gc.NotNil)
 
103
        c.Assert(err, jc.ErrorIsNil)
 
104
 
 
105
        flockAcquired := make(chan struct{}, 1)
 
106
        waitForFlock := func() {
 
107
                select {
 
108
                case <-flockAcquired:
 
109
                case <-time.After(testing.ShortWait):
 
110
                        c.Fatalf("timed out waiting for hook to acquire flock")
 
111
                }
 
112
        }
 
113
 
 
114
        // Run the hook in debug mode with no exit flock held.
 
115
        // The exit flock will be acquired immediately, and the
 
116
        // debug-hooks server process killed.
 
117
        s.PatchValue(&waitClientExit, func(*ServerSession) {
 
118
                flockAcquired <- struct{}{}
 
119
        })
 
120
        err = session.RunHook("myhook", s.tmpdir, os.Environ())
 
121
        c.Assert(err, gc.ErrorMatches, "signal: [kK]illed")
 
122
        waitForFlock()
 
123
 
 
124
        // Run the hook in debug mode, simulating the holding
 
125
        // of the exit flock. This simulates the client process
 
126
        // starting but not cleanly exiting (normally the .pid
 
127
        // file is updated, and the server waits on the client
 
128
        // process' death).
 
129
        ch := make(chan bool) // acquire the flock
 
130
        var clientExited bool
 
131
        s.PatchValue(&waitClientExit, func(*ServerSession) {
 
132
                clientExited = <-ch
 
133
                flockAcquired <- struct{}{}
 
134
        })
 
135
        go func() { ch <- true }() // asynchronously release the flock
 
136
        err = session.RunHook("myhook", s.tmpdir, os.Environ())
 
137
        waitForFlock()
 
138
        c.Assert(clientExited, jc.IsTrue)
 
139
        c.Assert(err, gc.ErrorMatches, "signal: [kK]illed")
 
140
}
 
141
 
 
142
func (s *DebugHooksServerSuite) TestRunHook(c *gc.C) {
 
143
        err := ioutil.WriteFile(s.ctx.ClientFileLock(), []byte{}, 0777)
 
144
        c.Assert(err, jc.ErrorIsNil)
 
145
        session, err := s.ctx.FindSession()
 
146
        c.Assert(session, gc.NotNil)
 
147
        c.Assert(err, jc.ErrorIsNil)
 
148
 
 
149
        const hookName = "myhook"
 
150
 
 
151
        // Run the hook in debug mode with the exit flock held,
 
152
        // and also create the .pid file. We'll populate it with
 
153
        // an invalid PID; this will cause the server process to
 
154
        // exit cleanly (as if the PID were real and no longer running).
 
155
        cmd := exec.Command("flock", s.ctx.ClientExitFileLock(), "-c", "sleep 5s")
 
156
        c.Assert(cmd.Start(), gc.IsNil)
 
157
        ch := make(chan error)
 
158
        go func() {
 
159
                ch <- session.RunHook(hookName, s.tmpdir, os.Environ())
 
160
        }()
 
161
 
 
162
        // Wait until either we find the debug dir, or the flock is released.
 
163
        ticker := time.Tick(10 * time.Millisecond)
 
164
        var debugdir os.FileInfo
 
165
        for debugdir == nil {
 
166
                select {
 
167
                case err = <-ch:
 
168
                        // flock was released before we found the debug dir.
 
169
                        c.Error("could not find hook.sh")
 
170
 
 
171
                case <-ticker:
 
172
                        tmpdir, err := os.Open(s.tmpdir)
 
173
                        if err != nil {
 
174
                                c.Fatalf("Failed to open $TMPDIR: %s", err)
 
175
                        }
 
176
                        fi, err := tmpdir.Readdir(-1)
 
177
                        if err != nil {
 
178
                                c.Fatalf("Failed to read $TMPDIR: %s", err)
 
179
                        }
 
180
                        tmpdir.Close()
 
181
                        for _, fi := range fi {
 
182
                                if fi.IsDir() {
 
183
                                        hooksh := filepath.Join(s.tmpdir, fi.Name(), "hook.sh")
 
184
                                        if _, err = os.Stat(hooksh); err == nil {
 
185
                                                debugdir = fi
 
186
                                                break
 
187
                                        }
 
188
                                }
 
189
                        }
 
190
                        if debugdir != nil {
 
191
                                break
 
192
                        }
 
193
                        time.Sleep(10 * time.Millisecond)
 
194
                }
 
195
        }
 
196
 
 
197
        envsh := filepath.Join(s.tmpdir, debugdir.Name(), "env.sh")
 
198
        s.verifyEnvshFile(c, envsh, hookName)
 
199
 
 
200
        hookpid := filepath.Join(s.tmpdir, debugdir.Name(), "hook.pid")
 
201
        err = ioutil.WriteFile(hookpid, []byte("not a pid"), 0777)
 
202
        c.Assert(err, jc.ErrorIsNil)
 
203
 
 
204
        // RunHook should complete without waiting to be
 
205
        // killed, and despite the exit lock being held.
 
206
        err = <-ch
 
207
        c.Assert(err, jc.ErrorIsNil)
 
208
        cmd.Process.Kill() // kill flock
 
209
}
 
210
 
 
211
func (s *DebugHooksServerSuite) verifyEnvshFile(c *gc.C, envshPath string, hookName string) {
 
212
        data, err := ioutil.ReadFile(envshPath)
 
213
        c.Assert(err, jc.ErrorIsNil)
 
214
        contents := string(data)
 
215
        c.Assert(contents, jc.Contains, fmt.Sprintf("JUJU_UNIT_NAME=%q", s.ctx.Unit))
 
216
        c.Assert(contents, jc.Contains, fmt.Sprintf("JUJU_HOOK_NAME=%q", hookName))
 
217
        c.Assert(contents, jc.Contains, fmt.Sprintf(`PS1="%s:%s %% "`, s.ctx.Unit, hookName))
 
218
}