Merge lp:~elopio/snappy/cross-compile-debs-merge-failover into lp:~fgimenez/snappy/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
Reviewer Review Type Date Requested Status
Federico Gimenez Approve
Review via email: mp+263051@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Federico Gimenez (fgimenez) wrote :

Nice, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '_integration-tests/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 }

Subscribers

People subscribed via source and target branches

to all changes: