Merge lp:~elopio/snappy/cross-compile-debs-merge-failover into lp:~fgimenez/snappy/cross-compile-debs
- cross-compile-debs-merge-failover
- Merge into cross-compile-debs
Proposed by
Leo Arias
Status: | Merged |
---|---|
Approved by: | Federico Gimenez |
Approved revision: | 514 |
Merged at revision: | 514 |
Proposed branch: | lp:~elopio/snappy/cross-compile-debs-merge-failover |
Merge into: | lp:~fgimenez/snappy/cross-compile-debs |
Diff against target: |
2937 lines (+1178/-736) 36 files modified
_integration-tests/README (+0/-1) _integration-tests/main.go (+36/-68) _integration-tests/snappy-selftest (+4/-4) _integration-tests/tests/80_test_failover (+0/-48) _integration-tests/tests/common_test.go (+74/-0) _integration-tests/tests/failover_rclocal_crash_test.go (+47/-0) _integration-tests/tests/failover_systemd_loop_test.go (+114/-0) _integration-tests/tests/failover_test.go (+151/-0) _integration-tests/tests/failover_zero_size_file_test.go (+120/-0) _integration-tests/tests/install_test.go (+14/-31) cmd/snappy/cmd_internal_unpack.go (+3/-3) cmd/snappy/cmd_low_level_unpack_test.go (+5/-5) debian/control (+0/-7) debian/integration-tests/control (+1/-1) debian/rules (+0/-6) debian/ubuntu-snappy-tests.install (+0/-1) helpers/touch.go (+2/-0) partition/assets.go (+87/-0) partition/assets_test.go (+36/-0) partition/bootloader.go (+11/-61) partition/bootloader_grub.go (+6/-20) partition/bootloader_grub_test.go (+5/-14) partition/bootloader_uboot.go (+23/-30) partition/bootloader_uboot_test.go (+17/-27) partition/dirs.go (+41/-0) partition/mount.go (+153/-0) partition/mount_test.go (+64/-0) partition/partition.go (+66/-299) partition/partition_test.go (+50/-98) partition/utils.go (+19/-0) snappy/errors.go (+2/-0) snappy/install.go (+2/-2) snappy/security.go (+5/-0) snappy/security_test.go (+7/-0) snappy/systemimage.go (+8/-1) snappy/systemimage_test.go (+5/-9) |
To merge this branch: | bzr merge lp:~elopio/snappy/cross-compile-debs-merge-failover |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Federico Gimenez | Approve | ||
Review via email: mp+263051@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '_integration-tests/README' |
2 | --- _integration-tests/README 2015-06-17 15:42:00 +0000 |
3 | +++ _integration-tests/README 2015-06-26 00:24:26 +0000 |
4 | @@ -19,4 +19,3 @@ |
5 | shell tests) with: |
6 | |
7 | $ go run _integration-test/main.go |
8 | - |
9 | |
10 | === modified file '_integration-tests/main.go' |
11 | --- _integration-tests/main.go 2015-06-25 08:57:01 +0000 |
12 | +++ _integration-tests/main.go 2015-06-26 00:24:26 +0000 |
13 | @@ -30,17 +30,15 @@ |
14 | ) |
15 | |
16 | const ( |
17 | - baseDir = "/tmp/snappy-test" |
18 | - debsTestBedPath = "/tmp/snappy-debs" |
19 | - defaultRelease = "rolling" |
20 | - defaultChannel = "edge" |
21 | - defaultArch = "amd64" |
22 | - defaultDist = "wily" |
23 | - defaultSSHPort = 22 |
24 | + baseDir = "/tmp/snappy-test" |
25 | + defaultRelease = "rolling" |
26 | + defaultChannel = "edge" |
27 | + defaultArch = "amd64" |
28 | + defaultSSHPort = 22 |
29 | ) |
30 | |
31 | var ( |
32 | - defaultDebsDir = filepath.Join(baseDir, "debs") |
33 | + testsDir = filepath.Join(baseDir, "tests") |
34 | imageDir = filepath.Join(baseDir, "image") |
35 | outputDir = filepath.Join(baseDir, "output") |
36 | imageTarget = filepath.Join(imageDir, "snappy.img") |
37 | @@ -52,25 +50,21 @@ |
38 | "--", "-i", imageTarget}...) |
39 | ) |
40 | |
41 | -func setupAndRunTests(useFlashedImage bool, debsDir, arch, testbedIP string, testbedPort int) { |
42 | +func setupAndRunTests(arch, testbedIP string, testbedPort int) { |
43 | + buildTests(arch) |
44 | + |
45 | rootPath := getRootPath() |
46 | - if useFlashedImage { |
47 | - // using the flashed image, so no debs must be installed. |
48 | - debsDir = "" |
49 | - } else if debsDir == defaultDebsDir { |
50 | - buildDebs(rootPath, debsDir, arch) |
51 | - } |
52 | if testbedIP == "" { |
53 | - createImage(defaultRelease, defaultChannel, getArchForImage()) |
54 | - adtRun(rootPath, debsDir, kvmSSHOptions) |
55 | + createImage(defaultRelease, defaultChannel) |
56 | + adtRun(rootPath, kvmSSHOptions) |
57 | } else { |
58 | execCommand("ssh-copy-id", "-p", strconv.Itoa(testbedPort), "ubuntu@"+testbedIP) |
59 | - adtRun(rootPath, debsDir, remoteTestbedSSHOptions(testbedIP, testbedPort)) |
60 | + adtRun(rootPath, remoteTestbedSSHOptions(testbedIP, testbedPort)) |
61 | } |
62 | } |
63 | |
64 | func execCommand(cmds ...string) { |
65 | - cmd := exec.Command(cmds[0], cmds[1:len(cmds)]...) |
66 | + cmd := exec.Command(cmds[0], cmds[1:]...) |
67 | cmd.Stdout = os.Stdout |
68 | cmd.Stderr = os.Stderr |
69 | if err := cmd.Run(); err != nil { |
70 | @@ -78,59 +72,40 @@ |
71 | } |
72 | } |
73 | |
74 | -func buildDebs(rootPath, destDir, arch string) { |
75 | - fmt.Println("Building debs...") |
76 | - prepareTargetDir(destDir) |
77 | - buildCommand := []string{"bzr", "bd", "-v", |
78 | - fmt.Sprintf("--result-dir=%s", destDir), |
79 | - "--split", |
80 | - rootPath, |
81 | - "--", |
82 | - } |
83 | - if arch != defaultArch { |
84 | - archFlag := fmt.Sprintf("-a%s", arch) |
85 | - buildCommand = append(buildCommand, archFlag) |
86 | - } |
87 | - dontSignDebs := []string{"-uc", "-us"} |
88 | - buildCommand = append(buildCommand, dontSignDebs...) |
89 | - |
90 | - fmt.Println(buildCommand) |
91 | - execCommand(buildCommand...) |
92 | +func buildTests(arch string) { |
93 | + fmt.Println("Building tests") |
94 | + prepareTargetDir(testsDir) |
95 | + if arch != "" { |
96 | + defer os.Setenv("GOARCH", os.Getenv("GOARCH")) |
97 | + os.Setenv("GOARCH", arch) |
98 | + } |
99 | + execCommand("go", "test", "-c", "./_integration-tests/tests") |
100 | + os.Rename("tests.test", "snappy.tests") |
101 | } |
102 | |
103 | -func createImage(release, channel, arch string) { |
104 | +func createImage(release, channel string) { |
105 | fmt.Println("Creating image...") |
106 | prepareTargetDir(imageDir) |
107 | execCommand( |
108 | "sudo", "ubuntu-device-flash", "--verbose", |
109 | "core", release, |
110 | "-o", imageTarget, |
111 | - fmt.Sprintf("--oem=%s", arch), |
112 | "--channel", channel, |
113 | "--developer-mode") |
114 | } |
115 | |
116 | -func adtRun(rootPath, debsDir string, testbedOptions []string) { |
117 | +func adtRun(rootPath string, testbedOptions []string) { |
118 | fmt.Println("Calling adt-run...") |
119 | prepareTargetDir(outputDir) |
120 | + |
121 | cmd := []string{ |
122 | - "adt-run", "-B", |
123 | + "adt-run", |
124 | + "-B", |
125 | + "--setup-commands", "touch /run/autopkgtest_no_reboot.stamp", |
126 | "--override-control", "debian/integration-tests/control", |
127 | "--built-tree", rootPath, |
128 | - "--output-dir", outputDir} |
129 | - |
130 | - if debsDir != "" { |
131 | - debsSetup := []string{ |
132 | - "--setup-commands", "touch /run/autopkgtest_no_reboot.stamp", |
133 | - "--setup-commands", "mount -o remount,rw /", |
134 | - "--setup-commands", |
135 | - fmt.Sprintf("dpkg -i %s/*deb", debsTestBedPath), |
136 | - "--setup-commands", |
137 | - "sync; sleep 2; mount -o remount,ro /", |
138 | - fmt.Sprintf("--copy=%s:%s", debsDir, debsTestBedPath)} |
139 | - cmd = append(cmd, debsSetup...) |
140 | + "--output-dir", outputDir, |
141 | } |
142 | - |
143 | execCommand(append(cmd, testbedOptions...)...) |
144 | } |
145 | |
146 | @@ -139,7 +114,8 @@ |
147 | "-H", testbedIP, |
148 | "-p", strconv.Itoa(testbedPort), |
149 | "-l", "ubuntu", |
150 | - "-i", filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa")} |
151 | + "-i", filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa"), |
152 | + "--reboot"} |
153 | return append(commonSSHOptions, options...) |
154 | } |
155 | |
156 | @@ -159,25 +135,17 @@ |
157 | return dir |
158 | } |
159 | |
160 | -func getArchForImage() string { |
161 | - return fmt.Sprintf("generic-%s", defaultArch) |
162 | -} |
163 | - |
164 | func main() { |
165 | var ( |
166 | - useFlashedImage = flag.Bool("installed-image", false, |
167 | - "Wether we should install the snappy version from the branch or use the one installed on the image") |
168 | - debsDir = flag.String("debs-dir", defaultDebsDir, |
169 | - "Directory with th1e snappy debian packages.") |
170 | - arch = flag.String("arch", defaultArch, |
171 | - "Target architecture (amd64, armhf)") |
172 | + arch = flag.String("arch", "", |
173 | + "Architecture of the test bed. Defaults to use the same architecture as the host.") |
174 | testbedIP = flag.String("ip", "", |
175 | - "IP of the testbed to run the tests in") |
176 | + "IP of the testbed. If no IP is passed, a virtual machine will be created for the test.") |
177 | testbedPort = flag.Int("port", defaultSSHPort, |
178 | - "SSH port of the testbed") |
179 | + "SSH port of the testbed. Defaults to use port 22.") |
180 | ) |
181 | |
182 | flag.Parse() |
183 | |
184 | - setupAndRunTests(*useFlashedImage, *debsDir, *arch, *testbedIP, *testbedPort) |
185 | + setupAndRunTests(*arch, *testbedIP, *testbedPort) |
186 | } |
187 | |
188 | === modified file '_integration-tests/snappy-selftest' |
189 | --- _integration-tests/snappy-selftest 2015-06-16 06:47:24 +0000 |
190 | +++ _integration-tests/snappy-selftest 2015-06-26 00:24:26 +0000 |
191 | @@ -4,7 +4,7 @@ |
192 | |
193 | MYDIR=$(dirname $0) |
194 | |
195 | -. $MYDIR/tests/framework |
196 | +. "$MYDIR"/tests/framework |
197 | |
198 | SNAPPY=snappy |
199 | |
200 | @@ -15,9 +15,9 @@ |
201 | exit 1 |
202 | fi |
203 | |
204 | -. $MYDIR/tests/settings |
205 | +. "$MYDIR"/tests/settings |
206 | |
207 | -. $MYDIR/tests/common.sh |
208 | +. "$MYDIR"/tests/common.sh |
209 | |
210 | # prepare the environment |
211 | if [ -z "$ADT_REBOOT_MARK" ]; then |
212 | @@ -27,7 +27,7 @@ |
213 | sudo snappy remove xkcd-webserver 2>&1 || true |
214 | fi |
215 | |
216 | -for test in $MYDIR/tests/*_test_*[!~#]; do |
217 | +for test in "$MYDIR"/tests/*_test_*[!~#]; do |
218 | CURRENT_TEST=$(basename $test) |
219 | |
220 | # after a reboot, skip ahead to the test that triggered it |
221 | |
222 | === removed file '_integration-tests/tests/80_test_failover' |
223 | --- _integration-tests/tests/80_test_failover 2015-06-15 15:36:29 +0000 |
224 | +++ _integration-tests/tests/80_test_failover 1970-01-01 00:00:00 +0000 |
225 | @@ -1,48 +0,0 @@ |
226 | -# Test that we rollback on failed upgrades |
227 | -# (needs autopkgtest with a supporting runner). |
228 | - |
229 | -test() { |
230 | - can_reboot || { echo "SKIP: cannot reboot testbed"; return; } |
231 | - |
232 | - # debug |
233 | - version_info |
234 | - boot_info |
235 | - |
236 | - if after_reboot; then |
237 | - # reboot: ensure we booted back into original |
238 | - orig_current=$(cat "${ADT_ARTIFACTS}/current") |
239 | - if [ "$orig_current" != "$current" ]; then |
240 | - fail "did not failover to good version (\"$orig_current\" != \"$current\")" |
241 | - fi |
242 | - |
243 | - # FIXME: grep log(other partition) for the crash to ensure it |
244 | - # really worked |
245 | - # FIXME2: reboot again as regression test for #1449904 |
246 | - |
247 | - # we booted, cleanup the panic for the next tests to work |
248 | - sudo mount -o remount,rw /writable/cache/system |
249 | - sudo rm /writable/cache/system/etc/rc.local |
250 | - return |
251 | - fi |
252 | - |
253 | - # fake new available version by doing a current-- |
254 | - switch_channel "s/build_number: $current/build_number: $((current-1))/" /etc/system-image/channel.ini |
255 | - |
256 | - # we should have something now :) |
257 | - version_info |
258 | - |
259 | - [ $avail -gt $current ] || fail "$avail is not newer than $current" |
260 | - |
261 | - # save version for post-upgrade test |
262 | - save_version_info $avail $current |
263 | - |
264 | - echo "upgrading..." |
265 | - sudo snappy update |
266 | - |
267 | - # break the upgrade by inserting a broken rc.local file onto the other |
268 | - # partition |
269 | - sudo mount -o remount,rw /writable/cache/system/ |
270 | - printf "#!/bin/sh\nprintf c > /proc/sysrq-trigger" | sudo tee /writable/cache/system/etc/rc.local |
271 | - |
272 | - reboot |
273 | -} |
274 | |
275 | === added file '_integration-tests/tests/common_test.go' |
276 | --- _integration-tests/tests/common_test.go 1970-01-01 00:00:00 +0000 |
277 | +++ _integration-tests/tests/common_test.go 2015-06-26 00:24:26 +0000 |
278 | @@ -0,0 +1,74 @@ |
279 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
280 | + |
281 | +/* |
282 | + * Copyright (C) 2015 Canonical Ltd |
283 | + * |
284 | + * This program is free software: you can redistribute it and/or modify |
285 | + * it under the terms of the GNU General Public License version 3 as |
286 | + * published by the Free Software Foundation. |
287 | + * |
288 | + * This program is distributed in the hope that it will be useful, |
289 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
290 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
291 | + * GNU General Public License for more details. |
292 | + * |
293 | + * You should have received a copy of the GNU General Public License |
294 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
295 | + * |
296 | + */ |
297 | + |
298 | +package tests |
299 | + |
300 | +import ( |
301 | + "fmt" |
302 | + "os" |
303 | + "os/exec" |
304 | + "testing" |
305 | + |
306 | + . "gopkg.in/check.v1" |
307 | +) |
308 | + |
309 | +type CommonSuite struct{} |
310 | + |
311 | +// Hook up gocheck into the "go test" runner |
312 | +func Test(t *testing.T) { TestingT(t) } |
313 | + |
314 | +func execCommand(c *C, cmds ...string) string { |
315 | + cmd := exec.Command(cmds[0], cmds[1:len(cmds)]...) |
316 | + output, err := cmd.CombinedOutput() |
317 | + stringOutput := string(output) |
318 | + c.Assert(err, IsNil, Commentf("Error: %v", stringOutput)) |
319 | + return stringOutput |
320 | +} |
321 | + |
322 | +func execCommandToFile(c *C, filename string, cmds ...string) { |
323 | + cmd := exec.Command(cmds[0], cmds[1:len(cmds)]...) |
324 | + outfile, err := os.Create(filename) |
325 | + c.Assert(err, IsNil, Commentf("Error creating output file %s", filename)) |
326 | + |
327 | + defer outfile.Close() |
328 | + cmd.Stdout = outfile |
329 | + |
330 | + err = cmd.Run() |
331 | + c.Assert(err, IsNil, Commentf("Error executing command '%v': %v", cmds, err)) |
332 | +} |
333 | + |
334 | +func (s *CommonSuite) SetUpSuite(c *C) { |
335 | + execCommand(c, "sudo", "systemctl", "stop", "snappy-autopilot.timer") |
336 | + execCommand(c, "sudo", "systemctl", "disable", "snappy-autopilot.timer") |
337 | +} |
338 | + |
339 | +func (s *CommonSuite) SetUpTest(c *C) { |
340 | + afterReboot := os.Getenv("ADT_REBOOT_MARK") |
341 | + |
342 | + if afterReboot == "" { |
343 | + c.Logf("****** Running %s", c.TestName()) |
344 | + } else { |
345 | + if afterReboot == c.TestName() { |
346 | + c.Logf("****** Resuming %s after reboot", c.TestName()) |
347 | + } else { |
348 | + c.Skip(fmt.Sprintf("****** Skipped %s after reboot caused by %s", |
349 | + c.TestName(), afterReboot)) |
350 | + } |
351 | + } |
352 | +} |
353 | |
354 | === added file '_integration-tests/tests/failover_rclocal_crash_test.go' |
355 | --- _integration-tests/tests/failover_rclocal_crash_test.go 1970-01-01 00:00:00 +0000 |
356 | +++ _integration-tests/tests/failover_rclocal_crash_test.go 2015-06-26 00:24:26 +0000 |
357 | @@ -0,0 +1,47 @@ |
358 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
359 | + |
360 | +/* |
361 | + * Copyright (C) 2015 Canonical Ltd |
362 | + * |
363 | + * This program is free software: you can redistribute it and/or modify |
364 | + * it under the terms of the GNU General Public License version 3 as |
365 | + * published by the Free Software Foundation. |
366 | + * |
367 | + * This program is distributed in the hope that it will be useful, |
368 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
369 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
370 | + * GNU General Public License for more details. |
371 | + * |
372 | + * You should have received a copy of the GNU General Public License |
373 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
374 | + * |
375 | + */ |
376 | + |
377 | +package tests |
378 | + |
379 | +import ( |
380 | + "fmt" |
381 | + |
382 | + . "gopkg.in/check.v1" |
383 | +) |
384 | + |
385 | +type rcLocalCrash struct{} |
386 | + |
387 | +func (rcLocalCrash) set(c *C) { |
388 | + makeWritable(c, baseOtherPath) |
389 | + targetFile := fmt.Sprintf("%s/etc/rc.local", baseOtherPath) |
390 | + execCommand(c, "sudo", "chmod", "a+xw", targetFile) |
391 | + execCommandToFile(c, targetFile, |
392 | + "sudo", "echo", "#!bin/sh\nprintf c > /proc/sysrq-trigger") |
393 | + makeReadonly(c, baseOtherPath) |
394 | +} |
395 | + |
396 | +func (rcLocalCrash) unset(c *C) { |
397 | + makeWritable(c, baseOtherPath) |
398 | + execCommand(c, "sudo", "rm", fmt.Sprintf("%s/etc/rc.local", baseOtherPath)) |
399 | + makeReadonly(c, baseOtherPath) |
400 | +} |
401 | + |
402 | +func (s *FailoverSuite) TestRCLocalCrash(c *C) { |
403 | + commonFailoverTest(c, rcLocalCrash{}) |
404 | +} |
405 | |
406 | === added file '_integration-tests/tests/failover_systemd_loop_test.go' |
407 | --- _integration-tests/tests/failover_systemd_loop_test.go 1970-01-01 00:00:00 +0000 |
408 | +++ _integration-tests/tests/failover_systemd_loop_test.go 2015-06-26 00:24:26 +0000 |
409 | @@ -0,0 +1,114 @@ |
410 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
411 | + |
412 | +/* |
413 | + * Copyright (C) 2015 Canonical Ltd |
414 | + * |
415 | + * This program is free software: you can redistribute it and/or modify |
416 | + * it under the terms of the GNU General Public License version 3 as |
417 | + * published by the Free Software Foundation. |
418 | + * |
419 | + * This program is distributed in the hope that it will be useful, |
420 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
421 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
422 | + * GNU General Public License for more details. |
423 | + * |
424 | + * You should have received a copy of the GNU General Public License |
425 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
426 | + * |
427 | + */ |
428 | + |
429 | +package tests |
430 | + |
431 | +import ( |
432 | + "fmt" |
433 | + |
434 | + . "gopkg.in/check.v1" |
435 | +) |
436 | + |
437 | +const ( |
438 | + deadlockService = `[Unit] |
439 | +Before=sysinit.target |
440 | +DefaultDependencies=no |
441 | + |
442 | +[Service] |
443 | +Type=oneshot |
444 | +ExecStartPre=-/bin/sh -c "echo 'DEBUG: $(date): deadlocked system' >/dev/console" |
445 | +ExecStartPre=-/bin/sh -c "echo 'DEBUG: $(date): deadlocked system' >/dev/ttyS0" |
446 | +ExecStart=/bin/systemctl start deadlock.service |
447 | +RemainAfterExit=yes |
448 | + |
449 | +[Install] |
450 | +RequiredBy=sysinit.target |
451 | +` |
452 | + rebootService = `[Unit] |
453 | +DefaultDependencies=no |
454 | +Description=Hack to force reboot if booting did not finish after 20s |
455 | + |
456 | +[Service] |
457 | +Type=oneshot |
458 | +ExecStartPre=/bin/sleep 20 |
459 | +ExecStart=-/bin/sh -c 'if ! systemctl is-active default.target; then wall "EMERGENCY REBOOT"; reboot -f; fi' |
460 | + |
461 | +[Install] |
462 | +RequiredBy=sysinit.target |
463 | +` |
464 | + baseSystemdPath = "/lib/systemd/system" |
465 | + systemdTargetRequiresDir = "sysinit.target.requires" |
466 | +) |
467 | + |
468 | +type systemdDependencyLoop struct{} |
469 | + |
470 | +func (systemdDependencyLoop) set(c *C) { |
471 | + installService(c, "deadlock", deadlockService, baseOtherPath) |
472 | + installService(c, "emerg-reboot", rebootService, baseOtherPath) |
473 | +} |
474 | + |
475 | +func (systemdDependencyLoop) unset(c *C) { |
476 | + unInstallService(c, "deadlock", baseOtherPath) |
477 | + unInstallService(c, "emerg-reboot", baseOtherPath) |
478 | +} |
479 | + |
480 | +func installService(c *C, serviceName, serviceCfg, basePath string) { |
481 | + makeWritable(c, basePath) |
482 | + |
483 | + // Create service file |
484 | + serviceFile := fmt.Sprintf("%s%s/%s.service", basePath, baseSystemdPath, serviceName) |
485 | + execCommand(c, "sudo", "chmod", "a+w", fmt.Sprintf("%s%s", basePath, baseSystemdPath)) |
486 | + execCommandToFile(c, serviceFile, "sudo", "echo", serviceCfg) |
487 | + |
488 | + // Create requires directory |
489 | + requiresDirPart := fmt.Sprintf("%s/%s", baseSystemdPath, systemdTargetRequiresDir) |
490 | + requiresDir := fmt.Sprintf("%s%s", basePath, requiresDirPart) |
491 | + execCommand(c, "sudo", "mkdir", "-p", requiresDir) |
492 | + |
493 | + // Symlink from the requires dir to the service file (with chroot for being |
494 | + // usable in the other partition) |
495 | + execCommand(c, "sudo", "chroot", basePath, "ln", "-s", |
496 | + fmt.Sprintf("%s/%s.service", baseSystemdPath, serviceName), |
497 | + fmt.Sprintf("%s/%s.service", requiresDirPart, serviceName), |
498 | + ) |
499 | + |
500 | + makeReadonly(c, basePath) |
501 | +} |
502 | + |
503 | +func unInstallService(c *C, serviceName, basePath string) { |
504 | + makeWritable(c, basePath) |
505 | + |
506 | + // Disable the service |
507 | + execCommand(c, "sudo", "chroot", basePath, |
508 | + "systemctl", "disable", fmt.Sprintf("%s.service", serviceName)) |
509 | + |
510 | + // Remove the service file |
511 | + execCommand(c, "sudo", "rm", |
512 | + fmt.Sprintf("%s%s/%s.service", basePath, baseSystemdPath, serviceName)) |
513 | + |
514 | + // Remove the requires symlink |
515 | + execCommand(c, "sudo", "rm", |
516 | + fmt.Sprintf("%s%s/%s/%s.service", basePath, baseSystemdPath, systemdTargetRequiresDir, serviceName)) |
517 | + |
518 | + makeReadonly(c, basePath) |
519 | +} |
520 | + |
521 | +func (s *FailoverSuite) TestSystemdDependencyLoop(c *C) { |
522 | + commonFailoverTest(c, systemdDependencyLoop{}) |
523 | +} |
524 | |
525 | === added file '_integration-tests/tests/failover_test.go' |
526 | --- _integration-tests/tests/failover_test.go 1970-01-01 00:00:00 +0000 |
527 | +++ _integration-tests/tests/failover_test.go 2015-06-26 00:24:26 +0000 |
528 | @@ -0,0 +1,151 @@ |
529 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
530 | + |
531 | +/* |
532 | + * Copyright (C) 2015 Canonical Ltd |
533 | + * |
534 | + * This program is free software: you can redistribute it and/or modify |
535 | + * it under the terms of the GNU General Public License version 3 as |
536 | + * published by the Free Software Foundation. |
537 | + * |
538 | + * This program is distributed in the hope that it will be useful, |
539 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
540 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
541 | + * GNU General Public License for more details. |
542 | + * |
543 | + * You should have received a copy of the GNU General Public License |
544 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
545 | + * |
546 | + */ |
547 | + |
548 | +package tests |
549 | + |
550 | +import ( |
551 | + "fmt" |
552 | + "io/ioutil" |
553 | + "os" |
554 | + "path/filepath" |
555 | + "regexp" |
556 | + "strconv" |
557 | + "strings" |
558 | + |
559 | + . "gopkg.in/check.v1" |
560 | +) |
561 | + |
562 | +type FailoverSuite struct { |
563 | + CommonSuite |
564 | +} |
565 | + |
566 | +var _ = Suite(&FailoverSuite{}) |
567 | + |
568 | +const ( |
569 | + baseOtherPath = "/writable/cache/system" |
570 | + channelCfgFile = "/etc/system-image/channel.ini" |
571 | +) |
572 | + |
573 | +// The types that implement this interface can be used in the test logic |
574 | +type failer interface { |
575 | + // Sets the failure conditions |
576 | + set(c *C) |
577 | + // Unsets the failure conditions |
578 | + unset(c *C) |
579 | +} |
580 | + |
581 | +// This is the logic common to all the failover tests. Each of them has define a |
582 | +// type implementing the failer interface and call this function with an instance |
583 | +// of it |
584 | +func commonFailoverTest(c *C, f failer) { |
585 | + currentVersion := getCurrentVersion(c) |
586 | + |
587 | + if afterReboot(c) { |
588 | + removeRebootMark(c) |
589 | + f.unset(c) |
590 | + c.Assert(getSavedVersion(c), Equals, currentVersion) |
591 | + } else { |
592 | + switchChannelVersion(c, currentVersion, currentVersion-1) |
593 | + setSavedVersion(c, currentVersion-1) |
594 | + |
595 | + callUpdate(c) |
596 | + f.set(c) |
597 | + reboot(c) |
598 | + } |
599 | +} |
600 | + |
601 | +func reboot(c *C) { |
602 | + // This will write the name of the current test as a reboot mark |
603 | + execCommand(c, "sudo", "/tmp/autopkgtest-reboot", c.TestName()) |
604 | +} |
605 | + |
606 | +func removeRebootMark(c *C) { |
607 | + err := os.Setenv("ADT_REBOOT_MARK", "") |
608 | + c.Assert(err, IsNil, Commentf("Error unsetting ADT_REBOOT_MARK")) |
609 | +} |
610 | + |
611 | +func afterReboot(c *C) bool { |
612 | + // $ADT_REBOOT_MARK contains the reboot mark, if we have rebooted it'll be the test name |
613 | + return os.Getenv("ADT_REBOOT_MARK") == c.TestName() |
614 | +} |
615 | + |
616 | +func getCurrentVersion(c *C) int { |
617 | + output := execCommand(c, "snappy", "list") |
618 | + pattern := "(?mU)^ubuntu-core (.*)$" |
619 | + re := regexp.MustCompile(pattern) |
620 | + match := re.FindStringSubmatch(output) |
621 | + c.Assert(match, NotNil, Commentf("Version not found in %s", output)) |
622 | + |
623 | + // match is like "ubuntu-core 2015-06-18 93 ubuntu" |
624 | + items := strings.Fields(match[0]) |
625 | + version, err := strconv.Atoi(items[2]) |
626 | + c.Assert(err, IsNil, Commentf("Error converting version to int %v", version)) |
627 | + return version |
628 | +} |
629 | + |
630 | +func setSavedVersion(c *C, version int) { |
631 | + versionFile := getVersionFile() |
632 | + err := ioutil.WriteFile(versionFile, []byte(strconv.Itoa(version)), 0777) |
633 | + c.Assert(err, IsNil, Commentf("Error writing version file %s with %s", versionFile, version)) |
634 | +} |
635 | + |
636 | +func getSavedVersion(c *C) int { |
637 | + versionFile := getVersionFile() |
638 | + contents, err := ioutil.ReadFile(versionFile) |
639 | + c.Assert(err, IsNil, Commentf("Error reading version file %s", versionFile)) |
640 | + |
641 | + version, err := strconv.Atoi(string(contents)) |
642 | + c.Assert(err, IsNil, Commentf("Error converting version %v", contents)) |
643 | + |
644 | + return version |
645 | +} |
646 | + |
647 | +func getVersionFile() string { |
648 | + return filepath.Join(os.Getenv("ADT_ARTIFACTS"), "version") |
649 | +} |
650 | + |
651 | +func switchChannelVersion(c *C, oldVersion, newVersion int) { |
652 | + targets := []string{"/", baseOtherPath} |
653 | + for _, target := range targets { |
654 | + file := filepath.Join(target, channelCfgFile) |
655 | + if _, err := os.Stat(file); err == nil { |
656 | + makeWritable(c, target) |
657 | + execCommand(c, |
658 | + "sudo", "sed", "-i", |
659 | + fmt.Sprintf( |
660 | + "s/build_number: %d/build_number: %d/g", |
661 | + oldVersion, newVersion), |
662 | + file) |
663 | + makeReadonly(c, target) |
664 | + } |
665 | + } |
666 | +} |
667 | + |
668 | +func callUpdate(c *C) { |
669 | + c.Log("Calling snappy update...") |
670 | + execCommand(c, "sudo", "snappy", "update") |
671 | +} |
672 | + |
673 | +func makeWritable(c *C, path string) { |
674 | + execCommand(c, "sudo", "mount", "-o", "remount,rw", path) |
675 | +} |
676 | + |
677 | +func makeReadonly(c *C, path string) { |
678 | + execCommand(c, "sudo", "mount", "-o", "remount,ro", path) |
679 | +} |
680 | |
681 | === added file '_integration-tests/tests/failover_zero_size_file_test.go' |
682 | --- _integration-tests/tests/failover_zero_size_file_test.go 1970-01-01 00:00:00 +0000 |
683 | +++ _integration-tests/tests/failover_zero_size_file_test.go 2015-06-26 00:24:26 +0000 |
684 | @@ -0,0 +1,120 @@ |
685 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
686 | + |
687 | +/* |
688 | + * Copyright (C) 2015 Canonical Ltd |
689 | + * |
690 | + * This program is free software: you can redistribute it and/or modify |
691 | + * it under the terms of the GNU General Public License version 3 as |
692 | + * published by the Free Software Foundation. |
693 | + * |
694 | + * This program is distributed in the hope that it will be useful, |
695 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
696 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
697 | + * GNU General Public License for more details. |
698 | + * |
699 | + * You should have received a copy of the GNU General Public License |
700 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
701 | + * |
702 | + */ |
703 | + |
704 | +package tests |
705 | + |
706 | +import ( |
707 | + "fmt" |
708 | + "path/filepath" |
709 | + "strings" |
710 | + |
711 | + . "gopkg.in/check.v1" |
712 | +) |
713 | + |
714 | +const ( |
715 | + // TODO: take into account arch for the boot path pattern |
716 | + origBootFilenamePattern = "boot/%s%s*" |
717 | + origSystemdFilenamePattern = "lib/systemd/%s%s" |
718 | + kernelFilename = "vmlinuz" |
719 | + initrdFilename = "initrd" |
720 | + systemdFilename = "systemd" |
721 | + destFilenamePrefix = "snappy-selftest-" |
722 | +) |
723 | + |
724 | +type zeroSizeKernel struct{} |
725 | +type zeroSizeInitrd struct{} |
726 | +type zeroSizeSystemd struct{} |
727 | + |
728 | +func (zeroSizeKernel) set(c *C) { |
729 | + commonSet(c, origBootFilenamePattern, kernelFilename) |
730 | +} |
731 | + |
732 | +func (zeroSizeKernel) unset(c *C) { |
733 | + commonUnset(c, origBootFilenamePattern, kernelFilename) |
734 | +} |
735 | + |
736 | +func (zeroSizeInitrd) set(c *C) { |
737 | + commonSet(c, origBootFilenamePattern, initrdFilename) |
738 | +} |
739 | + |
740 | +func (zeroSizeInitrd) unset(c *C) { |
741 | + commonUnset(c, origBootFilenamePattern, initrdFilename) |
742 | +} |
743 | + |
744 | +func (zeroSizeSystemd) set(c *C) { |
745 | + commonSet(c, origSystemdFilenamePattern, systemdFilename) |
746 | +} |
747 | + |
748 | +func (zeroSizeSystemd) unset(c *C) { |
749 | + commonUnset(c, origSystemdFilenamePattern, systemdFilename) |
750 | +} |
751 | + |
752 | +func commonSet(c *C, origPattern, filename string) { |
753 | + filenamePattern := fmt.Sprintf(origPattern, "", filename) |
754 | + completePattern := filepath.Join( |
755 | + baseOtherPath, |
756 | + filenamePattern) |
757 | + oldFilename := getSingleFilename(c, completePattern) |
758 | + filenameSuffix := fmt.Sprintf( |
759 | + strings.Replace(origPattern, "*", "", 1), destFilenamePrefix, filepath.Base(oldFilename)) |
760 | + newFilename := fmt.Sprintf( |
761 | + "%s/%s", baseOtherPath, filenameSuffix) |
762 | + |
763 | + renameFile(c, baseOtherPath, oldFilename, newFilename) |
764 | +} |
765 | + |
766 | +func commonUnset(c *C, origPattern, filename string) { |
767 | + completePattern := filepath.Join( |
768 | + baseOtherPath, |
769 | + fmt.Sprintf(origPattern, destFilenamePrefix, filename)) |
770 | + oldFilename := getSingleFilename(c, completePattern) |
771 | + newFilename := strings.Replace(oldFilename, destFilenamePrefix, "", 1) |
772 | + |
773 | + renameFile(c, baseOtherPath, oldFilename, newFilename) |
774 | +} |
775 | + |
776 | +func renameFile(c *C, basePath, oldFilename, newFilename string) { |
777 | + makeWritable(c, basePath) |
778 | + execCommand(c, "sudo", "mv", oldFilename, newFilename) |
779 | + execCommand(c, "sudo", "touch", oldFilename) |
780 | + makeReadonly(c, basePath) |
781 | +} |
782 | + |
783 | +func getSingleFilename(c *C, pattern string) string { |
784 | + matches, err := filepath.Glob(pattern) |
785 | + |
786 | + c.Assert(err, IsNil, Commentf("Error: %v", err)) |
787 | + c.Assert(len(matches), Equals, 1) |
788 | + |
789 | + return matches[0] |
790 | +} |
791 | + |
792 | +/* |
793 | +func (s *FailoverSuite) TestZeroSizeKernel(c *C) { |
794 | + commonFailoverTest(c, zeroSizeKernel{}) |
795 | +} |
796 | +*/ |
797 | + |
798 | +func (s *FailoverSuite) TestZeroSizeInitrd(c *C) { |
799 | + commonFailoverTest(c, zeroSizeInitrd{}) |
800 | +} |
801 | + |
802 | +func (s *FailoverSuite) TestZeroSizeSystemd(c *C) { |
803 | + commonFailoverTest(c, zeroSizeSystemd{}) |
804 | +} |
805 | |
806 | === renamed file '_integration-tests/tests/snappy_test.go' => '_integration-tests/tests/install_test.go' |
807 | --- _integration-tests/tests/snappy_test.go 2015-06-22 23:52:54 +0000 |
808 | +++ _integration-tests/tests/install_test.go 2015-06-26 00:24:26 +0000 |
809 | @@ -19,42 +19,24 @@ |
810 | |
811 | package tests |
812 | |
813 | -import ( |
814 | - "os/exec" |
815 | - "testing" |
816 | - |
817 | - . "gopkg.in/check.v1" |
818 | -) |
819 | - |
820 | -// Hook up gocheck into the "go test" runner |
821 | -func Test(t *testing.T) { TestingT(t) } |
822 | +import . "gopkg.in/check.v1" |
823 | |
824 | var _ = Suite(&InstallSuite{}) |
825 | |
826 | -type InstallSuite struct{} |
827 | - |
828 | -func (s *InstallSuite) installSnap(c *C, packageName string) string { |
829 | - return s.execCommand(c, "sudo", "snappy", "install", packageName) |
830 | -} |
831 | - |
832 | -func (s *InstallSuite) execCommand(c *C, cmds ...string) string { |
833 | - cmd := exec.Command(cmds[0], cmds[1:len(cmds)]...) |
834 | - output, err := cmd.CombinedOutput() |
835 | - stringOutput := string(output) |
836 | - c.Assert(err, IsNil, Commentf("Error: %v", stringOutput)) |
837 | - return stringOutput |
838 | -} |
839 | - |
840 | -func (s *InstallSuite) SetUpSuite(c *C) { |
841 | - s.execCommand(c, "sudo", "systemctl", "stop", "snappy-autopilot.timer") |
842 | +type InstallSuite struct { |
843 | + CommonSuite |
844 | +} |
845 | + |
846 | +func installSnap(c *C, packageName string) string { |
847 | + return execCommand(c, "sudo", "snappy", "install", packageName) |
848 | } |
849 | |
850 | func (s *InstallSuite) TearDownTest(c *C) { |
851 | - s.execCommand(c, "sudo", "snappy", "remove", "hello-world") |
852 | + execCommand(c, "sudo", "snappy", "remove", "hello-world") |
853 | } |
854 | |
855 | func (s *InstallSuite) TestInstallSnapMustPrintPackageInformation(c *C) { |
856 | - installOutput := s.installSnap(c, "hello-world") |
857 | + installOutput := installSnap(c, "hello-world") |
858 | |
859 | expected := "" + |
860 | "Installing hello-world\n" + |
861 | @@ -66,18 +48,19 @@ |
862 | } |
863 | |
864 | func (s *InstallSuite) TestCallBinaryFromInstalledSnap(c *C) { |
865 | - s.installSnap(c, "hello-world") |
866 | + installSnap(c, "hello-world") |
867 | |
868 | - echoOutput := s.execCommand(c, "hello-world.echo") |
869 | + echoOutput := execCommand(c, "hello-world.echo") |
870 | |
871 | c.Assert(echoOutput, Equals, "Hello World!\n") |
872 | } |
873 | |
874 | func (s *InstallSuite) TestInfoMustPrintInstalledPackageInformation(c *C) { |
875 | - s.installSnap(c, "hello-world") |
876 | + installSnap(c, "hello-world") |
877 | |
878 | - infoOutput := s.execCommand(c, "sudo", "snappy", "info") |
879 | + infoOutput := execCommand(c, "snappy", "info") |
880 | |
881 | expected := "(?ms).*^apps: hello-world\n" |
882 | + |
883 | c.Assert(infoOutput, Matches, expected) |
884 | } |
885 | |
886 | === modified file 'cmd/snappy/cmd_internal_unpack.go' |
887 | --- cmd/snappy/cmd_internal_unpack.go 2015-06-08 13:03:35 +0000 |
888 | +++ cmd/snappy/cmd_internal_unpack.go 2015-06-26 00:24:26 +0000 |
889 | @@ -70,7 +70,7 @@ |
890 | return filepath.Join("/etc/", file) |
891 | } |
892 | |
893 | -func readUid(user, passwdFile string) (uid int, err error) { |
894 | +func readUID(user, passwdFile string) (uid int, err error) { |
895 | f, err := os.Open(passwdFile) |
896 | if err != nil { |
897 | return -1, err |
898 | @@ -125,13 +125,13 @@ |
899 | if helpers.ShouldDropPrivs() { |
900 | |
901 | passFile := passwdFile(rootDir, "passwd") |
902 | - uid, err := readUid(dropPrivsUser, passFile) |
903 | + uid, err := readUID(dropPrivsUser, passFile) |
904 | if err != nil { |
905 | return err |
906 | } |
907 | |
908 | groupFile := passwdFile(rootDir, "group") |
909 | - gid, err := readUid(dropPrivsUser, groupFile) |
910 | + gid, err := readUID(dropPrivsUser, groupFile) |
911 | if err != nil { |
912 | return err |
913 | } |
914 | |
915 | === modified file 'cmd/snappy/cmd_low_level_unpack_test.go' |
916 | --- cmd/snappy/cmd_low_level_unpack_test.go 2015-06-02 20:46:07 +0000 |
917 | +++ cmd/snappy/cmd_low_level_unpack_test.go 2015-06-26 00:24:26 +0000 |
918 | @@ -42,7 +42,7 @@ |
919 | clickpkg:x:101:104::/nonexistent:/bin/false |
920 | `) |
921 | |
922 | - uid, err := readUid("clickpkg", f.Name()) |
923 | + uid, err := readUID("clickpkg", f.Name()) |
924 | c.Assert(err, IsNil) |
925 | c.Assert(uid, Equals, 101) |
926 | } |
927 | @@ -53,7 +53,7 @@ |
928 | clickpkg:x:104: |
929 | `) |
930 | |
931 | - gid, err := readUid("clickpkg", f.Name()) |
932 | + gid, err := readUID("clickpkg", f.Name()) |
933 | c.Assert(err, IsNil) |
934 | c.Assert(gid, Equals, 104) |
935 | } |
936 | @@ -66,7 +66,7 @@ |
937 | `) |
938 | defer os.Remove(f.Name()) |
939 | |
940 | - uid, err := readUid("clickpkg", f.Name()) |
941 | + uid, err := readUID("clickpkg", f.Name()) |
942 | c.Assert(err, IsNil) |
943 | c.Assert(uid, Equals, 102) |
944 | } |
945 | @@ -77,7 +77,7 @@ |
946 | clickpkg:x: |
947 | `) |
948 | |
949 | - _, err := readUid("clickpkg", f.Name()) |
950 | + _, err := readUID("clickpkg", f.Name()) |
951 | c.Assert(err, NotNil) |
952 | } |
953 | |
954 | @@ -86,6 +86,6 @@ |
955 | daemon: |
956 | `) |
957 | |
958 | - _, err := readUid("clickpkg", f.Name()) |
959 | + _, err := readUID("clickpkg", f.Name()) |
960 | c.Assert(err, NotNil) |
961 | } |
962 | |
963 | === modified file 'debian/control' |
964 | --- debian/control 2015-06-16 15:06:45 +0000 |
965 | +++ debian/control 2015-06-26 00:24:26 +0000 |
966 | @@ -51,10 +51,3 @@ |
967 | Built-Using: ${misc:Built-Using} |
968 | Description: Tool to interact with Ubuntu Core Snappy. |
969 | Manage an Ubuntu system with snappy. |
970 | - |
971 | -Package: ubuntu-snappy-tests |
972 | -Architecture: any |
973 | -Depends: ubuntu-snappy-cli (= ${binary:Version}), |
974 | - ${misc:Depends} |
975 | -Description: snappy selftests |
976 | - Installs snappy selftests binary |
977 | |
978 | === modified file 'debian/integration-tests/control' |
979 | --- debian/integration-tests/control 2015-06-16 04:52:54 +0000 |
980 | +++ debian/integration-tests/control 2015-06-26 00:24:26 +0000 |
981 | @@ -1,4 +1,4 @@ |
982 | -Test-Command: snappy.test -gocheck.vv -test.outputdir=$ADT_ARTIFACTS |
983 | +Test-Command: ./snappy.tests -gocheck.vv -test.outputdir=$ADT_ARTIFACTS |
984 | Restrictions: allow-stderr |
985 | Depends: ubuntu-snappy-tests |
986 | |
987 | |
988 | === modified file 'debian/rules' |
989 | --- debian/rules 2015-06-17 12:02:30 +0000 |
990 | +++ debian/rules 2015-06-26 00:24:26 +0000 |
991 | @@ -4,7 +4,6 @@ |
992 | #export DH_VERBOSE=1 |
993 | export DH_OPTIONS |
994 | export DH_GOPKG := launchpad.net/snappy |
995 | -DH_BUILDDIR = obj-$(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) |
996 | |
997 | %: |
998 | dh $@ --buildsystem=golang --with=golang --fail-missing --with systemd |
999 | @@ -51,11 +50,6 @@ |
1000 | -pubuntu-snappy \ |
1001 | snappy-autopilot.service |
1002 | |
1003 | -override_dh_auto_build: |
1004 | - dh_auto_build |
1005 | - GOPATH=$$PWD/$(DH_BUILDDIR) go test -c ./_integration-tests/tests |
1006 | - mv tests.test $$PWD/$(DH_BUILDDIR)/bin/snappy.test |
1007 | - |
1008 | override_dh_auto_install: |
1009 | dh_auto_install -O--buildsystem=golang |
1010 | # Making the packages private |
1011 | |
1012 | === removed file 'debian/ubuntu-snappy-tests.install' |
1013 | --- debian/ubuntu-snappy-tests.install 2015-06-13 18:10:49 +0000 |
1014 | +++ debian/ubuntu-snappy-tests.install 1970-01-01 00:00:00 +0000 |
1015 | @@ -1,1 +0,0 @@ |
1016 | -/usr/bin/snappy.test |
1017 | |
1018 | === modified file 'helpers/touch.go' |
1019 | --- helpers/touch.go 2015-05-15 13:33:27 +0000 |
1020 | +++ helpers/touch.go 2015-06-26 00:24:26 +0000 |
1021 | @@ -38,6 +38,8 @@ |
1022 | "unsafe" |
1023 | ) |
1024 | |
1025 | +// ErrNotAbsPath is returned when an absolute path is needed but the received |
1026 | +// path is not. |
1027 | var ErrNotAbsPath = errors.New("not an absolute path") |
1028 | |
1029 | // UpdateTimestamp updates the timestamp of the file at pathname. It does not |
1030 | |
1031 | === added file 'partition/assets.go' |
1032 | --- partition/assets.go 1970-01-01 00:00:00 +0000 |
1033 | +++ partition/assets.go 2015-06-26 00:24:26 +0000 |
1034 | @@ -0,0 +1,87 @@ |
1035 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
1036 | + |
1037 | +/* |
1038 | + * Copyright (C) 2014-2015 Canonical Ltd |
1039 | + * |
1040 | + * This program is free software: you can redistribute it and/or modify |
1041 | + * it under the terms of the GNU General Public License version 3 as |
1042 | + * published by the Free Software Foundation. |
1043 | + * |
1044 | + * This program is distributed in the hope that it will be useful, |
1045 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1046 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1047 | + * GNU General Public License for more details. |
1048 | + * |
1049 | + * You should have received a copy of the GNU General Public License |
1050 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1051 | + * |
1052 | + */ |
1053 | + |
1054 | +package partition |
1055 | + |
1056 | +import ( |
1057 | + "errors" |
1058 | + "io/ioutil" |
1059 | + "os" |
1060 | + "path/filepath" |
1061 | + |
1062 | + "gopkg.in/yaml.v2" |
1063 | +) |
1064 | + |
1065 | +// Representation of the yaml in the hardwareSpecFile |
1066 | +type hardwareSpecType struct { |
1067 | + Kernel string `yaml:"kernel"` |
1068 | + Initrd string `yaml:"initrd"` |
1069 | + DtbDir string `yaml:"dtbs"` |
1070 | + PartitionLayout string `yaml:"partition-layout"` |
1071 | + Bootloader bootloaderName `yaml:"bootloader"` |
1072 | +} |
1073 | + |
1074 | +var ( |
1075 | + // ErrNoHardwareYaml is returned when no hardware yaml is found in |
1076 | + // the update, this means that there is nothing to process with regards |
1077 | + // to device parts. |
1078 | + ErrNoHardwareYaml = errors.New("no hardware.yaml") |
1079 | + |
1080 | + // Declarative specification of the type of system which specifies such |
1081 | + // details as: |
1082 | + // |
1083 | + // - the location of initrd+kernel within the system-image archive. |
1084 | + // - the location of hardware-specific .dtb files within the |
1085 | + // system-image archive. |
1086 | + // - the type of bootloader that should be used for this system. |
1087 | + // - expected system partition layout (single or dual rootfs's). |
1088 | + hardwareSpecFileReal = filepath.Join(cacheDir, "hardware.yaml") |
1089 | + |
1090 | + // useful to override in the tests |
1091 | + hardwareSpecFile = hardwareSpecFileReal |
1092 | + |
1093 | + // Directory that _may_ get automatically created on unpack that |
1094 | + // contains updated hardware-specific boot assets (such as initrd, |
1095 | + // kernel) |
1096 | + assetsDir = filepath.Join(cacheDir, "assets") |
1097 | + |
1098 | + // Directory that _may_ get automatically created on unpack that |
1099 | + // contains updated hardware-specific assets that require flashing |
1100 | + // to the disk (such as uBoot, MLO) |
1101 | + flashAssetsDir = filepath.Join(cacheDir, "flashtool-assets") |
1102 | +) |
1103 | + |
1104 | +func readHardwareSpec() (*hardwareSpecType, error) { |
1105 | + var h hardwareSpecType |
1106 | + |
1107 | + data, err := ioutil.ReadFile(hardwareSpecFile) |
1108 | + // if hardware.yaml does not exist it just means that there was no |
1109 | + // device part in the update. |
1110 | + if os.IsNotExist(err) { |
1111 | + return nil, ErrNoHardwareYaml |
1112 | + } else if err != nil { |
1113 | + return nil, err |
1114 | + } |
1115 | + |
1116 | + if err := yaml.Unmarshal([]byte(data), &h); err != nil { |
1117 | + return nil, err |
1118 | + } |
1119 | + |
1120 | + return &h, nil |
1121 | +} |
1122 | |
1123 | === added file 'partition/assets_test.go' |
1124 | --- partition/assets_test.go 1970-01-01 00:00:00 +0000 |
1125 | +++ partition/assets_test.go 2015-06-26 00:24:26 +0000 |
1126 | @@ -0,0 +1,36 @@ |
1127 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
1128 | + |
1129 | +/* |
1130 | + * Copyright (C) 2014-2015 Canonical Ltd |
1131 | + * |
1132 | + * This program is free software: you can redistribute it and/or modify |
1133 | + * it under the terms of the GNU General Public License version 3 as |
1134 | + * published by the Free Software Foundation. |
1135 | + * |
1136 | + * This program is distributed in the hope that it will be useful, |
1137 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1138 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1139 | + * GNU General Public License for more details. |
1140 | + * |
1141 | + * You should have received a copy of the GNU General Public License |
1142 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1143 | + * |
1144 | + */ |
1145 | + |
1146 | +package partition |
1147 | + |
1148 | +import ( |
1149 | + . "gopkg.in/check.v1" |
1150 | +) |
1151 | + |
1152 | +func (s *PartitionTestSuite) TestHardwareSpec(c *C) { |
1153 | + |
1154 | + hardwareSpecFile = makeHardwareYaml(c, "") |
1155 | + hw, err := readHardwareSpec() |
1156 | + c.Assert(err, IsNil) |
1157 | + c.Assert(hw.Kernel, Equals, "assets/vmlinuz") |
1158 | + c.Assert(hw.Initrd, Equals, "assets/initrd.img") |
1159 | + c.Assert(hw.DtbDir, Equals, "assets/dtbs") |
1160 | + c.Assert(hw.PartitionLayout, Equals, bootloaderSystemAB) |
1161 | + c.Assert(hw.Bootloader, Equals, bootloaderNameUboot) |
1162 | +} |
1163 | |
1164 | === modified file 'partition/bootloader.go' |
1165 | --- partition/bootloader.go 2015-06-09 17:43:20 +0000 |
1166 | +++ partition/bootloader.go 2015-06-26 00:24:26 +0000 |
1167 | @@ -45,7 +45,7 @@ |
1168 | |
1169 | // Switch bootloader configuration so that the "other" root |
1170 | // filesystem partition will be used on next boot. |
1171 | - ToggleRootFS() error |
1172 | + ToggleRootFS(otherRootfs string) error |
1173 | |
1174 | // Hook function called before system-image starts downloading |
1175 | // and applying archives that allows files to be copied between |
1176 | @@ -60,14 +60,6 @@ |
1177 | GetBootVar(name string) (string, error) |
1178 | |
1179 | // Return the 1-character name corresponding to the |
1180 | - // rootfs currently being used. |
1181 | - GetRootFSName() string |
1182 | - |
1183 | - // Return the 1-character name corresponding to the |
1184 | - // other rootfs. |
1185 | - GetOtherRootFSName() string |
1186 | - |
1187 | - // Return the 1-character name corresponding to the |
1188 | // rootfs that will be used on _next_ boot. |
1189 | // |
1190 | // XXX: Note the distinction between this method and |
1191 | @@ -78,7 +70,7 @@ |
1192 | |
1193 | // Update the bootloader configuration to mark the |
1194 | // currently-booted rootfs as having booted successfully. |
1195 | - MarkCurrentBootSuccessful() error |
1196 | + MarkCurrentBootSuccessful(currentRootfs string) error |
1197 | |
1198 | // Return the additional required chroot bind mounts for this bootloader |
1199 | AdditionalBindMounts() []string |
1200 | @@ -88,15 +80,6 @@ |
1201 | BootDir() string |
1202 | } |
1203 | |
1204 | -type bootloaderType struct { |
1205 | - partition *Partition |
1206 | - |
1207 | - // each rootfs partition has a corresponding u-boot directory named |
1208 | - // from the last character of the partition name ('a' or 'b'). |
1209 | - currentRootfs string |
1210 | - otherRootfs string |
1211 | -} |
1212 | - |
1213 | // Factory method that returns a new bootloader for the given partition |
1214 | var bootloader = bootloaderImpl |
1215 | |
1216 | @@ -115,46 +98,13 @@ |
1217 | return nil, ErrBootloader |
1218 | } |
1219 | |
1220 | -func newBootLoader(partition *Partition) *bootloaderType { |
1221 | - b := new(bootloaderType) |
1222 | - |
1223 | - b.partition = partition |
1224 | - |
1225 | - currentLabel := partition.rootPartition().name |
1226 | - |
1227 | - // FIXME: is this the right thing to do? i.e. what should we do |
1228 | - // on a single partition system? |
1229 | - if partition.otherRootPartition() == nil { |
1230 | - return nil |
1231 | - } |
1232 | - otherLabel := partition.otherRootPartition().name |
1233 | - |
1234 | - b.currentRootfs = string(currentLabel[len(currentLabel)-1]) |
1235 | - b.otherRootfs = string(otherLabel[len(otherLabel)-1]) |
1236 | - |
1237 | - return b |
1238 | -} |
1239 | - |
1240 | -// Return true if the next boot will use the other rootfs |
1241 | -// partition. |
1242 | -func isNextBootOther(bootloader bootLoader) bool { |
1243 | - value, err := bootloader.GetBootVar(bootloaderBootmodeVar) |
1244 | - if err != nil { |
1245 | - return false |
1246 | - } |
1247 | - |
1248 | - if value != bootloaderBootmodeTry { |
1249 | - return false |
1250 | - } |
1251 | - |
1252 | - fsname, err := bootloader.GetNextBootRootFSName() |
1253 | - if err != nil { |
1254 | - return false |
1255 | - } |
1256 | - |
1257 | - if fsname == bootloader.GetOtherRootFSName() { |
1258 | - return true |
1259 | - } |
1260 | - |
1261 | - return false |
1262 | +// BootloaderDir returns the full path to the (mounted and writable) |
1263 | +// bootloader-specific boot directory. |
1264 | +func BootloaderDir() string { |
1265 | + b, err := bootloader(nil) |
1266 | + if err != nil { |
1267 | + return "" |
1268 | + } |
1269 | + |
1270 | + return b.BootDir() |
1271 | } |
1272 | |
1273 | === modified file 'partition/bootloader_grub.go' |
1274 | --- partition/bootloader_grub.go 2015-06-09 17:22:59 +0000 |
1275 | +++ partition/bootloader_grub.go 2015-06-26 00:24:26 +0000 |
1276 | @@ -49,7 +49,6 @@ |
1277 | ) |
1278 | |
1279 | type grub struct { |
1280 | - *bootloaderType |
1281 | } |
1282 | |
1283 | const bootloaderNameGrub bootloaderName = "grub" |
1284 | @@ -59,13 +58,8 @@ |
1285 | if !helpers.FileExists(bootloaderGrubConfigFile) || !helpers.FileExists(bootloaderGrubUpdateCmd) { |
1286 | return nil |
1287 | } |
1288 | - b := newBootLoader(partition) |
1289 | - if b == nil { |
1290 | - return nil |
1291 | - } |
1292 | - g := &grub{bootloaderType: b} |
1293 | |
1294 | - return g |
1295 | + return &grub{} |
1296 | } |
1297 | |
1298 | func (g *grub) Name() bootloaderName { |
1299 | @@ -77,10 +71,10 @@ |
1300 | // Approach: |
1301 | // |
1302 | // Update the grub configuration. |
1303 | -func (g *grub) ToggleRootFS() (err error) { |
1304 | +func (g *grub) ToggleRootFS(otherRootfs string) (err error) { |
1305 | |
1306 | // create the grub config |
1307 | - if err := runInChroot(g.partition.MountTarget(), bootloaderGrubUpdateCmd); err != nil { |
1308 | + if err := runInChroot(mountTarget, bootloaderGrubUpdateCmd); err != nil { |
1309 | return err |
1310 | } |
1311 | |
1312 | @@ -91,7 +85,7 @@ |
1313 | // Record the partition that will be used for next boot. This |
1314 | // isn't necessary for correct operation under grub, but allows |
1315 | // us to query the next boot device easily. |
1316 | - return g.setBootVar(bootloaderRootfsVar, g.otherRootfs) |
1317 | + return g.setBootVar(bootloaderRootfsVar, otherRootfs) |
1318 | } |
1319 | |
1320 | func (g *grub) GetBootVar(name string) (value string, err error) { |
1321 | @@ -127,22 +121,14 @@ |
1322 | return g.GetBootVar(bootloaderRootfsVar) |
1323 | } |
1324 | |
1325 | -func (g *grub) GetRootFSName() string { |
1326 | - return g.currentRootfs |
1327 | -} |
1328 | - |
1329 | -func (g *grub) GetOtherRootFSName() string { |
1330 | - return g.otherRootfs |
1331 | -} |
1332 | - |
1333 | -func (g *grub) MarkCurrentBootSuccessful() (err error) { |
1334 | +func (g *grub) MarkCurrentBootSuccessful(currentRootfs string) (err error) { |
1335 | // Clear the variable set by grub on boot to denote a good |
1336 | // boot. |
1337 | if err := g.unsetBootVar(bootloaderGrubTrialBootVar); err != nil { |
1338 | return err |
1339 | } |
1340 | |
1341 | - if err := g.setBootVar(bootloaderRootfsVar, g.currentRootfs); err != nil { |
1342 | + if err := g.setBootVar(bootloaderRootfsVar, currentRootfs); err != nil { |
1343 | return err |
1344 | } |
1345 | |
1346 | |
1347 | === modified file 'partition/bootloader_grub_test.go' |
1348 | --- partition/bootloader_grub_test.go 2015-06-09 17:43:20 +0000 |
1349 | +++ partition/bootloader_grub_test.go 2015-06-26 00:24:26 +0000 |
1350 | @@ -63,15 +63,6 @@ |
1351 | c.Assert(g.Name(), Equals, bootloaderNameGrub) |
1352 | } |
1353 | |
1354 | -func (s *PartitionTestSuite) TestNewGrubSinglePartition(c *C) { |
1355 | - runLsblk = mockRunLsblkSingleRootSnappy |
1356 | - s.makeFakeGrubEnv(c) |
1357 | - |
1358 | - partition := New() |
1359 | - g := newGrub(partition) |
1360 | - c.Assert(g, IsNil) |
1361 | -} |
1362 | - |
1363 | type singleCommand []string |
1364 | |
1365 | var allCommands = []singleCommand{} |
1366 | @@ -88,14 +79,14 @@ |
1367 | partition := New() |
1368 | g := newGrub(partition) |
1369 | c.Assert(g, NotNil) |
1370 | - err := g.ToggleRootFS() |
1371 | + err := g.ToggleRootFS("b") |
1372 | c.Assert(err, IsNil) |
1373 | |
1374 | // this is always called |
1375 | - mp := singleCommand{"/bin/mountpoint", "/writable/cache/system"} |
1376 | + mp := singleCommand{"/bin/mountpoint", mountTarget} |
1377 | c.Assert(allCommands[0], DeepEquals, mp) |
1378 | |
1379 | - expectedGrubUpdate := singleCommand{"/usr/sbin/chroot", "/writable/cache/system", bootloaderGrubUpdateCmd} |
1380 | + expectedGrubUpdate := singleCommand{"/usr/sbin/chroot", mountTarget, bootloaderGrubUpdateCmd} |
1381 | c.Assert(allCommands[1], DeepEquals, expectedGrubUpdate) |
1382 | |
1383 | expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_mode=try"} |
1384 | @@ -141,11 +132,11 @@ |
1385 | partition := New() |
1386 | g := newGrub(partition) |
1387 | c.Assert(g, NotNil) |
1388 | - err := g.MarkCurrentBootSuccessful() |
1389 | + err := g.MarkCurrentBootSuccessful("a") |
1390 | c.Assert(err, IsNil) |
1391 | |
1392 | // this is always called |
1393 | - mp := singleCommand{"/bin/mountpoint", "/writable/cache/system"} |
1394 | + mp := singleCommand{"/bin/mountpoint", mountTarget} |
1395 | c.Assert(allCommands[0], DeepEquals, mp) |
1396 | |
1397 | expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "unset", "snappy_trial_boot"} |
1398 | |
1399 | === modified file 'partition/bootloader_uboot.go' |
1400 | --- partition/bootloader_uboot.go 2015-06-09 17:22:59 +0000 |
1401 | +++ partition/bootloader_uboot.go 2015-06-26 00:24:26 +0000 |
1402 | @@ -58,8 +58,6 @@ |
1403 | const bootloaderNameUboot bootloaderName = "u-boot" |
1404 | |
1405 | type uboot struct { |
1406 | - *bootloaderType |
1407 | - |
1408 | // full path to rootfs-specific assets on boot partition |
1409 | currentBootPath string |
1410 | otherBootPath string |
1411 | @@ -78,13 +76,13 @@ |
1412 | return nil |
1413 | } |
1414 | |
1415 | - b := newBootLoader(partition) |
1416 | - if b == nil { |
1417 | - return nil |
1418 | - } |
1419 | - u := uboot{bootloaderType: b} |
1420 | - u.currentBootPath = filepath.Join(bootloaderUbootDir, u.currentRootfs) |
1421 | - u.otherBootPath = filepath.Join(bootloaderUbootDir, u.otherRootfs) |
1422 | + u := uboot{} |
1423 | + |
1424 | + currentRootfs := partition.rootPartition().shortName |
1425 | + u.currentBootPath = filepath.Join(bootloaderUbootDir, currentRootfs) |
1426 | + |
1427 | + otherRootfs := partition.otherRootPartition().shortName |
1428 | + u.otherBootPath = filepath.Join(bootloaderUbootDir, otherRootfs) |
1429 | |
1430 | return &u |
1431 | } |
1432 | @@ -103,7 +101,7 @@ |
1433 | // - Copy the "other" rootfs's kernel+initrd to the boot partition, |
1434 | // renaming them in the process to ensure the next boot uses the |
1435 | // correct versions. |
1436 | -func (u *uboot) ToggleRootFS() (err error) { |
1437 | +func (u *uboot) ToggleRootFS(otherRootfs string) (err error) { |
1438 | |
1439 | // If the file exists, update it. Otherwise create it. |
1440 | // |
1441 | @@ -112,7 +110,7 @@ |
1442 | // recreate to allow the system to boot! |
1443 | changes := []configFileChange{ |
1444 | configFileChange{Name: bootloaderRootfsVar, |
1445 | - Value: string(u.otherRootfs), |
1446 | + Value: string(otherRootfs), |
1447 | }, |
1448 | configFileChange{Name: bootloaderBootmodeVar, |
1449 | Value: bootloaderBootmodeTry, |
1450 | @@ -142,14 +140,6 @@ |
1451 | return value, nil |
1452 | } |
1453 | |
1454 | -func (u *uboot) GetRootFSName() string { |
1455 | - return u.currentRootfs |
1456 | -} |
1457 | - |
1458 | -func (u *uboot) GetOtherRootFSName() string { |
1459 | - return u.otherRootfs |
1460 | -} |
1461 | - |
1462 | // FIXME: put into utils package |
1463 | func readLines(path string) (lines []string, err error) { |
1464 | |
1465 | @@ -200,13 +190,13 @@ |
1466 | return file.Sync() |
1467 | } |
1468 | |
1469 | -func (u *uboot) MarkCurrentBootSuccessful() (err error) { |
1470 | +func (u *uboot) MarkCurrentBootSuccessful(currentRootfs string) (err error) { |
1471 | changes := []configFileChange{ |
1472 | configFileChange{Name: bootloaderBootmodeVar, |
1473 | Value: bootloaderBootmodeSuccess, |
1474 | }, |
1475 | configFileChange{Name: bootloaderRootfsVar, |
1476 | - Value: string(u.currentRootfs), |
1477 | + Value: string(currentRootfs), |
1478 | }, |
1479 | } |
1480 | |
1481 | @@ -227,14 +217,18 @@ |
1482 | func (u *uboot) HandleAssets() (err error) { |
1483 | // check if we have anything, if there is no hardware yaml, there is nothing |
1484 | // to process. |
1485 | - hardware, err := u.partition.hardwareSpec() |
1486 | + hardware, err := readHardwareSpec() |
1487 | if err == ErrNoHardwareYaml { |
1488 | return nil |
1489 | } else if err != nil { |
1490 | return err |
1491 | } |
1492 | - // ensure to remove the file once we are done |
1493 | - defer os.Remove(u.partition.hardwareSpecFile) |
1494 | + // ensure to remove the file once we are done (and all was good) |
1495 | + defer func() { |
1496 | + if err == nil { |
1497 | + os.Remove(hardwareSpecFile) |
1498 | + } |
1499 | + }() |
1500 | |
1501 | // validate bootloader |
1502 | if hardware.Bootloader != u.Name() { |
1503 | @@ -244,8 +238,9 @@ |
1504 | hardware.Bootloader) |
1505 | } |
1506 | |
1507 | - // validate partition layout |
1508 | - if u.partition.dualRootPartitions() && hardware.PartitionLayout != bootloaderSystemAB { |
1509 | + // validate partition layout, we ONLY support bootloaderSystemAB |
1510 | + // currently |
1511 | + if hardware.PartitionLayout != bootloaderSystemAB { |
1512 | return fmt.Errorf("hardware spec requires dual root partitions") |
1513 | } |
1514 | |
1515 | @@ -263,7 +258,7 @@ |
1516 | } |
1517 | |
1518 | // expand path |
1519 | - path := filepath.Join(u.partition.cacheDir(), file) |
1520 | + path := filepath.Join(cacheDir, file) |
1521 | |
1522 | if !helpers.FileExists(path) { |
1523 | return fmt.Errorf("can not find file %s", path) |
1524 | @@ -281,7 +276,7 @@ |
1525 | // fully speced |
1526 | |
1527 | // install .dtb files |
1528 | - dtbSrcDir := filepath.Join(u.partition.cacheDir(), hardware.DtbDir) |
1529 | + dtbSrcDir := filepath.Join(cacheDir, hardware.DtbDir) |
1530 | if helpers.FileExists(dtbSrcDir) { |
1531 | // ensure we cleanup the source dir |
1532 | defer os.RemoveAll(dtbSrcDir) |
1533 | @@ -303,8 +298,6 @@ |
1534 | } |
1535 | } |
1536 | |
1537 | - flashAssetsDir := u.partition.flashAssetsDir() |
1538 | - |
1539 | if helpers.FileExists(flashAssetsDir) { |
1540 | // FIXME: we don't currently do anything with the |
1541 | // MLO + uImage files since they are not specified in |
1542 | |
1543 | === modified file 'partition/bootloader_uboot_test.go' |
1544 | --- partition/bootloader_uboot_test.go 2015-06-12 05:23:40 +0000 |
1545 | +++ partition/bootloader_uboot_test.go 2015-06-26 00:24:26 +0000 |
1546 | @@ -89,15 +89,6 @@ |
1547 | c.Assert(u.Name(), Equals, bootloaderNameUboot) |
1548 | } |
1549 | |
1550 | -func (s *PartitionTestSuite) TestNewUbootSinglePartition(c *C) { |
1551 | - runLsblk = mockRunLsblkSingleRootSnappy |
1552 | - s.makeFakeUbootEnv(c) |
1553 | - |
1554 | - partition := New() |
1555 | - u := newUboot(partition) |
1556 | - c.Assert(u, IsNil) |
1557 | -} |
1558 | - |
1559 | func (s *PartitionTestSuite) TestUbootGetBootVar(c *C) { |
1560 | s.makeFakeUbootEnv(c) |
1561 | |
1562 | @@ -111,7 +102,7 @@ |
1563 | c.Assert(nextBoot, Equals, "a") |
1564 | |
1565 | // ensure that nextBootIsOther works too |
1566 | - c.Assert(isNextBootOther(u), Equals, false) |
1567 | + c.Assert(partition.IsNextBootOther(), Equals, false) |
1568 | } |
1569 | |
1570 | func (s *PartitionTestSuite) TestUbootToggleRootFS(c *C) { |
1571 | @@ -121,7 +112,7 @@ |
1572 | u := newUboot(partition) |
1573 | c.Assert(u, NotNil) |
1574 | |
1575 | - err := u.ToggleRootFS() |
1576 | + err := u.ToggleRootFS("b") |
1577 | c.Assert(err, IsNil) |
1578 | |
1579 | nextBoot, err := u.GetBootVar(bootloaderRootfsVar) |
1580 | @@ -129,7 +120,7 @@ |
1581 | c.Assert(nextBoot, Equals, "b") |
1582 | |
1583 | // ensure that nextBootIsOther works too |
1584 | - c.Assert(isNextBootOther(u), Equals, true) |
1585 | + c.Assert(partition.IsNextBootOther(), Equals, true) |
1586 | } |
1587 | |
1588 | func (s *PartitionTestSuite) TestUbootGetEnvVar(c *C) { |
1589 | @@ -158,7 +149,7 @@ |
1590 | |
1591 | func makeMockAssetsDir(c *C) { |
1592 | for _, f := range []string{"assets/vmlinuz", "assets/initrd.img", "assets/dtbs/foo.dtb", "assets/dtbs/bar.dtb"} { |
1593 | - p := filepath.Join(defaultCacheDir, f) |
1594 | + p := filepath.Join(cacheDir, f) |
1595 | os.MkdirAll(filepath.Dir(p), 0755) |
1596 | err := ioutil.WriteFile(p, []byte(f), 0644) |
1597 | c.Assert(err, IsNil) |
1598 | @@ -172,8 +163,8 @@ |
1599 | c.Assert(err, IsNil) |
1600 | |
1601 | // mock the hardwareYaml and the cacheDir |
1602 | - p.hardwareSpecFile = makeHardwareYaml(c, "") |
1603 | - defaultCacheDir = c.MkDir() |
1604 | + hardwareSpecFile = makeHardwareYaml(c, "") |
1605 | + cacheDir = c.MkDir() |
1606 | |
1607 | // create mock assets/ |
1608 | makeMockAssetsDir(c) |
1609 | @@ -192,8 +183,8 @@ |
1610 | } |
1611 | |
1612 | // ensure nothing left behind |
1613 | - c.Assert(helpers.FileExists(filepath.Join(defaultCacheDir, "assets")), Equals, false) |
1614 | - c.Assert(helpers.FileExists(p.hardwareSpecFile), Equals, false) |
1615 | + c.Assert(helpers.FileExists(filepath.Join(cacheDir, "assets")), Equals, false) |
1616 | + c.Assert(helpers.FileExists(hardwareSpecFile), Equals, false) |
1617 | } |
1618 | |
1619 | func (s *PartitionTestSuite) TestHandleAssetsVerifyBootloader(c *C) { |
1620 | @@ -203,7 +194,8 @@ |
1621 | c.Assert(err, IsNil) |
1622 | |
1623 | // mock the hardwareYaml and the cacheDir |
1624 | - p.hardwareSpecFile = makeHardwareYaml(c, "bootloader: grub") |
1625 | + hardwareSpecFile = makeHardwareYaml(c, "bootloader: grub") |
1626 | + cacheDir = c.MkDir() |
1627 | |
1628 | err = bootloader.HandleAssets() |
1629 | c.Assert(err, NotNil) |
1630 | @@ -216,18 +208,16 @@ |
1631 | c.Assert(err, IsNil) |
1632 | |
1633 | // mock the hardwareYaml and the cacheDir |
1634 | - p.hardwareSpecFile = makeHardwareYaml(c, ` |
1635 | + hardwareSpecFile = makeHardwareYaml(c, ` |
1636 | bootloader: u-boot |
1637 | partition-layout: inplace |
1638 | `) |
1639 | - |
1640 | err = bootloader.HandleAssets() |
1641 | c.Assert(err, NotNil) |
1642 | } |
1643 | |
1644 | func (s *PartitionTestSuite) TestHandleAssetsNoHardwareYaml(c *C) { |
1645 | s.makeFakeUbootEnv(c) |
1646 | - defaultCacheDir = c.MkDir() |
1647 | |
1648 | p := New() |
1649 | bootloader, err := bootloader(p) |
1650 | @@ -242,7 +232,7 @@ |
1651 | bootloader, err := bootloader(p) |
1652 | c.Assert(err, IsNil) |
1653 | |
1654 | - p.hardwareSpecFile = makeHardwareYaml(c, ` |
1655 | + hardwareSpecFile = makeHardwareYaml(c, ` |
1656 | bootloader u-boot |
1657 | `) |
1658 | |
1659 | @@ -268,7 +258,7 @@ |
1660 | // enter "try" mode so that we check to ensure that snappy |
1661 | // correctly modifies the snappy_mode variable from "try" to |
1662 | // "regular" to denote a good boot. |
1663 | - err = u.ToggleRootFS() |
1664 | + err = u.ToggleRootFS("b") |
1665 | c.Assert(err, IsNil) |
1666 | |
1667 | c.Assert(helpers.FileExists(bootloaderUbootEnvFile), Equals, true) |
1668 | @@ -278,7 +268,7 @@ |
1669 | c.Assert(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, false) |
1670 | c.Assert(strings.Contains(string(bytes), "snappy_ab=b"), Equals, true) |
1671 | |
1672 | - err = u.MarkCurrentBootSuccessful() |
1673 | + err = u.MarkCurrentBootSuccessful("b") |
1674 | c.Assert(err, IsNil) |
1675 | |
1676 | c.Assert(helpers.FileExists(bootloaderUbootStampFile), Equals, false) |
1677 | @@ -288,7 +278,7 @@ |
1678 | c.Assert(err, IsNil) |
1679 | c.Assert(strings.Contains(string(bytes), "snappy_mode=try"), Equals, false) |
1680 | c.Assert(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, true) |
1681 | - c.Assert(strings.Contains(string(bytes), "snappy_ab=a"), Equals, true) |
1682 | + c.Assert(strings.Contains(string(bytes), "snappy_ab=b"), Equals, true) |
1683 | } |
1684 | |
1685 | func (s *PartitionTestSuite) TestNoWriteNotNeeded(c *C) { |
1686 | @@ -301,7 +291,7 @@ |
1687 | u := newUboot(partition) |
1688 | c.Assert(u, NotNil) |
1689 | |
1690 | - c.Check(u.MarkCurrentBootSuccessful(), IsNil) |
1691 | + c.Check(u.MarkCurrentBootSuccessful("a"), IsNil) |
1692 | c.Assert(atomiCall, Equals, false) |
1693 | } |
1694 | |
1695 | @@ -318,7 +308,7 @@ |
1696 | u := newUboot(partition) |
1697 | c.Assert(u, NotNil) |
1698 | |
1699 | - c.Check(u.MarkCurrentBootSuccessful(), IsNil) |
1700 | + c.Check(u.MarkCurrentBootSuccessful("a"), IsNil) |
1701 | c.Assert(atomiCall, Equals, true) |
1702 | |
1703 | bytes, err := ioutil.ReadFile(bootloaderUbootEnvFile) |
1704 | |
1705 | === added file 'partition/dirs.go' |
1706 | --- partition/dirs.go 1970-01-01 00:00:00 +0000 |
1707 | +++ partition/dirs.go 2015-06-26 00:24:26 +0000 |
1708 | @@ -0,0 +1,41 @@ |
1709 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
1710 | + |
1711 | +/* |
1712 | + * Copyright (C) 2014-2015 Canonical Ltd |
1713 | + * |
1714 | + * This program is free software: you can redistribute it and/or modify |
1715 | + * it under the terms of the GNU General Public License version 3 as |
1716 | + * published by the Free Software Foundation. |
1717 | + * |
1718 | + * This program is distributed in the hope that it will be useful, |
1719 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1720 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1721 | + * GNU General Public License for more details. |
1722 | + * |
1723 | + * You should have received a copy of the GNU General Public License |
1724 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1725 | + * |
1726 | + */ |
1727 | + |
1728 | +package partition |
1729 | + |
1730 | +import ( |
1731 | + "path/filepath" |
1732 | +) |
1733 | + |
1734 | +// The full path to the cache directory, which is used as a |
1735 | +// scratch pad, for downloading new images to and bind mounting the |
1736 | +// rootfs. |
1737 | +const cacheDirReal = "/writable/cache" |
1738 | + |
1739 | +var ( |
1740 | + // useful for overwriting in the tests |
1741 | + cacheDir = cacheDirReal |
1742 | + |
1743 | + // Directory to mount writable root filesystem below the cache |
1744 | + // diretory. |
1745 | + mountTargetReal = filepath.Join(cacheDir, "system") |
1746 | + |
1747 | + // useful to override in tests |
1748 | + mountTarget = mountTargetReal |
1749 | +) |
1750 | |
1751 | === added file 'partition/mount.go' |
1752 | --- partition/mount.go 1970-01-01 00:00:00 +0000 |
1753 | +++ partition/mount.go 2015-06-26 00:24:26 +0000 |
1754 | @@ -0,0 +1,153 @@ |
1755 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
1756 | + |
1757 | +/* |
1758 | + * Copyright (C) 2014-2015 Canonical Ltd |
1759 | + * |
1760 | + * This program is free software: you can redistribute it and/or modify |
1761 | + * it under the terms of the GNU General Public License version 3 as |
1762 | + * published by the Free Software Foundation. |
1763 | + * |
1764 | + * This program is distributed in the hope that it will be useful, |
1765 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1766 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1767 | + * GNU General Public License for more details. |
1768 | + * |
1769 | + * You should have received a copy of the GNU General Public License |
1770 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1771 | + * |
1772 | + */ |
1773 | + |
1774 | +package partition |
1775 | + |
1776 | +import ( |
1777 | + "fmt" |
1778 | + "sort" |
1779 | +) |
1780 | + |
1781 | +// MountOption represents how the partition should be mounted, currently |
1782 | +// RO (read-only) and RW (read-write) are supported |
1783 | +type MountOption int |
1784 | + |
1785 | +const ( |
1786 | + // RO mounts the partition read-only |
1787 | + RO MountOption = iota |
1788 | + // RW mounts the partition read-only |
1789 | + RW |
1790 | +) |
1791 | + |
1792 | +// mountEntry represents a mount this package has created. |
1793 | +type mountEntry struct { |
1794 | + source string |
1795 | + target string |
1796 | + |
1797 | + options string |
1798 | + |
1799 | + // true if target refers to a bind mount. We could derive this |
1800 | + // from options, but this field saves the effort. |
1801 | + bindMount bool |
1802 | +} |
1803 | + |
1804 | +// mountEntryArray represents an array of mountEntry objects. |
1805 | +type mountEntryArray []mountEntry |
1806 | + |
1807 | +// current mounts that this package has created. |
1808 | +var mounts mountEntryArray |
1809 | + |
1810 | +// Len is part of the sort interface, required to allow sort to work |
1811 | +// with an array of Mount objects. |
1812 | +func (mounts mountEntryArray) Len() int { |
1813 | + return len(mounts) |
1814 | +} |
1815 | + |
1816 | +// Less is part of the sort interface, required to allow sort to work |
1817 | +// with an array of Mount objects. |
1818 | +func (mounts mountEntryArray) Less(i, j int) bool { |
1819 | + return mounts[i].target < mounts[j].target |
1820 | +} |
1821 | + |
1822 | +// Swap is part of the sort interface, required to allow sort to work |
1823 | +// with an array of Mount objects. |
1824 | +func (mounts mountEntryArray) Swap(i, j int) { |
1825 | + mounts[i], mounts[j] = mounts[j], mounts[i] |
1826 | +} |
1827 | + |
1828 | +// removeMountByTarget removes the Mount specified by the target from |
1829 | +// the global mounts array. |
1830 | +func removeMountByTarget(mnts mountEntryArray, target string) (results mountEntryArray) { |
1831 | + |
1832 | + for _, m := range mnts { |
1833 | + if m.target != target { |
1834 | + results = append(results, m) |
1835 | + } |
1836 | + } |
1837 | + |
1838 | + return results |
1839 | +} |
1840 | + |
1841 | +// undoMounts unmounts all mounts this package has mounted optionally |
1842 | +// only unmounting bind mounts and leaving all remaining mounts. |
1843 | +func undoMounts(bindMountsOnly bool) error { |
1844 | + |
1845 | + mountsCopy := make(mountEntryArray, len(mounts), cap(mounts)) |
1846 | + copy(mountsCopy, mounts) |
1847 | + |
1848 | + // reverse sort to ensure unmounts are handled in the correct |
1849 | + // order. |
1850 | + sort.Sort(sort.Reverse(mountsCopy)) |
1851 | + |
1852 | + // Iterate backwards since we want a reverse-sorted list of |
1853 | + // mounts to ensure we can unmount in order. |
1854 | + for _, mount := range mountsCopy { |
1855 | + if bindMountsOnly && !mount.bindMount { |
1856 | + continue |
1857 | + } |
1858 | + |
1859 | + if err := unmountAndRemoveFromGlobalMountList(mount.target); err != nil { |
1860 | + return err |
1861 | + } |
1862 | + } |
1863 | + |
1864 | + return nil |
1865 | +} |
1866 | + |
1867 | +// FIXME: use syscall.Mount() here |
1868 | +func mount(source, target, options string) (err error) { |
1869 | + var args []string |
1870 | + |
1871 | + args = append(args, "/bin/mount") |
1872 | + if options != "" { |
1873 | + args = append(args, fmt.Sprintf("-o%s", options)) |
1874 | + } |
1875 | + |
1876 | + args = append(args, source) |
1877 | + args = append(args, target) |
1878 | + |
1879 | + return runCommand(args...) |
1880 | +} |
1881 | + |
1882 | +// Mount the given directory and add it to the global mounts slice |
1883 | +func mountAndAddToGlobalMountList(m mountEntry) (err error) { |
1884 | + |
1885 | + err = mount(m.source, m.target, m.options) |
1886 | + if err == nil { |
1887 | + mounts = append(mounts, m) |
1888 | + } |
1889 | + |
1890 | + return err |
1891 | +} |
1892 | + |
1893 | +// Unmount the given directory and remove it from the global "mounts" slice |
1894 | +func unmountAndRemoveFromGlobalMountList(target string) (err error) { |
1895 | + err = runCommand("/bin/umount", target) |
1896 | + if err != nil { |
1897 | + return err |
1898 | + |
1899 | + } |
1900 | + |
1901 | + results := removeMountByTarget(mounts, target) |
1902 | + |
1903 | + // Update global |
1904 | + mounts = results |
1905 | + |
1906 | + return nil |
1907 | +} |
1908 | |
1909 | === added file 'partition/mount_test.go' |
1910 | --- partition/mount_test.go 1970-01-01 00:00:00 +0000 |
1911 | +++ partition/mount_test.go 2015-06-26 00:24:26 +0000 |
1912 | @@ -0,0 +1,64 @@ |
1913 | +// -*- Mode: Go; indent-tabs-mode: t -*- |
1914 | + |
1915 | +/* |
1916 | + * Copyright (C) 2014-2015 Canonical Ltd |
1917 | + * |
1918 | + * This program is free software: you can redistribute it and/or modify |
1919 | + * it under the terms of the GNU General Public License version 3 as |
1920 | + * published by the Free Software Foundation. |
1921 | + * |
1922 | + * This program is distributed in the hope that it will be useful, |
1923 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1924 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1925 | + * GNU General Public License for more details. |
1926 | + * |
1927 | + * You should have received a copy of the GNU General Public License |
1928 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1929 | + * |
1930 | + */ |
1931 | + |
1932 | +package partition |
1933 | + |
1934 | +import ( |
1935 | + . "gopkg.in/check.v1" |
1936 | +) |
1937 | + |
1938 | +func (s *PartitionTestSuite) TestMountEntryArray(c *C) { |
1939 | + mea := mountEntryArray{} |
1940 | + |
1941 | + c.Assert(mea.Len(), Equals, 0) |
1942 | + |
1943 | + me := mountEntry{source: "/dev", |
1944 | + target: "/dev", |
1945 | + options: "bind", |
1946 | + bindMount: true} |
1947 | + |
1948 | + mea = append(mea, me) |
1949 | + c.Assert(mea.Len(), Equals, 1) |
1950 | + |
1951 | + me = mountEntry{source: "/foo", |
1952 | + target: "/foo", |
1953 | + options: "", |
1954 | + bindMount: false} |
1955 | + |
1956 | + mea = append(mea, me) |
1957 | + c.Assert(mea.Len(), Equals, 2) |
1958 | + |
1959 | + c.Assert(mea.Less(0, 1), Equals, true) |
1960 | + c.Assert(mea.Less(1, 0), Equals, false) |
1961 | + |
1962 | + mea.Swap(0, 1) |
1963 | + c.Assert(mea.Less(0, 1), Equals, false) |
1964 | + c.Assert(mea.Less(1, 0), Equals, true) |
1965 | + |
1966 | + results := removeMountByTarget(mea, "invalid") |
1967 | + |
1968 | + // No change expected |
1969 | + c.Assert(results, DeepEquals, mea) |
1970 | + |
1971 | + results = removeMountByTarget(mea, "/dev") |
1972 | + |
1973 | + c.Assert(len(results), Equals, 1) |
1974 | + c.Assert(results[0], Equals, mountEntry{source: "/foo", |
1975 | + target: "/foo", options: "", bindMount: false}) |
1976 | +} |
1977 | |
1978 | === modified file 'partition/partition.go' |
1979 | --- partition/partition.go 2015-06-09 17:43:20 +0000 |
1980 | +++ partition/partition.go 2015-06-26 00:24:26 +0000 |
1981 | @@ -23,50 +23,37 @@ |
1982 | import ( |
1983 | "errors" |
1984 | "fmt" |
1985 | - "io/ioutil" |
1986 | "os" |
1987 | "os/signal" |
1988 | "path/filepath" |
1989 | "regexp" |
1990 | - "sort" |
1991 | "strings" |
1992 | + "sync" |
1993 | "syscall" |
1994 | |
1995 | - "gopkg.in/yaml.v2" |
1996 | - |
1997 | "launchpad.net/snappy/logger" |
1998 | ) |
1999 | |
2000 | -var signalHandlerRegistered = false |
2001 | - |
2002 | -// Name of writable user data partition label as created by |
2003 | -// ubuntu-device-flash(1). |
2004 | -const writablePartitionLabel = "writable" |
2005 | - |
2006 | -// Name of primary root filesystem partition label as created by |
2007 | -// ubuntu-device-flash(1). |
2008 | -const rootfsAlabel = "system-a" |
2009 | - |
2010 | -// Name of primary root filesystem partition label as created by |
2011 | -// ubuntu-device-flash(1). Note that this partition will |
2012 | -// only be present if this is an A/B upgrade system. |
2013 | -const rootfsBlabel = "system-b" |
2014 | - |
2015 | -// name of boot partition label as created by ubuntu-device-flash(1). |
2016 | -const bootPartitionLabel = "system-boot" |
2017 | - |
2018 | -// its useful to override this in tests |
2019 | -const realDefaultCacheDir = "/writable/cache" |
2020 | - |
2021 | -// FIXME: Should query system-image-cli (see bug LP:#1380574). |
2022 | -var defaultCacheDir = realDefaultCacheDir |
2023 | - |
2024 | -// Directory to mount writable root filesystem below the cache |
2025 | -// diretory. |
2026 | -const mountTarget = "system" |
2027 | - |
2028 | -// File creation mode used when any directories are created |
2029 | -const dirMode = 0750 |
2030 | +const ( |
2031 | + // Name of writable user data partition label as created by |
2032 | + // ubuntu-device-flash(1). |
2033 | + writablePartitionLabel = "writable" |
2034 | + |
2035 | + // Name of primary root filesystem partition label as created by |
2036 | + // ubuntu-device-flash(1). |
2037 | + rootfsAlabel = "system-a" |
2038 | + |
2039 | + // Name of primary root filesystem partition label as created by |
2040 | + // ubuntu-device-flash(1). Note that this partition will |
2041 | + // only be present if this is an A/B upgrade system. |
2042 | + rootfsBlabel = "system-b" |
2043 | + |
2044 | + // name of boot partition label as created by ubuntu-device-flash(1). |
2045 | + bootPartitionLabel = "system-boot" |
2046 | + |
2047 | + // File creation mode used when any directories are created |
2048 | + dirMode = 0750 |
2049 | +) |
2050 | |
2051 | var ( |
2052 | // ErrBootloader is returned if the bootloader can not be determined |
2053 | @@ -79,41 +66,6 @@ |
2054 | // ErrNoDualPartition is returned if you try to use a dual |
2055 | // partition feature on a single partition |
2056 | ErrNoDualPartition = errors.New("No dual partition") |
2057 | - |
2058 | - // ErrNoHardwareYaml is returned when no hardware yaml is found in |
2059 | - // the update, this means that there is nothing to process with regards |
2060 | - // to device parts. |
2061 | - ErrNoHardwareYaml = errors.New("no hardware.yaml") |
2062 | -) |
2063 | - |
2064 | -// Declarative specification of the type of system which specifies such |
2065 | -// details as: |
2066 | -// |
2067 | -// - the location of initrd+kernel within the system-image archive. |
2068 | -// - the location of hardware-specific .dtb files within the |
2069 | -// system-image archive. |
2070 | -// - the type of bootloader that should be used for this system. |
2071 | -// - expected system partition layout (single or dual rootfs's). |
2072 | -const hardwareSpecFile = "hardware.yaml" |
2073 | - |
2074 | -// Directory that _may_ get automatically created on unpack that |
2075 | -// contains updated hardware-specific boot assets (such as initrd, kernel) |
2076 | -const assetsDir = "assets" |
2077 | - |
2078 | -// Directory that _may_ get automatically created on unpack that |
2079 | -// contains updated hardware-specific assets that require flashing |
2080 | -// to the disk (such as uBoot, MLO) |
2081 | -const flashAssetsDir = "flashtool-assets" |
2082 | - |
2083 | -// MountOption represents how the partition should be mounted, currently |
2084 | -// RO (read-only) and RW (read-write) are supported |
2085 | -type MountOption int |
2086 | - |
2087 | -const ( |
2088 | - // RO mounts the partition read-only |
2089 | - RO MountOption = iota |
2090 | - // RW mounts the partition read-only |
2091 | - RW |
2092 | ) |
2093 | |
2094 | // Interface provides the interface to interact with a partition |
2095 | @@ -128,29 +80,7 @@ |
2096 | |
2097 | // run the function f with the otherRoot mounted |
2098 | RunWithOther(rw MountOption, f func(otherRoot string) (err error)) (err error) |
2099 | - |
2100 | - // Returns the full path to the (mounted and writable) |
2101 | - // bootloader-specific boot directory. |
2102 | - BootloaderDir() string |
2103 | -} |
2104 | - |
2105 | -// mountEntry represents a mount this package has created. |
2106 | -type mountEntry struct { |
2107 | - source string |
2108 | - target string |
2109 | - |
2110 | - options string |
2111 | - |
2112 | - // true if target refers to a bind mount. We could derive this |
2113 | - // from options, but this field saves the effort. |
2114 | - bindMount bool |
2115 | -} |
2116 | - |
2117 | -// mountEntryArray represents an array of mountEntry objects. |
2118 | -type mountEntryArray []mountEntry |
2119 | - |
2120 | -// current mounts that this package has created. |
2121 | -var mounts mountEntryArray |
2122 | +} |
2123 | |
2124 | // Partition is the type to interact with the partition |
2125 | type Partition struct { |
2126 | @@ -159,14 +89,15 @@ |
2127 | |
2128 | // just root partitions |
2129 | roots []string |
2130 | - |
2131 | - hardwareSpecFile string |
2132 | } |
2133 | |
2134 | type blockDevice struct { |
2135 | // label for partition |
2136 | name string |
2137 | |
2138 | + // the last char of the partition label |
2139 | + shortName string |
2140 | + |
2141 | // full path to device on which partition exists |
2142 | // (for example "/dev/sda3") |
2143 | device string |
2144 | @@ -178,77 +109,10 @@ |
2145 | mountpoint string |
2146 | } |
2147 | |
2148 | -// Representation of HARDWARE_SPEC_FILE |
2149 | -type hardwareSpecType struct { |
2150 | - Kernel string `yaml:"kernel"` |
2151 | - Initrd string `yaml:"initrd"` |
2152 | - DtbDir string `yaml:"dtbs"` |
2153 | - PartitionLayout string `yaml:"partition-layout"` |
2154 | - Bootloader bootloaderName `yaml:"bootloader"` |
2155 | -} |
2156 | - |
2157 | -// Len is part of the sort interface, required to allow sort to work |
2158 | -// with an array of Mount objects. |
2159 | -func (mounts mountEntryArray) Len() int { |
2160 | - return len(mounts) |
2161 | -} |
2162 | - |
2163 | -// Less is part of the sort interface, required to allow sort to work |
2164 | -// with an array of Mount objects. |
2165 | -func (mounts mountEntryArray) Less(i, j int) bool { |
2166 | - return mounts[i].target < mounts[j].target |
2167 | -} |
2168 | - |
2169 | -// Swap is part of the sort interface, required to allow sort to work |
2170 | -// with an array of Mount objects. |
2171 | -func (mounts mountEntryArray) Swap(i, j int) { |
2172 | - mounts[i], mounts[j] = mounts[j], mounts[i] |
2173 | -} |
2174 | - |
2175 | -// removeMountByTarget removes the Mount specified by the target from |
2176 | -// the global mounts array. |
2177 | -func removeMountByTarget(mnts mountEntryArray, target string) (results mountEntryArray) { |
2178 | - |
2179 | - for _, m := range mnts { |
2180 | - if m.target != target { |
2181 | - results = append(results, m) |
2182 | - } |
2183 | - } |
2184 | - |
2185 | - return results |
2186 | -} |
2187 | +var once sync.Once |
2188 | |
2189 | func init() { |
2190 | - if !signalHandlerRegistered { |
2191 | - setupSignalHandler() |
2192 | - signalHandlerRegistered = true |
2193 | - } |
2194 | -} |
2195 | - |
2196 | -// undoMounts unmounts all mounts this package has mounted optionally |
2197 | -// only unmounting bind mounts and leaving all remaining mounts. |
2198 | -func undoMounts(bindMountsOnly bool) error { |
2199 | - |
2200 | - mountsCopy := make(mountEntryArray, len(mounts), cap(mounts)) |
2201 | - copy(mountsCopy, mounts) |
2202 | - |
2203 | - // reverse sort to ensure unmounts are handled in the correct |
2204 | - // order. |
2205 | - sort.Sort(sort.Reverse(mountsCopy)) |
2206 | - |
2207 | - // Iterate backwards since we want a reverse-sorted list of |
2208 | - // mounts to ensure we can unmount in order. |
2209 | - for _, mount := range mountsCopy { |
2210 | - if bindMountsOnly && !mount.bindMount { |
2211 | - continue |
2212 | - } |
2213 | - |
2214 | - if err := unmountAndRemoveFromGlobalMountList(mount.target); err != nil { |
2215 | - return err |
2216 | - } |
2217 | - } |
2218 | - |
2219 | - return nil |
2220 | + once.Do(setupSignalHandler) |
2221 | } |
2222 | |
2223 | func signalHandler(sig os.Signal) { |
2224 | @@ -291,66 +155,6 @@ |
2225 | return labels |
2226 | } |
2227 | |
2228 | -func mount(source, target, options string) (err error) { |
2229 | - var args []string |
2230 | - |
2231 | - args = append(args, "/bin/mount") |
2232 | - if options != "" { |
2233 | - args = append(args, fmt.Sprintf("-o%s", options)) |
2234 | - } |
2235 | - |
2236 | - args = append(args, source) |
2237 | - args = append(args, target) |
2238 | - |
2239 | - return runCommand(args...) |
2240 | -} |
2241 | - |
2242 | -// Mount the given directory and add it to the global mounts slice |
2243 | -func mountAndAddToGlobalMountList(m mountEntry) (err error) { |
2244 | - |
2245 | - err = mount(m.source, m.target, m.options) |
2246 | - if err == nil { |
2247 | - mounts = append(mounts, m) |
2248 | - } |
2249 | - |
2250 | - return err |
2251 | -} |
2252 | - |
2253 | -// Unmount the given directory and remove it from the global "mounts" slice |
2254 | -func unmountAndRemoveFromGlobalMountList(target string) (err error) { |
2255 | - err = runCommand("/bin/umount", target) |
2256 | - if err != nil { |
2257 | - return err |
2258 | - |
2259 | - } |
2260 | - |
2261 | - results := removeMountByTarget(mounts, target) |
2262 | - |
2263 | - // Update global |
2264 | - mounts = results |
2265 | - |
2266 | - return nil |
2267 | -} |
2268 | - |
2269 | -// Run fsck(8) on specified device. |
2270 | -func fsck(device string) (err error) { |
2271 | - return runCommand( |
2272 | - "/sbin/fsck", |
2273 | - "-M", // Paranoia - don't fsck if already mounted |
2274 | - "-av", device) |
2275 | -} |
2276 | - |
2277 | -// Returns the position of the string in the given slice or -1 if its not found |
2278 | -func stringInSlice(slice []string, value string) int { |
2279 | - for i, s := range slice { |
2280 | - if s == value { |
2281 | - return i |
2282 | - } |
2283 | - } |
2284 | - |
2285 | - return -1 |
2286 | -} |
2287 | - |
2288 | var runLsblk = func() (out []string, err error) { |
2289 | output, err := runCommandWithStdout( |
2290 | "/bin/lsblk", |
2291 | @@ -430,8 +234,10 @@ |
2292 | continue |
2293 | } |
2294 | */ |
2295 | + shortName := string(name[len(name)-1]) |
2296 | bd := blockDevice{ |
2297 | name: fields["LABEL"], |
2298 | + shortName: shortName, |
2299 | device: device, |
2300 | mountpoint: fields["MOUNTPOINT"], |
2301 | parentName: disk, |
2302 | @@ -443,20 +249,11 @@ |
2303 | return partitions, nil |
2304 | } |
2305 | |
2306 | -var makeDirectory = func(path string, mode os.FileMode) error { |
2307 | - return os.MkdirAll(path, mode) |
2308 | -} |
2309 | - |
2310 | -func (p *Partition) makeMountPoint() (err error) { |
2311 | - return makeDirectory(p.MountTarget(), dirMode) |
2312 | -} |
2313 | - |
2314 | // New creates a new partition type |
2315 | func New() *Partition { |
2316 | p := new(Partition) |
2317 | |
2318 | p.getPartitionDetails() |
2319 | - p.hardwareSpecFile = filepath.Join(p.cacheDir(), hardwareSpecFile) |
2320 | |
2321 | return p |
2322 | } |
2323 | @@ -500,7 +297,7 @@ |
2324 | }() |
2325 | } |
2326 | |
2327 | - err = f(p.MountTarget()) |
2328 | + err = f(mountTarget) |
2329 | return err |
2330 | } |
2331 | |
2332 | @@ -529,7 +326,8 @@ |
2333 | return err |
2334 | } |
2335 | |
2336 | - return bootloader.MarkCurrentBootSuccessful() |
2337 | + currentRootfs := p.rootPartition().shortName |
2338 | + return bootloader.MarkCurrentBootSuccessful(currentRootfs) |
2339 | } |
2340 | |
2341 | // IsNextBootOther return true if the next boot will use the other rootfs |
2342 | @@ -539,48 +337,27 @@ |
2343 | if err != nil { |
2344 | return false |
2345 | } |
2346 | - return isNextBootOther(bootloader) |
2347 | -} |
2348 | - |
2349 | -// Returns the full path to the cache directory, which is used as a |
2350 | -// scratch pad, for downloading new images to and bind mounting the |
2351 | -// rootfs. |
2352 | -func (p *Partition) cacheDir() string { |
2353 | - return defaultCacheDir |
2354 | -} |
2355 | - |
2356 | -func (p *Partition) hardwareSpec() (hardwareSpecType, error) { |
2357 | - h := hardwareSpecType{} |
2358 | - |
2359 | - data, err := ioutil.ReadFile(p.hardwareSpecFile) |
2360 | - // if hardware.yaml does not exist it just means that there was no |
2361 | - // device part in the update. |
2362 | - if os.IsNotExist(err) { |
2363 | - return h, ErrNoHardwareYaml |
2364 | - } else if err != nil { |
2365 | - return h, err |
2366 | - } |
2367 | - |
2368 | - if err := yaml.Unmarshal([]byte(data), &h); err != nil { |
2369 | - return h, err |
2370 | - } |
2371 | - |
2372 | - return h, nil |
2373 | -} |
2374 | - |
2375 | -// Return full path to the main assets directory |
2376 | -func (p *Partition) assetsDir() string { |
2377 | - return filepath.Join(p.cacheDir(), assetsDir) |
2378 | -} |
2379 | - |
2380 | -// Return the full path to the hardware-specific flash assets directory. |
2381 | -func (p *Partition) flashAssetsDir() string { |
2382 | - return filepath.Join(p.cacheDir(), flashAssetsDir) |
2383 | -} |
2384 | - |
2385 | -// MountTarget gets the full path to the mount target directory |
2386 | -func (p *Partition) MountTarget() string { |
2387 | - return filepath.Join(p.cacheDir(), mountTarget) |
2388 | + |
2389 | + value, err := bootloader.GetBootVar(bootloaderBootmodeVar) |
2390 | + if err != nil { |
2391 | + return false |
2392 | + } |
2393 | + |
2394 | + if value != bootloaderBootmodeTry { |
2395 | + return false |
2396 | + } |
2397 | + |
2398 | + fsname, err := bootloader.GetNextBootRootFSName() |
2399 | + if err != nil { |
2400 | + return false |
2401 | + } |
2402 | + |
2403 | + otherRootfs := p.otherRootPartition().shortName |
2404 | + if fsname == otherRootfs { |
2405 | + return true |
2406 | + } |
2407 | + |
2408 | + return false |
2409 | } |
2410 | |
2411 | func (p *Partition) getPartitionDetails() (err error) { |
2412 | @@ -676,11 +453,13 @@ |
2413 | func (p *Partition) mountOtherRootfs(readOnly bool) (err error) { |
2414 | var other *blockDevice |
2415 | |
2416 | - p.makeMountPoint() |
2417 | + if err := os.MkdirAll(mountTarget, dirMode); err != nil { |
2418 | + return err |
2419 | + } |
2420 | |
2421 | other = p.otherRootPartition() |
2422 | |
2423 | - m := mountEntry{source: other.device, target: p.MountTarget()} |
2424 | + m := mountEntry{source: other.device, target: mountTarget} |
2425 | |
2426 | if readOnly { |
2427 | m.options = "ro" |
2428 | @@ -707,9 +486,7 @@ |
2429 | |
2430 | // Ensure the other partition is mounted read-only. |
2431 | func (p *Partition) ensureOtherMountedRO() (err error) { |
2432 | - mountpoint := p.MountTarget() |
2433 | - |
2434 | - if err = runCommand("/bin/mountpoint", mountpoint); err == nil { |
2435 | + if err = runCommand("/bin/mountpoint", mountTarget); err == nil { |
2436 | // already mounted |
2437 | return err |
2438 | } |
2439 | @@ -741,14 +518,14 @@ |
2440 | |
2441 | return mountAndAddToGlobalMountList(mountEntry{ |
2442 | source: other.device, |
2443 | - target: p.MountTarget()}) |
2444 | + target: mountTarget}) |
2445 | } |
2446 | // r/w -> r/o: no fsck required. |
2447 | - return mount(other.device, p.MountTarget(), "remount,ro") |
2448 | + return mount(other.device, mountTarget, "remount,ro") |
2449 | } |
2450 | |
2451 | func (p *Partition) unmountOtherRootfs() (err error) { |
2452 | - return unmountAndRemoveFromGlobalMountList(p.MountTarget()) |
2453 | + return unmountAndRemoveFromGlobalMountList(mountTarget) |
2454 | } |
2455 | |
2456 | // The bootloader requires a few filesystems to be mounted when |
2457 | @@ -773,7 +550,7 @@ |
2458 | } |
2459 | |
2460 | for _, fs := range requiredChrootMounts { |
2461 | - target := filepath.Join(p.MountTarget(), fs) |
2462 | + target := filepath.Join(mountTarget, fs) |
2463 | |
2464 | err := mountAndAddToGlobalMountList(mountEntry{source: fs, |
2465 | target: target, |
2466 | @@ -787,11 +564,11 @@ |
2467 | // Grub also requires access to both rootfs's when run from |
2468 | // within a chroot (to allow it to create menu entries for |
2469 | // both), so bindmount the real rootfs. |
2470 | - targetInChroot := filepath.Join(p.MountTarget(), p.MountTarget()) |
2471 | + targetInChroot := filepath.Join(mountTarget, mountTarget) |
2472 | |
2473 | // FIXME: we should really remove this after the unmount |
2474 | |
2475 | - if err = makeDirectory(targetInChroot, dirMode); err != nil { |
2476 | + if err = os.MkdirAll(targetInChroot, dirMode); err != nil { |
2477 | return err |
2478 | } |
2479 | |
2480 | @@ -822,7 +599,8 @@ |
2481 | // wrong given that handleAssets may fails and we will |
2482 | // knowingly boot into a broken system |
2483 | err = p.RunWithOther(RW, func(otherRoot string) (err error) { |
2484 | - return bootloader.ToggleRootFS() |
2485 | + otherRootfs := p.otherRootPartition().shortName |
2486 | + return bootloader.ToggleRootFS(otherRootfs) |
2487 | }) |
2488 | |
2489 | if err != nil { |
2490 | @@ -831,14 +609,3 @@ |
2491 | |
2492 | return bootloader.HandleAssets() |
2493 | } |
2494 | - |
2495 | -// BootloaderDir returns the full path to the (mounted and writable) |
2496 | -// bootloader-specific boot directory. |
2497 | -func (p *Partition) BootloaderDir() string { |
2498 | - bootloader, err := bootloader(p) |
2499 | - if err != nil { |
2500 | - return "" |
2501 | - } |
2502 | - |
2503 | - return bootloader.BootDir() |
2504 | -} |
2505 | |
2506 | === modified file 'partition/partition_test.go' |
2507 | --- partition/partition_test.go 2015-06-09 17:43:20 +0000 |
2508 | +++ partition/partition_test.go 2015-06-26 00:24:26 +0000 |
2509 | @@ -44,14 +44,13 @@ |
2510 | return err |
2511 | } |
2512 | |
2513 | -func mockMakeDirectory(path string, mode os.FileMode) error { |
2514 | - return nil |
2515 | -} |
2516 | - |
2517 | func (s *PartitionTestSuite) SetUpTest(c *C) { |
2518 | s.tempdir = c.MkDir() |
2519 | runLsblk = mockRunLsblkDualSnappy |
2520 | |
2521 | + // custom mount target |
2522 | + mountTarget = c.MkDir() |
2523 | + |
2524 | // setup fake paths for grub |
2525 | bootloaderGrubDir = filepath.Join(s.tempdir, "boot", "grub") |
2526 | bootloaderGrubConfigFile = filepath.Join(bootloaderGrubDir, "grub.cfg") |
2527 | @@ -72,8 +71,10 @@ |
2528 | |
2529 | // always restore what we might have mocked away |
2530 | runCommand = runCommandImpl |
2531 | - defaultCacheDir = realDefaultCacheDir |
2532 | bootloader = bootloaderImpl |
2533 | + cacheDir = cacheDirReal |
2534 | + hardwareSpecFile = hardwareSpecFileReal |
2535 | + mountTarget = mountTargetReal |
2536 | |
2537 | // grub vars |
2538 | bootloaderGrubConfigFile = bootloaderGrubConfigFileReal |
2539 | @@ -109,20 +110,6 @@ |
2540 | return tmp.Name() |
2541 | } |
2542 | |
2543 | -func (s *PartitionTestSuite) TestHardwareSpec(c *C) { |
2544 | - p := New() |
2545 | - c.Assert(p, NotNil) |
2546 | - |
2547 | - p.hardwareSpecFile = makeHardwareYaml(c, "") |
2548 | - hw, err := p.hardwareSpec() |
2549 | - c.Assert(err, IsNil) |
2550 | - c.Assert(hw.Kernel, Equals, "assets/vmlinuz") |
2551 | - c.Assert(hw.Initrd, Equals, "assets/initrd.img") |
2552 | - c.Assert(hw.DtbDir, Equals, "assets/dtbs") |
2553 | - c.Assert(hw.PartitionLayout, Equals, bootloaderSystemAB) |
2554 | - c.Assert(hw.Bootloader, Equals, bootloaderNameUboot) |
2555 | -} |
2556 | - |
2557 | func mockRunLsblkDualSnappy() (output []string, err error) { |
2558 | dualData := ` |
2559 | NAME="sda" LABEL="" PKNAME="" MOUNTPOINT="" |
2560 | @@ -136,46 +123,6 @@ |
2561 | return strings.Split(dualData, "\n"), err |
2562 | } |
2563 | |
2564 | -func (s *PartitionTestSuite) TestMountEntryArray(c *C) { |
2565 | - mea := mountEntryArray{} |
2566 | - |
2567 | - c.Assert(mea.Len(), Equals, 0) |
2568 | - |
2569 | - me := mountEntry{source: "/dev", |
2570 | - target: "/dev", |
2571 | - options: "bind", |
2572 | - bindMount: true} |
2573 | - |
2574 | - mea = append(mea, me) |
2575 | - c.Assert(mea.Len(), Equals, 1) |
2576 | - |
2577 | - me = mountEntry{source: "/foo", |
2578 | - target: "/foo", |
2579 | - options: "", |
2580 | - bindMount: false} |
2581 | - |
2582 | - mea = append(mea, me) |
2583 | - c.Assert(mea.Len(), Equals, 2) |
2584 | - |
2585 | - c.Assert(mea.Less(0, 1), Equals, true) |
2586 | - c.Assert(mea.Less(1, 0), Equals, false) |
2587 | - |
2588 | - mea.Swap(0, 1) |
2589 | - c.Assert(mea.Less(0, 1), Equals, false) |
2590 | - c.Assert(mea.Less(1, 0), Equals, true) |
2591 | - |
2592 | - results := removeMountByTarget(mea, "invalid") |
2593 | - |
2594 | - // No change expected |
2595 | - c.Assert(results, DeepEquals, mea) |
2596 | - |
2597 | - results = removeMountByTarget(mea, "/dev") |
2598 | - |
2599 | - c.Assert(len(results), Equals, 1) |
2600 | - c.Assert(results[0], Equals, mountEntry{source: "/foo", |
2601 | - target: "/foo", options: "", bindMount: false}) |
2602 | -} |
2603 | - |
2604 | func (s *PartitionTestSuite) TestSnappyDualRoot(c *C) { |
2605 | p := New() |
2606 | c.Assert(p.dualRootPartitions(), Equals, true) |
2607 | @@ -218,14 +165,13 @@ |
2608 | return nil |
2609 | }) |
2610 | c.Assert(err, IsNil) |
2611 | - c.Assert(reportedRoot, Equals, (&Partition{}).MountTarget()) |
2612 | + c.Assert(reportedRoot, Equals, mountTarget) |
2613 | } |
2614 | |
2615 | func (s *PartitionTestSuite) TestRunWithOtherDualParitionRWFuncErr(c *C) { |
2616 | c.Assert(mounts, DeepEquals, mountEntryArray(nil)) |
2617 | |
2618 | runCommand = mockRunCommand |
2619 | - makeDirectory = mockMakeDirectory |
2620 | |
2621 | p := New() |
2622 | err := p.RunWithOther(RW, func(otherRoot string) (err error) { |
2623 | @@ -239,8 +185,12 @@ |
2624 | // ensure cleanup happend |
2625 | |
2626 | // FIXME: mounts are global |
2627 | - expected := mountEntry{source: "/dev/sda4", |
2628 | - target: "/writable/cache/system", options: "", bindMount: false} |
2629 | + expected := mountEntry{ |
2630 | + source: "/dev/sda4", |
2631 | + target: mountTarget, |
2632 | + options: "", |
2633 | + bindMount: false, |
2634 | + } |
2635 | |
2636 | // At program exit, "other" should still be mounted |
2637 | c.Assert(mounts, DeepEquals, mountEntryArray{expected}) |
2638 | @@ -295,8 +245,12 @@ |
2639 | c.Assert(p, NotNil) |
2640 | |
2641 | p.mountOtherRootfs(false) |
2642 | - expected := mountEntry{source: "/dev/sda4", |
2643 | - target: "/writable/cache/system", options: "", bindMount: false} |
2644 | + expected := mountEntry{ |
2645 | + source: "/dev/sda4", |
2646 | + target: mountTarget, |
2647 | + options: "", |
2648 | + bindMount: false, |
2649 | + } |
2650 | |
2651 | c.Assert(mounts, DeepEquals, mountEntryArray{expected}) |
2652 | |
2653 | @@ -313,26 +267,26 @@ |
2654 | |
2655 | p.bindmountRequiredFilesystems() |
2656 | c.Assert(mounts, DeepEquals, mountEntryArray{ |
2657 | - mountEntry{source: "/dev", target: p.MountTarget() + "/dev", |
2658 | - options: "bind", bindMount: true}, |
2659 | - |
2660 | - mountEntry{source: "/proc", target: p.MountTarget() + "/proc", |
2661 | - options: "bind", bindMount: true}, |
2662 | - |
2663 | - mountEntry{source: "/sys", target: p.MountTarget() + "/sys", |
2664 | - options: "bind", bindMount: true}, |
2665 | - mountEntry{source: "/boot/efi", target: p.MountTarget() + "/boot/efi", |
2666 | + mountEntry{source: "/dev", target: mountTarget + "/dev", |
2667 | + options: "bind", bindMount: true}, |
2668 | + |
2669 | + mountEntry{source: "/proc", target: mountTarget + "/proc", |
2670 | + options: "bind", bindMount: true}, |
2671 | + |
2672 | + mountEntry{source: "/sys", target: mountTarget + "/sys", |
2673 | + options: "bind", bindMount: true}, |
2674 | + mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi", |
2675 | options: "bind", bindMount: true}, |
2676 | |
2677 | // this comes from the grub bootloader via AdditionalBindMounts |
2678 | - mountEntry{source: "/boot/grub", target: p.MountTarget() + "/boot/grub", |
2679 | + mountEntry{source: "/boot/grub", target: mountTarget + "/boot/grub", |
2680 | options: "bind", bindMount: true}, |
2681 | |
2682 | // Required to allow grub inside the chroot to access |
2683 | // the "current" rootfs outside the chroot (used |
2684 | // to generate the grub menuitems). |
2685 | mountEntry{source: "/", |
2686 | - target: p.MountTarget() + p.MountTarget(), |
2687 | + target: mountTarget + mountTarget, |
2688 | options: "bind,ro", bindMount: true}, |
2689 | }) |
2690 | p.unmountRequiredFilesystems() |
2691 | @@ -351,23 +305,23 @@ |
2692 | p.bindmountRequiredFilesystems() |
2693 | c.Assert(mounts, DeepEquals, mountEntryArray{ |
2694 | |
2695 | - mountEntry{source: "/dev/sda4", target: "/writable/cache/system", |
2696 | + mountEntry{source: "/dev/sda4", target: mountTarget, |
2697 | options: "", bindMount: false}, |
2698 | |
2699 | - mountEntry{source: "/dev", target: p.MountTarget() + "/dev", |
2700 | - options: "bind", bindMount: true}, |
2701 | - |
2702 | - mountEntry{source: "/proc", target: p.MountTarget() + "/proc", |
2703 | - options: "bind", bindMount: true}, |
2704 | - |
2705 | - mountEntry{source: "/sys", target: p.MountTarget() + "/sys", |
2706 | - options: "bind", bindMount: true}, |
2707 | - |
2708 | - mountEntry{source: "/boot/efi", target: p.MountTarget() + "/boot/efi", |
2709 | + mountEntry{source: "/dev", target: mountTarget + "/dev", |
2710 | + options: "bind", bindMount: true}, |
2711 | + |
2712 | + mountEntry{source: "/proc", target: mountTarget + "/proc", |
2713 | + options: "bind", bindMount: true}, |
2714 | + |
2715 | + mountEntry{source: "/sys", target: mountTarget + "/sys", |
2716 | + options: "bind", bindMount: true}, |
2717 | + |
2718 | + mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi", |
2719 | options: "bind", bindMount: true}, |
2720 | |
2721 | mountEntry{source: "/", |
2722 | - target: p.MountTarget() + p.MountTarget(), |
2723 | + target: mountTarget + mountTarget, |
2724 | options: "bind,ro", bindMount: true}, |
2725 | }) |
2726 | |
2727 | @@ -375,8 +329,12 @@ |
2728 | undoMounts(true) |
2729 | |
2730 | c.Assert(mounts, DeepEquals, mountEntryArray{ |
2731 | - mountEntry{source: "/dev/sda4", target: "/writable/cache/system", |
2732 | - options: "", bindMount: false}, |
2733 | + mountEntry{ |
2734 | + source: "/dev/sda4", |
2735 | + target: mountTarget, |
2736 | + options: "", |
2737 | + bindMount: false, |
2738 | + }, |
2739 | }) |
2740 | |
2741 | // should unmount everything |
2742 | @@ -421,7 +379,7 @@ |
2743 | func (b *mockBootloader) Name() bootloaderName { |
2744 | return "mocky" |
2745 | } |
2746 | -func (b *mockBootloader) ToggleRootFS() error { |
2747 | +func (b *mockBootloader) ToggleRootFS(otherRootfs string) error { |
2748 | b.ToggleRootFSCalled = true |
2749 | return nil |
2750 | } |
2751 | @@ -436,16 +394,10 @@ |
2752 | func (b *mockBootloader) GetBootVar(name string) (string, error) { |
2753 | return "", nil |
2754 | } |
2755 | -func (b *mockBootloader) GetRootFSName() string { |
2756 | - return "" |
2757 | -} |
2758 | -func (b *mockBootloader) GetOtherRootFSName() string { |
2759 | - return "" |
2760 | -} |
2761 | func (b *mockBootloader) GetNextBootRootFSName() (string, error) { |
2762 | return "", nil |
2763 | } |
2764 | -func (b *mockBootloader) MarkCurrentBootSuccessful() error { |
2765 | +func (b *mockBootloader) MarkCurrentBootSuccessful(currentRootfs string) error { |
2766 | b.MarkCurrentBootSuccessfulCalled = true |
2767 | return nil |
2768 | } |
2769 | |
2770 | === modified file 'partition/utils.go' |
2771 | --- partition/utils.go 2015-05-19 14:09:19 +0000 |
2772 | +++ partition/utils.go 2015-06-26 00:24:26 +0000 |
2773 | @@ -69,3 +69,22 @@ |
2774 | |
2775 | // This is a var instead of a function to making mocking in the tests easier |
2776 | var runCommandWithStdout = runCommandWithStdoutImpl |
2777 | + |
2778 | +// Run fsck(8) on specified device. |
2779 | +func fsck(device string) (err error) { |
2780 | + return runCommand( |
2781 | + "/sbin/fsck", |
2782 | + "-M", // Paranoia - don't fsck if already mounted |
2783 | + "-av", device) |
2784 | +} |
2785 | + |
2786 | +// Returns the position of the string in the given slice or -1 if its not found |
2787 | +func stringInSlice(slice []string, value string) int { |
2788 | + for i, s := range slice { |
2789 | + if s == value { |
2790 | + return i |
2791 | + } |
2792 | + } |
2793 | + |
2794 | + return -1 |
2795 | +} |
2796 | |
2797 | === modified file 'snappy/errors.go' |
2798 | --- snappy/errors.go 2015-06-04 22:21:51 +0000 |
2799 | +++ snappy/errors.go 2015-06-26 00:24:26 +0000 |
2800 | @@ -140,6 +140,8 @@ |
2801 | |
2802 | // ErrInvalidSeccompPolicy is returned when policy-version and policy-vender are not set together |
2803 | ErrInvalidSeccompPolicy = errors.New("policy-version and policy-vendor must be specified together") |
2804 | + // ErrNoSeccompPolicy is returned when an expected seccomp policy is not provided. |
2805 | + ErrNoSeccompPolicy = errors.New("no seccomp policy provided") |
2806 | ) |
2807 | |
2808 | // ErrArchitectureNotSupported is returned when trying to install a snappy package that |
2809 | |
2810 | === modified file 'snappy/install.go' |
2811 | --- snappy/install.go 2015-06-11 06:33:42 +0000 |
2812 | +++ snappy/install.go 2015-06-26 00:24:26 +0000 |
2813 | @@ -25,6 +25,7 @@ |
2814 | "sort" |
2815 | |
2816 | "launchpad.net/snappy/logger" |
2817 | + "launchpad.net/snappy/partition" |
2818 | "launchpad.net/snappy/progress" |
2819 | "launchpad.net/snappy/provisioning" |
2820 | ) |
2821 | @@ -96,8 +97,7 @@ |
2822 | // FIXME: this is terrible, we really need a single |
2823 | // bootloader dir like /boot or /boot/loader |
2824 | // instead of having to query the partition code |
2825 | - p := newPartition() |
2826 | - if provisioning.InDeveloperMode(p.BootloaderDir()) { |
2827 | + if provisioning.InDeveloperMode(partition.BootloaderDir()) { |
2828 | flags |= AllowUnauthenticated |
2829 | } |
2830 | |
2831 | |
2832 | === modified file 'snappy/security.go' |
2833 | --- snappy/security.go 2015-06-02 16:31:01 +0000 |
2834 | +++ snappy/security.go 2015-06-26 00:24:26 +0000 |
2835 | @@ -151,6 +151,11 @@ |
2836 | syscalls := []string{} |
2837 | |
2838 | if sd.SecurityOverride != nil { |
2839 | + if sd.SecurityOverride.Seccomp == "" { |
2840 | + logger.Noticef("No seccomp policy found") |
2841 | + return nil, ErrNoSeccompPolicy |
2842 | + } |
2843 | + |
2844 | fn := filepath.Join(baseDir, sd.SecurityOverride.Seccomp) |
2845 | var s securitySeccompOverride |
2846 | err := readSeccompOverride(fn, &s) |
2847 | |
2848 | === modified file 'snappy/security_test.go' |
2849 | --- snappy/security_test.go 2015-06-08 16:26:25 +0000 |
2850 | +++ snappy/security_test.go 2015-06-26 00:24:26 +0000 |
2851 | @@ -58,6 +58,13 @@ |
2852 | c.Assert(string(content), Equals, expected) |
2853 | } |
2854 | |
2855 | +func (a *SecurityTestSuite) TestSnappyNoSeccompOverrideEntry(c *C) { |
2856 | + sd := SecurityDefinitions{SecurityOverride: &SecurityOverrideDefinition{}} |
2857 | + |
2858 | + _, err := generateSeccompPolicy(c.MkDir(), "appName", sd) |
2859 | + c.Assert(err, Equals, ErrNoSeccompPolicy) |
2860 | +} |
2861 | + |
2862 | // no special security settings generate the default |
2863 | func (a *SecurityTestSuite) TestSnappyHandleApparmorSecurityDefault(c *C) { |
2864 | sec := &SecurityDefinitions{} |
2865 | |
2866 | === modified file 'snappy/systemimage.go' |
2867 | --- snappy/systemimage.go 2015-06-10 13:38:01 +0000 |
2868 | +++ snappy/systemimage.go 2015-06-26 00:24:26 +0000 |
2869 | @@ -169,9 +169,16 @@ |
2870 | return s.partition.ToggleNextBoot() |
2871 | } |
2872 | |
2873 | +// override in tests |
2874 | +var bootloaderDir = bootloaderDirImpl |
2875 | + |
2876 | +func bootloaderDirImpl() string { |
2877 | + return partition.BootloaderDir() |
2878 | +} |
2879 | + |
2880 | // Install installs the snap |
2881 | func (s *SystemImagePart) Install(pb progress.Meter, flags InstallFlags) (name string, err error) { |
2882 | - if provisioning.IsSideLoaded(s.partition.BootloaderDir()) { |
2883 | + if provisioning.IsSideLoaded(bootloaderDir()) { |
2884 | return "", ErrSideLoaded |
2885 | } |
2886 | |
2887 | |
2888 | === modified file 'snappy/systemimage_test.go' |
2889 | --- snappy/systemimage_test.go 2015-06-09 17:43:20 +0000 |
2890 | +++ snappy/systemimage_test.go 2015-06-26 00:24:26 +0000 |
2891 | @@ -28,7 +28,7 @@ |
2892 | "strings" |
2893 | "testing" |
2894 | |
2895 | - partition "launchpad.net/snappy/partition" |
2896 | + "launchpad.net/snappy/partition" |
2897 | "launchpad.net/snappy/provisioning" |
2898 | |
2899 | . "gopkg.in/check.v1" |
2900 | @@ -72,6 +72,7 @@ |
2901 | func (s *SITestSuite) TearDownTest(c *C) { |
2902 | s.mockSystemImageWebServer.Close() |
2903 | systemImageRoot = "/" |
2904 | + bootloaderDir = bootloaderDirImpl |
2905 | } |
2906 | |
2907 | func makeMockSystemImageCli(c *C, tempdir string) string { |
2908 | @@ -390,18 +391,16 @@ |
2909 | device-part: /some/path/file.tgz |
2910 | developer-mode: true |
2911 | ` |
2912 | - tempBootDir, err = ioutil.TempDir("", "") |
2913 | - c.Assert(err, IsNil) |
2914 | - |
2915 | + tempBootDir := c.MkDir() |
2916 | parts, err := s.systemImage.Updates() |
2917 | |
2918 | sp := parts[0].(*SystemImagePart) |
2919 | mockPartition := MockPartition{} |
2920 | sp.partition = &mockPartition |
2921 | |
2922 | - bootDir := sp.partition.BootloaderDir() |
2923 | + bootloaderDir = func() string { return tempBootDir } |
2924 | |
2925 | - sideLoaded := filepath.Join(bootDir, provisioning.InstallYamlFile) |
2926 | + sideLoaded := filepath.Join(tempBootDir, provisioning.InstallYamlFile) |
2927 | |
2928 | err = os.MkdirAll(filepath.Dir(sideLoaded), 0775) |
2929 | c.Assert(err, IsNil) |
2930 | @@ -414,7 +413,4 @@ |
2931 | // Ensure the install fails if the system is sideloaded |
2932 | _, err = sp.Install(pb, 0) |
2933 | c.Assert(err, Equals, ErrSideLoaded) |
2934 | - |
2935 | - os.Remove(tempBootDir) |
2936 | - tempBootDir = "" |
2937 | } |
Nice, thanks!