Merge lp:~fgimenez/snappy/wait-for-idle-partition into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Federico Gimenez on 2015-10-01
Status: Merged
Approved by: Leo Arias on 2015-10-06
Approved revision: 727
Merged at revision: 740
Proposed branch: lp:~fgimenez/snappy/wait-for-idle-partition
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Prerequisite: lp:~fgimenez/snappy/extract-mount-functions
Diff against target: 364 lines (+222/-35)
4 files modified
_integration-tests/testutils/cli/cli.go (+15/-8)
_integration-tests/testutils/cli/cli_test.go (+30/-2)
_integration-tests/testutils/partition/partition.go (+58/-7)
_integration-tests/testutils/partition/partition_test.go (+119/-18)
To merge this branch: bzr merge lp:~fgimenez/snappy/wait-for-idle-partition
Reviewer Review Type Date Requested Status
Leo Arias 2015-10-01 Approve on 2015-10-06
Review via email: mp+273100@code.launchpad.net

Commit Message

Commands to wait for partition idle before trying to remount

Description of the Change

Commands to wait for partition idle before trying to remount

workaround for bug #1496665

To post a comment you must log in.
727. By Federico Gimenez on 2015-10-02

typo

Leo Arias (elopio) wrote :

all tests pass, and looks good. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '_integration-tests/testutils/cli/cli.go'
2--- _integration-tests/testutils/cli/cli.go 2015-10-01 08:21:24 +0000
3+++ _integration-tests/testutils/cli/cli.go 2015-10-02 13:11:04 +0000
4@@ -33,19 +33,15 @@
5 // ExecCommand executes a shell command and returns a string with the output
6 // of the command. In case of error, it will fail the test.
7 func ExecCommand(c *check.C, cmds ...string) string {
8- fmt.Println(strings.Join(cmds, " "))
9- cmd := execCommand(cmds[0], cmds[1:len(cmds)]...)
10- output, err := cmd.CombinedOutput()
11- stringOutput := string(output)
12- fmt.Print(stringOutput)
13- c.Assert(err, check.IsNil, check.Commentf("Error: %v", stringOutput))
14- return stringOutput
15+ output, err := ExecCommandErr(cmds...)
16+ c.Assert(err, check.IsNil, check.Commentf("Error: %v", output))
17+ return output
18 }
19
20 // ExecCommandToFile executes a shell command and saves the output of the
21 // command to a file. In case of error, it will fail the test.
22 func ExecCommandToFile(c *check.C, filename string, cmds ...string) {
23- cmd := execCommand(cmds[0], cmds[1:len(cmds)]...)
24+ cmd := execCommand(cmds[0], cmds[1:]...)
25 outfile, err := os.Create(filename)
26 c.Assert(err, check.IsNil, check.Commentf("Error creating output file %s", filename))
27
28@@ -55,3 +51,14 @@
29 err = cmd.Run()
30 c.Assert(err, check.IsNil, check.Commentf("Error executing command '%v': %v", cmds, err))
31 }
32+
33+// ExecCommandErr executes a shell command and returns a string with the output
34+// of the command and eventually the obtained error
35+func ExecCommandErr(cmds ...string) (output string, err error) {
36+ fmt.Println(strings.Join(cmds, " "))
37+ cmd := execCommand(cmds[0], cmds[1:]...)
38+ outputByte, err := cmd.CombinedOutput()
39+ output = string(outputByte)
40+ fmt.Print(output)
41+ return
42+}
43
44=== modified file '_integration-tests/testutils/cli/cli_test.go'
45--- _integration-tests/testutils/cli/cli_test.go 2015-10-01 08:21:24 +0000
46+++ _integration-tests/testutils/cli/cli_test.go 2015-10-02 13:11:04 +0000
47@@ -36,6 +36,7 @@
48
49 type cliTestSuite struct {
50 backExecCommand func(string, ...string) *exec.Cmd
51+ helperProcess string
52 }
53
54 var _ = check.Suite(&cliTestSuite{})
55@@ -49,8 +50,12 @@
56 execCommand = s.backExecCommand
57 }
58
59+func (s *cliTestSuite) SetUpTest(c *check.C) {
60+ s.helperProcess = "TestHelperProcess"
61+}
62+
63 func (s *cliTestSuite) fakeExecCommand(command string, args ...string) *exec.Cmd {
64- cs := []string{"-check.f=cliTestSuite.TestHelperProcess", "--", command}
65+ cs := []string{"-check.f=cliTestSuite." + s.helperProcess, "--", command}
66 cs = append(cs, args...)
67 cmd := exec.Command(os.Args[0], cs...)
68 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
69@@ -58,11 +63,19 @@
70 }
71
72 func (s *cliTestSuite) TestHelperProcess(c *check.C) {
73+ baseHelperProcess(0)
74+}
75+
76+func (s *cliTestSuite) TestHelperProcessErr(c *check.C) {
77+ baseHelperProcess(1)
78+}
79+
80+func baseHelperProcess(exitValue int) {
81 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
82 return
83 }
84 fmt.Fprintf(os.Stdout, execOutput)
85- os.Exit(0)
86+ os.Exit(exitValue)
87 }
88
89 func (s *cliTestSuite) TestExecCommand(c *check.C) {
90@@ -83,3 +96,18 @@
91 c.Assert(err, check.IsNil)
92 c.Assert(string(actualFileContents), check.Equals, execOutput)
93 }
94+
95+func (s *cliTestSuite) TestExecCommandErr(c *check.C) {
96+ actualOutput, err := ExecCommandErr("mycmd")
97+
98+ c.Assert(actualOutput, check.Equals, execOutput)
99+ c.Assert(err, check.IsNil)
100+}
101+
102+func (s *cliTestSuite) TestExecCommandErrWithError(c *check.C) {
103+ s.helperProcess = "TestHelperProcessErr"
104+ actualOutput, err := ExecCommandErr("mycmd")
105+
106+ c.Assert(actualOutput, check.Equals, execOutput)
107+ c.Assert(err, check.NotNil)
108+}
109
110=== modified file '_integration-tests/testutils/partition/partition.go'
111--- _integration-tests/testutils/partition/partition.go 2015-10-01 09:37:04 +0000
112+++ _integration-tests/testutils/partition/partition.go 2015-10-02 13:11:04 +0000
113@@ -20,19 +20,70 @@
114 package partition
115
116 import (
117+ "bufio"
118+ "fmt"
119+ "regexp"
120+ "strings"
121+
122 "gopkg.in/check.v1"
123
124 "launchpad.net/snappy/_integration-tests/testutils/cli"
125-)
126-
127-var execCommand = cli.ExecCommand
128+ "launchpad.net/snappy/_integration-tests/testutils/wait"
129+)
130+
131+const (
132+ // beginning of the string returned by lsof -V when the partition
133+ // is not being used, in this case the exit status of lsof is 1
134+ lsofNotUsed = "lsof: no file system use located"
135+ // custom pattern to be returned by the check functions
136+ lsofNotBeingWritten = "volume-is-idle"
137+)
138+
139+var (
140+ execCommand = cli.ExecCommandErr
141+ waitForFunction = wait.ForFunction
142+)
143
144 // MakeWritable remounts a path with read and write permissions.
145-func MakeWritable(c *check.C, path string) {
146- execCommand(c, "sudo", "mount", "-o", "remount,rw", path)
147+func MakeWritable(c *check.C, path string) (err error) {
148+ return commonMount(c, path, "remount,rw")
149 }
150
151 // MakeReadonly remounts a path with only read permissions.
152-func MakeReadonly(c *check.C, path string) {
153- execCommand(c, "sudo", "mount", "-o", "remount,ro", path)
154+func MakeReadonly(c *check.C, path string) (err error) {
155+ return commonMount(c, path, "remount,ro")
156+}
157+
158+func commonMount(c *check.C, path, mountOption string) (err error) {
159+ err = waitForFunction(c, lsofNotBeingWritten, checkPathBusyFunc(path))
160+
161+ if err != nil {
162+ return
163+ }
164+
165+ execCommand("sudo", "mount", "-o", mountOption, path)
166+ return
167+}
168+
169+func checkPathBusyFunc(path string) func() (string, error) {
170+ return func() (result string, err error) {
171+ // lsof exit status is 1 for unused partitions
172+ var info string
173+ if info, err = execCommand("lsof", "-V", path); err != nil {
174+ // check if the output matches lsofNotUsed
175+ if !strings.HasPrefix(info, lsofNotUsed) {
176+ return info, err
177+ }
178+ }
179+ reader := strings.NewReader(info)
180+ scanner := bufio.NewScanner(reader)
181+ for scanner.Scan() {
182+ fields := strings.Fields(scanner.Text())
183+ if match, _ := regexp.MatchString("^[0-9]+w$", fields[3]); match {
184+ fmt.Printf("match! %s", fields[3])
185+ return fields[3], nil
186+ }
187+ }
188+ return lsofNotBeingWritten, nil
189+ }
190 }
191
192=== modified file '_integration-tests/testutils/partition/partition_test.go'
193--- _integration-tests/testutils/partition/partition_test.go 2015-10-01 09:59:40 +0000
194+++ _integration-tests/testutils/partition/partition_test.go 2015-10-02 13:11:04 +0000
195@@ -20,50 +20,151 @@
196 package partition
197
198 import (
199+ "fmt"
200 "strings"
201
202 "gopkg.in/check.v1"
203 )
204
205-const path = "mypath"
206+const (
207+ path = "mypath"
208+ writableCmd = "sudo mount -o remount,rw " + path
209+ readonlyCmd = "sudo mount -o remount,ro " + path
210+ waitCmd = lsofNotBeingWritten
211+)
212
213 type partitionTestSuite struct {
214- execCalls map[string]int
215- backExecCommand func(*check.C, ...string) string
216+ execCalls map[string]int
217+ waitCalls map[string]int
218+ execOutput string
219+ execError bool
220+ backExecCommand func(...string) (string, error)
221+ backWaitForFunction func(*check.C, string, func() (string, error)) error
222+ waitError bool
223 }
224
225 var _ = check.Suite(&partitionTestSuite{})
226
227 func (s *partitionTestSuite) SetUpSuite(c *check.C) {
228 s.backExecCommand = execCommand
229+ s.backWaitForFunction = waitForFunction
230 execCommand = s.fakeExecCommand
231+ waitForFunction = s.fakeWaitForFunction
232 }
233
234 func (s *partitionTestSuite) TearDownSuite(c *check.C) {
235 execCommand = s.backExecCommand
236+ waitForFunction = s.backWaitForFunction
237 }
238
239 func (s *partitionTestSuite) SetUpTest(c *check.C) {
240 s.execCalls = make(map[string]int)
241+ s.waitCalls = make(map[string]int)
242+ s.waitError = false
243+ s.execOutput = ""
244+ s.execError = false
245 }
246
247-func (s *partitionTestSuite) fakeExecCommand(c *check.C, args ...string) (output string) {
248+func (s *partitionTestSuite) fakeExecCommand(args ...string) (output string, err error) {
249 s.execCalls[strings.Join(args, " ")]++
250+
251+ if s.execError {
252+ err = fmt.Errorf("Exec error!")
253+ }
254+
255+ return s.execOutput, err
256+}
257+
258+func (s *partitionTestSuite) fakeWaitForFunction(c *check.C, pattern string, f func() (string, error)) (err error) {
259+ s.waitCalls[pattern]++
260+
261+ if s.waitError {
262+ err = fmt.Errorf("Wait error!")
263+ }
264+
265 return
266 }
267
268-func (s *partitionTestSuite) TestMakeWritable(c *check.C) {
269- cmd := "sudo mount -o remount,rw " + path
270-
271- MakeWritable(c, path)
272-
273- c.Assert(s.execCalls[cmd], check.Equals, 1)
274-}
275-
276-func (s *partitionTestSuite) TestMakeReadOnly(c *check.C) {
277- cmd := "sudo mount -o remount,ro " + path
278-
279- MakeReadonly(c, path)
280-
281- c.Assert(s.execCalls[cmd], check.Equals, 1)
282+func (s *partitionTestSuite) TestMakeWritableCallsExecCommand(c *check.C) {
283+ err := MakeWritable(c, path)
284+
285+ c.Assert(err, check.IsNil)
286+ c.Assert(s.execCalls[writableCmd], check.Equals, 1)
287+}
288+
289+func (s *partitionTestSuite) TestMakeWritableWaitsForIdlePartition(c *check.C) {
290+ err := MakeWritable(c, path)
291+
292+ c.Assert(err, check.IsNil)
293+ c.Assert(s.waitCalls[waitCmd], check.Equals, 1)
294+}
295+
296+func (s *partitionTestSuite) TestMakeWritableReturnsWaitError(c *check.C) {
297+ s.waitError = true
298+ err := MakeWritable(c, path)
299+
300+ c.Assert(err, check.NotNil)
301+ c.Assert(s.waitCalls[waitCmd], check.Equals, 1)
302+ c.Assert(s.execCalls[writableCmd], check.Equals, 0)
303+}
304+
305+func (s *partitionTestSuite) TestMakeReadOnlyCallsExecCommand(c *check.C) {
306+ err := MakeReadonly(c, path)
307+
308+ c.Assert(err, check.IsNil)
309+ c.Assert(s.execCalls[readonlyCmd], check.Equals, 1)
310+}
311+
312+func (s *partitionTestSuite) TestMakeReadonlyWaitsForIdlePartition(c *check.C) {
313+ err := MakeReadonly(c, path)
314+
315+ c.Assert(err, check.IsNil)
316+ c.Assert(s.waitCalls[waitCmd], check.Equals, 1)
317+}
318+
319+func (s *partitionTestSuite) TestMakeReadonlyReturnsWaitError(c *check.C) {
320+ s.waitError = true
321+ err := MakeReadonly(c, path)
322+
323+ c.Assert(err, check.NotNil)
324+ c.Assert(s.waitCalls[waitCmd], check.Equals, 1)
325+ c.Assert(s.execCalls[readonlyCmd], check.Equals, 0)
326+}
327+
328+func (s *partitionTestSuite) TestCheckPartitionBusyFunc(c *check.C) {
329+ testCases := []struct {
330+ execCommandOutput string
331+ expected string
332+ }{
333+ {`prg 4827 user mem REG 8,2 3339 10354893 /usr/share/prg
334+prg 4827 user 15w REG 8,2 197132 12452026 /home/user/prg`, "15w"},
335+ {`prg 4827 user mem REG 8,2 3339 10354893 /usr/share/prg
336+prg 4827 user 15w REG 8,2 197132 12452026 /home/user/prg
337+prg 4827 user 1w REG 8,2 197132 12452026 /home/user/prg`, "15w"},
338+ {`prg 4827 user mem REG 8,2 3339 10354893 /usr/share/prg`, lsofNotBeingWritten},
339+ {`prg 4827 user cwd REG 8,2 3339 10354893 /usr/share/prg`, lsofNotBeingWritten},
340+ }
341+
342+ for _, testCase := range testCases {
343+ s.execOutput = testCase.execCommandOutput
344+ f := checkPathBusyFunc(path)
345+
346+ actual, err := f()
347+ c.Check(err, check.IsNil)
348+ c.Check(actual, check.Equals, testCase.expected,
349+ check.Commentf("input text %s, expected output %s, obtained %s",
350+ testCase.execCommandOutput, testCase.expected, actual))
351+ }
352+}
353+
354+func (s *partitionTestSuite) TestCheckPartitionBusyFuncReturnsErrorOnLsofError(c *check.C) {
355+ s.execOutput = "not a lsof common output on not used partitions"
356+ s.execError = true
357+
358+ f := checkPathBusyFunc(path)
359+
360+ actual, err := f()
361+
362+ c.Check(err, check.NotNil)
363+ c.Check(actual, check.Equals, s.execOutput)
364 }

Subscribers

People subscribed via source and target branches