Merge lp:~mvo/snappy/snappy-all-snap2 into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Michael Vogt
Status: Work in progress
Proposed branch: lp:~mvo/snappy/snappy-all-snap2
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 5273 lines (+682/-3895)
35 files modified
bootloader/bootloader.go (+65/-229)
bootloader/bootloader_grub.go (+4/-51)
bootloader/bootloader_grub_test.go (+47/-122)
bootloader/bootloader_test.go (+94/-381)
bootloader/bootloader_uboot.go (+25/-216)
bootloader/bootloader_uboot_test.go (+47/-285)
bootloader/utils.go (+63/-0)
bootloader/utils_test.go (+47/-0)
cmd/snappy/cmd_booted.go (+2/-8)
gen-coverage.sh (+2/-2)
partition/assets.go (+0/-87)
partition/assets_test.go (+0/-36)
partition/dirs.go (+0/-41)
partition/mount.go (+0/-153)
partition/mount_test.go (+0/-64)
partition/partition.go (+0/-622)
partition/utils.go (+0/-82)
partition/utils_test.go (+0/-47)
pkg/types.go (+2/-0)
snappy/click.go (+2/-1)
snappy/dirs.go (+4/-0)
snappy/install.go (+2/-2)
snappy/install_test.go (+0/-92)
snappy/kernel.go (+103/-0)
snappy/oem.go (+11/-0)
snappy/os.go (+68/-0)
snappy/parts.go (+4/-8)
snappy/purge.go (+1/-1)
snappy/purge_test.go (+1/-1)
snappy/snapp.go (+80/-15)
snappy/snapp_test.go (+8/-11)
snappy/systemimage.go (+0/-518)
snappy/systemimage_native.go (+0/-211)
snappy/systemimage_native_test.go (+0/-123)
snappy/systemimage_test.go (+0/-486)
To merge this branch: bzr merge lp:~mvo/snappy/snappy-all-snap2
Reviewer Review Type Date Requested Status
Snappy Developers Pending
Review via email: mp+269922@code.launchpad.net

Description of the change

This moves the os/kernel into snaps.

To post a comment you must log in.
lp:~mvo/snappy/snappy-all-snap2 updated
667. By Michael Vogt

fix activate in the kernel

668. By Michael Vogt

re-add utils.go

Unmerged revisions

668. By Michael Vogt

re-add utils.go

667. By Michael Vogt

fix activate in the kernel

666. By Michael Vogt

add {Oem,OsKernel}Snap types

665. By Michael Vogt

re-add test hook function

664. By Michael Vogt

rename partition to bootloader

663. By Michael Vogt

add tests for the new code

662. By Michael Vogt

rename PartitionTestSuite->BootloaderTestSuite

661. By Michael Vogt

unset snappy_trial_boot

660. By Michael Vogt

move partition.go bits into bootloader as we do not care about partitioning at this level anymore

659. By Michael Vogt

kill partition.IsNextBootOther

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== renamed directory 'partition' => 'bootloader'
2=== modified file 'bootloader/bootloader.go'
3--- partition/bootloader.go 2015-07-16 11:07:30 +0000
4+++ bootloader/bootloader.go 2015-09-03 08:14:56 +0000
5@@ -17,70 +17,40 @@
6 *
7 */
8
9-package partition
10+package bootloader
11
12 import (
13- "fmt"
14- "os"
15- "path/filepath"
16+ "errors"
17+ "strings"
18+)
19
20- "launchpad.net/snappy/helpers"
21+var (
22+ // ErrBootloader is returned if the bootloader can not be determined
23+ ErrBootloader = errors.New("Unable to determine bootloader")
24 )
25
26 const (
27- // bootloader variable used to denote which rootfs to boot from
28- bootloaderRootfsVar = "snappy_ab"
29-
30 // bootloader variable used to determine if boot was successful.
31 // Set to value of either bootloaderBootmodeTry (when attempting
32 // to boot a new rootfs) or bootloaderBootmodeSuccess (to denote
33 // that the boot of the new rootfs was successful).
34 bootloaderBootmodeVar = "snappy_mode"
35
36- bootloaderTrialBootVar = "snappy_trial_boot"
37-
38 // Initial and final values
39 bootloaderBootmodeTry = "try"
40 bootloaderBootmodeSuccess = "regular"
41
42- // textual description in hardware.yaml for AB systems
43- bootloaderSystemAB = "system-AB"
44+ // set by the bootloader itself to "1" and it will be cleared
45+ // as the last action of the boot
46+ bootloaderTrialBootVar = "snappy_trial_boot"
47 )
48
49-type bootloaderName string
50-
51 type bootLoader interface {
52- // Name of the bootloader
53- Name() bootloaderName
54-
55- // Switch bootloader configuration so that the "other" root
56- // filesystem partition will be used on next boot.
57- ToggleRootFS(otherRootfs string) error
58-
59- // Hook function called before system-image starts downloading
60- // and applying archives that allows files to be copied between
61- // partitions.
62- SyncBootFiles(bootAssets map[string]string) error
63-
64- // Install any hardware-specific files that system-image
65- // downloaded.
66- HandleAssets() error
67-
68 // Return the value of the specified bootloader variable
69 GetBootVar(name string) (string, error)
70
71- // Return the 1-character name corresponding to the
72- // rootfs that will be used on _next_ boot.
73- //
74- // XXX: Note the distinction between this method and
75- // GetOtherRootFSName(): the latter corresponds to the other
76- // partition, whereas the value returned by this method is
77- // queried directly from the bootloader.
78- GetNextBootRootFSName() (string, error)
79-
80- // Update the bootloader configuration to mark the
81- // currently-booted rootfs as having booted successfully.
82- MarkCurrentBootSuccessful(currentRootfs string) error
83+ // Set the value of the specified bootloader variable
84+ SetBootVar(key, value string) error
85
86 // BootDir returns the (writable) bootloader-specific boot
87 // directory.
88@@ -88,16 +58,16 @@
89 }
90
91 // Factory method that returns a new bootloader for the given partition
92-var bootloader = bootloaderImpl
93+var new = bootloaderImpl
94
95-func bootloaderImpl(p *Partition) (bootLoader, error) {
96+func bootloaderImpl() (bootLoader, error) {
97 // try uboot
98- if uboot := newUboot(p); uboot != nil {
99+ if uboot := newUboot(); uboot != nil {
100 return uboot, nil
101 }
102
103 // no, try grub
104- if grub := newGrub(p); grub != nil {
105+ if grub := newGrub(); grub != nil {
106 return grub, nil
107 }
108
109@@ -105,196 +75,62 @@
110 return nil, ErrBootloader
111 }
112
113-type bootloaderType struct {
114- partition *Partition
115-
116- // each rootfs partition has a corresponding u-boot directory named
117- // from the last character of the partition name ('a' or 'b').
118- currentRootfs string
119- otherRootfs string
120-
121- // full path to rootfs-specific assets on boot partition
122- currentBootPath string
123- otherBootPath string
124-
125- // FIXME: this should /boot if possible
126- // the dir that the bootloader lives in (e.g. /boot/uboot)
127- bootloaderDir string
128-}
129-
130-func newBootLoader(partition *Partition, bootloaderDir string) *bootloaderType {
131- // FIXME: is this the right thing to do? i.e. what should we do
132- // on a single partition system?
133- if partition.otherRootPartition() == nil {
134- return nil
135- }
136-
137- // full label of the system {system-a,system-b}
138- currentLabel := partition.rootPartition().name
139- otherLabel := partition.otherRootPartition().name
140-
141- // single letter description of the rootfs {a,b}
142- currentRootfs := string(currentLabel[len(currentLabel)-1])
143- otherRootfs := string(otherLabel[len(otherLabel)-1])
144-
145- return &bootloaderType{
146- partition: partition,
147-
148- currentRootfs: currentRootfs,
149- otherRootfs: otherRootfs,
150-
151- // the paths that the kernel/initramfs are loaded, e.g.
152- // /boot/uboot/a
153- currentBootPath: filepath.Join(bootloaderDir, currentRootfs),
154- otherBootPath: filepath.Join(bootloaderDir, otherRootfs),
155-
156- // the base bootloader dir, e.g. /boot/uboot or /boot/grub
157- bootloaderDir: bootloaderDir,
158- }
159-}
160-
161-// FIXME:
162-// - populate kernel if missing
163-func (b *bootloaderType) SyncBootFiles(bootAssets map[string]string) (err error) {
164- for src, dst := range bootAssets {
165- if err := helpers.CopyIfDifferent(src, filepath.Join(b.bootloaderDir, dst)); err != nil {
166- return err
167- }
168- }
169-
170- srcDir := b.currentBootPath
171- destDir := b.otherBootPath
172-
173- // ensure they exist
174- for _, dir := range []string{srcDir, destDir} {
175- if err := os.MkdirAll(dir, 0755); err != nil {
176- return err
177- }
178-
179- }
180- return helpers.RSyncWithDelete(srcDir, destDir)
181-}
182-
183-// FIXME:
184-// - if this fails it will never be re-tried because the "other" patition
185-// is updated to revision-N in /etc/system-image/channel.ini
186-// so the system only downloads from revision-N onwards even though the
187-// complete update was not applied (i.e. kernel missing)
188-func (b *bootloaderType) HandleAssets() (err error) {
189- // check if we have anything, if there is no hardware yaml, there is nothing
190- // to process.
191- hardware, err := readHardwareSpec()
192- if err == ErrNoHardwareYaml {
193- return nil
194- } else if err != nil {
195- return err
196- }
197- // ensure to remove the file if there are no errors
198- defer func() {
199- if err == nil {
200- os.Remove(hardwareSpecFile)
201- }
202- }()
203-
204- /*
205- // validate bootloader
206- if hardware.Bootloader != b.Name() {
207- return fmt.Errorf(
208- "bootloader is of type %s but hardware spec requires %s",
209- b.Name(),
210- hardware.Bootloader)
211- }
212- */
213-
214- // validate partition layout
215- if b.partition.dualRootPartitions() && hardware.PartitionLayout != bootloaderSystemAB {
216- return fmt.Errorf("hardware spec requires dual root partitions")
217- }
218-
219- // ensure we have the destdir
220- destDir := b.otherBootPath
221- if err := os.MkdirAll(destDir, dirMode); err != nil {
222- return err
223- }
224-
225- // install kernel+initrd
226- for _, file := range []string{hardware.Kernel, hardware.Initrd} {
227-
228- if file == "" {
229- continue
230- }
231-
232- // expand path
233- path := filepath.Join(cacheDir, file)
234-
235- if !helpers.FileExists(path) {
236- return fmt.Errorf("can not find file %s", path)
237- }
238-
239- // ensure we remove the dir later
240- defer func() {
241- if err == nil {
242- os.RemoveAll(filepath.Dir(path))
243- }
244- }()
245-
246- if err := runCommand("/bin/cp", path, destDir); err != nil {
247- return err
248- }
249- }
250-
251- // TODO: look at the OEM package for dtb changes too once that is
252- // fully speced
253-
254- // install .dtb files
255- dtbSrcDir := filepath.Join(cacheDir, hardware.DtbDir)
256- // ensure there is a DtbDir specified
257- if hardware.DtbDir != "" && helpers.FileExists(dtbSrcDir) {
258- // ensure we cleanup the source dir
259- defer func() {
260- if err == nil {
261- os.RemoveAll(dtbSrcDir)
262- }
263- }()
264-
265- dtbDestDir := filepath.Join(destDir, "dtbs")
266- if err := os.MkdirAll(dtbDestDir, dirMode); err != nil {
267- return err
268- }
269-
270- files, err := filepath.Glob(filepath.Join(dtbSrcDir, "*"))
271+// MarkBootSuccessful marks the boot as successful
272+func MarkBootSuccessful() (err error) {
273+ bootloader, err := new()
274+ if err != nil {
275+ return err
276+ }
277+
278+ // FIXME: these vars should be applied all in a single run
279+ for _, k := range []string{"snappy_os", "snappy_kernel"} {
280+ value, err := bootloader.GetBootVar(k)
281 if err != nil {
282 return err
283 }
284
285- for _, file := range files {
286- if err := runCommand("/bin/cp", file, dtbDestDir); err != nil {
287- return err
288- }
289- }
290- }
291-
292- if helpers.FileExists(flashAssetsDir) {
293- // FIXME: we don't currently do anything with the
294- // MLO + uImage files since they are not specified in
295- // the hardware spec. So for now, just remove them.
296-
297- if err := os.RemoveAll(flashAssetsDir); err != nil {
298+ // FIXME: a bit ugly
299+ newKey := strings.Replace(k, "snappy_", "snappy_good_", -1)
300+ if err := bootloader.SetBootVar(newKey, value); err != nil {
301 return err
302 }
303- }
304-
305- return err
306+
307+ }
308+
309+ if err := bootloader.SetBootVar("snappy_mode", "regular"); err != nil {
310+ return err
311+ }
312+
313+ return bootloader.SetBootVar("snappy_trial_boot", "0")
314 }
315
316-// BootloaderDir returns the full path to the (mounted and writable)
317+// Dir returns the full path to the (mounted and writable)
318 // bootloader-specific boot directory.
319-func BootloaderDir() string {
320- if helpers.FileExists(bootloaderUbootDir) {
321- return bootloaderUbootDir
322- } else if helpers.FileExists(bootloaderGrubDir) {
323- return bootloaderGrubDir
324- }
325-
326- return ""
327+func Dir() string {
328+ bootloader, err := new()
329+ if err != nil {
330+ return ""
331+ }
332+
333+ return bootloader.BootDir()
334+}
335+
336+// SetBootVar sets a boot variable
337+func SetBootVar(key, value string) error {
338+ bootloader, err := new()
339+ if err != nil {
340+ return err
341+ }
342+
343+ return bootloader.SetBootVar(key, value)
344+}
345+
346+// GetBootVar sets a boot variable
347+func GetBootVar(key string) (string, error) {
348+ bootloader, err := new()
349+ if err != nil {
350+ return "", err
351+ }
352+
353+ return bootloader.GetBootVar(key)
354 }
355
356=== modified file 'bootloader/bootloader_grub.go'
357--- partition/bootloader_grub.go 2015-07-23 11:54:14 +0000
358+++ bootloader/bootloader_grub.go 2015-09-03 08:14:56 +0000
359@@ -17,7 +17,7 @@
360 *
361 */
362
363-package partition
364+package bootloader
365
366 import (
367 "fmt"
368@@ -45,45 +45,15 @@
369 )
370
371 type grub struct {
372- bootloaderType
373 }
374
375-const bootloaderNameGrub bootloaderName = "grub"
376-
377 // newGrub create a new Grub bootloader object
378-func newGrub(partition *Partition) bootLoader {
379+func newGrub() bootLoader {
380 if !helpers.FileExists(bootloaderGrubConfigFile) {
381 return nil
382 }
383
384- b := newBootLoader(partition, bootloaderGrubDir)
385- if b == nil {
386- return nil
387- }
388- g := grub{bootloaderType: *b}
389-
390- return &g
391-}
392-
393-func (g *grub) Name() bootloaderName {
394- return bootloaderNameGrub
395-}
396-
397-// ToggleRootFS make the Grub bootloader switch rootfs's.
398-//
399-// Approach:
400-//
401-// Update the grub configuration.
402-func (g *grub) ToggleRootFS(otherRootfs string) (err error) {
403-
404- if err := g.setBootVar(bootloaderBootmodeVar, bootloaderBootmodeTry); err != nil {
405- return err
406- }
407-
408- // Record the partition that will be used for next boot. This
409- // isn't necessary for correct operation under grub, but allows
410- // us to query the next boot device easily.
411- return g.setBootVar(bootloaderRootfsVar, otherRootfs)
412+ return &grub{}
413 }
414
415 func (g *grub) GetBootVar(name string) (value string, err error) {
416@@ -103,7 +73,7 @@
417 return cfg.Get("", name)
418 }
419
420-func (g *grub) setBootVar(name, value string) (err error) {
421+func (g *grub) SetBootVar(name, value string) (err error) {
422 // note that strings are not quoted since because
423 // RunCommand() does not use a shell and thus adding quotes
424 // stores them in the environment file (which is not desirable)
425@@ -111,23 +81,6 @@
426 return runCommand(bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", arg)
427 }
428
429-func (g *grub) GetNextBootRootFSName() (label string, err error) {
430- return g.GetBootVar(bootloaderRootfsVar)
431-}
432-
433-func (g *grub) MarkCurrentBootSuccessful(currentRootfs string) (err error) {
434- // Clear the variable set on boot to denote a good boot.
435- if err := g.setBootVar(bootloaderTrialBootVar, "0"); err != nil {
436- return err
437- }
438-
439- if err := g.setBootVar(bootloaderRootfsVar, currentRootfs); err != nil {
440- return err
441- }
442-
443- return g.setBootVar(bootloaderBootmodeVar, bootloaderBootmodeSuccess)
444-}
445-
446 func (g *grub) BootDir() string {
447 return bootloaderGrubDir
448 }
449
450=== modified file 'bootloader/bootloader_grub_test.go'
451--- partition/bootloader_grub_test.go 2015-07-23 11:54:14 +0000
452+++ bootloader/bootloader_grub_test.go 2015-09-03 08:14:56 +0000
453@@ -17,15 +17,13 @@
454 *
455 */
456
457-package partition
458+package bootloader
459
460 import (
461 "fmt"
462 "io/ioutil"
463 "os"
464- "path/filepath"
465-
466- "launchpad.net/snappy/helpers"
467+ "strings"
468
469 . "gopkg.in/check.v1"
470 )
471@@ -35,7 +33,21 @@
472 c.Assert(err, IsNil)
473 }
474
475-func (s *PartitionTestSuite) makeFakeGrubEnv(c *C) {
476+type singleCommand []string
477+
478+var allCommands = []singleCommand{}
479+
480+func mockRunCommandWithCapture(args ...string) (err error) {
481+ allCommands = append(allCommands, args)
482+ return nil
483+}
484+
485+func mockGrubEditenvList(cmd ...string) (string, error) {
486+ mockGrubEditenvOutput := fmt.Sprintf("%s=regular", bootloaderBootmodeVar)
487+ return mockGrubEditenvOutput, nil
488+}
489+
490+func (s *BootloaderTestSuite) makeFakeGrubEnv(c *C) {
491 // create bootloader
492 err := os.MkdirAll(bootloaderGrubDir, 0755)
493 c.Assert(err, IsNil)
494@@ -48,135 +60,48 @@
495 runCommand = mockRunCommandWithCapture
496 }
497
498-func (s *PartitionTestSuite) TestNewGrubNoGrubReturnsNil(c *C) {
499+func (s *BootloaderTestSuite) TestNewGrubNoGrubReturnsNil(c *C) {
500 bootloaderGrubConfigFile = "no-such-dir"
501
502- partition := New()
503- g := newGrub(partition)
504+ g := newGrub()
505 c.Assert(g, IsNil)
506 }
507
508-func (s *PartitionTestSuite) TestNewGrub(c *C) {
509- s.makeFakeGrubEnv(c)
510-
511- partition := New()
512- g := newGrub(partition)
513- c.Assert(g, NotNil)
514- c.Assert(g.Name(), Equals, bootloaderNameGrub)
515-}
516-
517-type singleCommand []string
518-
519-var allCommands = []singleCommand{}
520-
521-func mockRunCommandWithCapture(args ...string) (err error) {
522- allCommands = append(allCommands, args)
523- return nil
524-}
525-
526-func (s *PartitionTestSuite) TestToggleRootFS(c *C) {
527- s.makeFakeGrubEnv(c)
528- allCommands = []singleCommand{}
529-
530- partition := New()
531- g := newGrub(partition)
532- c.Assert(g, NotNil)
533- err := g.ToggleRootFS("b")
534- c.Assert(err, IsNil)
535-
536- // this is always called
537- mp := singleCommand{"/bin/mountpoint", mountTarget}
538- c.Assert(allCommands[0], DeepEquals, mp)
539-
540- expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_mode=try"}
541- c.Assert(allCommands[1], DeepEquals, expectedGrubSet)
542-
543- // the https://developer.ubuntu.com/en/snappy/porting guide says
544- // we always use the short names
545- expectedGrubSet = singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_ab=b"}
546- c.Assert(allCommands[2], DeepEquals, expectedGrubSet)
547-
548- c.Assert(len(allCommands), Equals, 3)
549-}
550-
551-func mockGrubEditenvList(cmd ...string) (string, error) {
552- mockGrubEditenvOutput := fmt.Sprintf("%s=regular", bootloaderBootmodeVar)
553- return mockGrubEditenvOutput, nil
554-}
555-
556-func (s *PartitionTestSuite) TestGetBootVer(c *C) {
557+func (s *BootloaderTestSuite) TestNewGrub(c *C) {
558+ s.makeFakeGrubEnv(c)
559+
560+ g := newGrub()
561+ c.Assert(g, NotNil)
562+}
563+
564+func (s *BootloaderTestSuite) TestGetBootVer(c *C) {
565 s.makeFakeGrubEnv(c)
566 runCommandWithStdout = mockGrubEditenvList
567
568- partition := New()
569- g := newGrub(partition)
570+ g := newGrub()
571
572 v, err := g.GetBootVar(bootloaderBootmodeVar)
573 c.Assert(err, IsNil)
574 c.Assert(v, Equals, "regular")
575 }
576
577-func (s *PartitionTestSuite) TestGetBootloaderWithGrub(c *C) {
578- s.makeFakeGrubEnv(c)
579- p := New()
580- bootloader, err := bootloader(p)
581- c.Assert(err, IsNil)
582- c.Assert(bootloader.Name(), Equals, bootloaderNameGrub)
583-}
584-
585-func (s *PartitionTestSuite) TestGrubMarkCurrentBootSuccessful(c *C) {
586- s.makeFakeGrubEnv(c)
587- allCommands = []singleCommand{}
588-
589- partition := New()
590- g := newGrub(partition)
591- c.Assert(g, NotNil)
592- err := g.MarkCurrentBootSuccessful("a")
593- c.Assert(err, IsNil)
594-
595- // this is always called
596- mp := singleCommand{"/bin/mountpoint", mountTarget}
597- c.Assert(allCommands[0], DeepEquals, mp)
598-
599- expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_trial_boot=0"}
600-
601- c.Assert(allCommands[1], DeepEquals, expectedGrubSet)
602-
603- expectedGrubSet2 := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_ab=a"}
604-
605- c.Assert(allCommands[2], DeepEquals, expectedGrubSet2)
606-
607- expectedGrubSet3 := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_mode=regular"}
608-
609- c.Assert(allCommands[3], DeepEquals, expectedGrubSet3)
610-
611-}
612-
613-func (s *PartitionTestSuite) TestSyncBootFilesWithAssets(c *C) {
614- err := os.MkdirAll(bootloaderGrubDir, 0755)
615- c.Assert(err, IsNil)
616-
617- runCommand = mockRunCommand
618- b := grub{
619- bootloaderType{
620- currentBootPath: c.MkDir(),
621- otherBootPath: c.MkDir(),
622- bootloaderDir: c.MkDir(),
623- },
624- }
625-
626- bootfile := filepath.Join(c.MkDir(), "bootfile")
627- err = ioutil.WriteFile(bootfile, []byte(bootfile), 0644)
628- c.Assert(err, IsNil)
629-
630- bootassets := map[string]string{
631- bootfile: filepath.Base(bootfile),
632- }
633-
634- err = b.SyncBootFiles(bootassets)
635- c.Assert(err, IsNil)
636-
637- dst := filepath.Join(b.bootloaderDir, bootassets[bootfile])
638- c.Check(helpers.FileExists(dst), Equals, true)
639- c.Check(helpers.FilesAreEqual(bootfile, dst), Equals, true)
640+func (s *BootloaderTestSuite) TestSetBootVer(c *C) {
641+ s.makeFakeGrubEnv(c)
642+ runCommandWithStdout = mockGrubEditenvList
643+
644+ g := newGrub()
645+
646+ err := g.SetBootVar("snappy_os", "123")
647+ c.Assert(err, IsNil)
648+
649+ expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_os=123"}
650+ c.Assert(allCommands[0], DeepEquals, expectedGrubSet)
651+ c.Assert(allCommands, HasLen, 1)
652+}
653+
654+func (s *BootloaderTestSuite) TestGrubBootdir(c *C) {
655+ s.makeFakeGrubEnv(c)
656+
657+ g := newGrub()
658+ c.Assert(strings.HasSuffix(g.BootDir(), "/boot/grub"), Equals, true)
659 }
660
661=== renamed file 'partition/partition_test.go' => 'bootloader/bootloader_test.go'
662--- partition/partition_test.go 2015-07-16 11:07:30 +0000
663+++ bootloader/bootloader_test.go 2015-09-03 08:14:56 +0000
664@@ -17,14 +17,11 @@
665 *
666 */
667
668-package partition
669+package bootloader
670
671 import (
672- "errors"
673- "io/ioutil"
674 "os"
675 "path/filepath"
676- "strings"
677 "testing"
678
679 . "gopkg.in/check.v1"
680@@ -34,22 +31,14 @@
681 func Test(t *testing.T) { TestingT(t) }
682
683 // partition specific testsuite
684-type PartitionTestSuite struct {
685+type BootloaderTestSuite struct {
686 tempdir string
687 }
688
689-var _ = Suite(&PartitionTestSuite{})
690-
691-func mockRunCommand(args ...string) (err error) {
692- return err
693-}
694-
695-func (s *PartitionTestSuite) SetUpTest(c *C) {
696+var _ = Suite(&BootloaderTestSuite{})
697+
698+func (s *BootloaderTestSuite) SetUpTest(c *C) {
699 s.tempdir = c.MkDir()
700- runLsblk = mockRunLsblkDualSnappy
701-
702- // custom mount target
703- mountTarget = c.MkDir()
704
705 // setup fake paths for grub
706 bootloaderGrubDir = filepath.Join(s.tempdir, "boot", "grub")
707@@ -58,23 +47,15 @@
708
709 // and uboot
710 bootloaderUbootDir = filepath.Join(s.tempdir, "boot", "uboot")
711- bootloaderUbootConfigFile = filepath.Join(bootloaderUbootDir, "uEnv.txt")
712- bootloaderUbootEnvFile = filepath.Join(bootloaderUbootDir, "uEnv.txt")
713 bootloaderUbootFwEnvFile = filepath.Join(bootloaderUbootDir, "uboot.env")
714- bootloaderUbootStampFile = filepath.Join(bootloaderUbootDir, "snappy-stamp.txt")
715-
716- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
717 }
718
719-func (s *PartitionTestSuite) TearDownTest(c *C) {
720+func (s *BootloaderTestSuite) TearDownTest(c *C) {
721 os.RemoveAll(s.tempdir)
722
723 // always restore what we might have mocked away
724 runCommand = runCommandImpl
725- bootloader = bootloaderImpl
726- cacheDir = cacheDirReal
727- hardwareSpecFile = hardwareSpecFileReal
728- mountTarget = mountTargetReal
729+ new = bootloaderImpl
730
731 // grub vars
732 bootloaderGrubConfigFile = bootloaderGrubConfigFileReal
733@@ -82,369 +63,101 @@
734
735 // uboot vars
736 bootloaderUbootDir = bootloaderUbootDirReal
737- bootloaderUbootConfigFile = bootloaderUbootConfigFileReal
738- bootloaderUbootEnvFile = bootloaderUbootEnvFileReal
739- bootloaderUbootStampFile = bootloaderUbootStampFileReal
740-
741- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
742-}
743-
744-func makeHardwareYaml(c *C, hardwareYaml string) (outPath string) {
745- tmp, err := ioutil.TempFile(c.MkDir(), "hw-")
746- c.Assert(err, IsNil)
747- defer tmp.Close()
748-
749- if hardwareYaml == "" {
750- hardwareYaml = `
751-kernel: assets/vmlinuz
752-initrd: assets/initrd.img
753-dtbs: assets/dtbs
754-partition-layout: system-AB
755-bootloader: u-boot
756-`
757- }
758- _, err = tmp.Write([]byte(hardwareYaml))
759- c.Assert(err, IsNil)
760-
761- return tmp.Name()
762-}
763-
764-func mockRunLsblkDualSnappy() (output []string, err error) {
765- dualData := `
766-NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
767-NAME="sda1" LABEL="" PKNAME="sda" MOUNTPOINT=""
768-NAME="sda2" LABEL="system-boot" PKNAME="sda" MOUNTPOINT="/boot/efi"
769-NAME="sda3" LABEL="system-a" PKNAME="sda" MOUNTPOINT="/"
770-NAME="sda4" LABEL="system-b" PKNAME="sda" MOUNTPOINT=""
771-NAME="sda5" LABEL="writable" PKNAME="sda" MOUNTPOINT="/writable"
772-NAME="sr0" LABEL="" PKNAME="" MOUNTPOINT=""
773-`
774- return strings.Split(dualData, "\n"), err
775-}
776-
777-func (s *PartitionTestSuite) TestSnappyDualRoot(c *C) {
778- p := New()
779- c.Assert(p.dualRootPartitions(), Equals, true)
780- c.Assert(p.singleRootPartition(), Equals, false)
781-
782- rootPartitions := p.rootPartitions()
783- c.Assert(rootPartitions[0].name, Equals, "system-a")
784- c.Assert(rootPartitions[0].device, Equals, "/dev/sda3")
785- c.Assert(rootPartitions[0].parentName, Equals, "/dev/sda")
786- c.Assert(rootPartitions[1].name, Equals, "system-b")
787- c.Assert(rootPartitions[1].device, Equals, "/dev/sda4")
788- c.Assert(rootPartitions[1].parentName, Equals, "/dev/sda")
789-
790- wp := p.writablePartition()
791- c.Assert(wp.name, Equals, "writable")
792- c.Assert(wp.device, Equals, "/dev/sda5")
793- c.Assert(wp.parentName, Equals, "/dev/sda")
794-
795- boot := p.bootPartition()
796- c.Assert(boot.name, Equals, "system-boot")
797- c.Assert(boot.device, Equals, "/dev/sda2")
798- c.Assert(boot.parentName, Equals, "/dev/sda")
799-
800- root := p.rootPartition()
801- c.Assert(root.name, Equals, "system-a")
802- c.Assert(root.device, Equals, "/dev/sda3")
803- c.Assert(root.parentName, Equals, "/dev/sda")
804-
805- other := p.otherRootPartition()
806- c.Assert(other.name, Equals, "system-b")
807- c.Assert(other.device, Equals, "/dev/sda4")
808- c.Assert(other.parentName, Equals, "/dev/sda")
809-}
810-
811-func (s *PartitionTestSuite) TestRunWithOtherDualParitionRO(c *C) {
812- p := New()
813- reportedRoot := ""
814- err := p.RunWithOther(RO, func(otherRoot string) (err error) {
815- reportedRoot = otherRoot
816- return nil
817- })
818- c.Assert(err, IsNil)
819- c.Assert(reportedRoot, Equals, mountTarget)
820-}
821-
822-func (s *PartitionTestSuite) TestRunWithOtherDualParitionRWFuncErr(c *C) {
823- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
824-
825- runCommand = mockRunCommand
826-
827- p := New()
828- err := p.RunWithOther(RW, func(otherRoot string) (err error) {
829- return errors.New("canary")
830- })
831-
832- // ensure we actually got the right error
833- c.Assert(err, NotNil)
834- c.Assert(err.Error(), Equals, "canary")
835-
836- // ensure cleanup happend
837-
838- // FIXME: mounts are global
839- expected := mountEntry{
840- source: "/dev/sda4",
841- target: mountTarget,
842- options: "",
843- bindMount: false,
844- }
845-
846- // At program exit, "other" should still be mounted
847- c.Assert(mounts, DeepEquals, mountEntryArray{expected})
848-
849- undoMounts(false)
850-
851- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
852-}
853-
854-func (s *PartitionTestSuite) TestRunWithOtherSingleParitionRO(c *C) {
855- runLsblk = mockRunLsblkSingleRootSnappy
856- p := New()
857- err := p.RunWithOther(RO, func(otherRoot string) (err error) {
858- return nil
859- })
860- c.Assert(err, Equals, ErrNoDualPartition)
861-}
862-
863-func mockRunLsblkSingleRootSnappy() (output []string, err error) {
864- dualData := `
865-NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
866-NAME="sda1" LABEL="" PKNAME="sda" MOUNTPOINT=""
867-NAME="sda2" LABEL="system-boot" PKNAME="sda" MOUNTPOINT=""
868-NAME="sda3" LABEL="system-a" PKNAME="sda" MOUNTPOINT="/"
869-NAME="sda5" LABEL="writable" PKNAME="sda" MOUNTPOINT="/writable"
870-`
871- return strings.Split(dualData, "\n"), err
872-}
873-func (s *PartitionTestSuite) TestSnappySingleRoot(c *C) {
874- runLsblk = mockRunLsblkSingleRootSnappy
875-
876- p := New()
877- c.Assert(p.dualRootPartitions(), Equals, false)
878- c.Assert(p.singleRootPartition(), Equals, true)
879-
880- root := p.rootPartition()
881- c.Assert(root.name, Equals, "system-a")
882- c.Assert(root.device, Equals, "/dev/sda3")
883- c.Assert(root.parentName, Equals, "/dev/sda")
884-
885- other := p.otherRootPartition()
886- c.Assert(other, IsNil)
887-
888- rootPartitions := p.rootPartitions()
889- c.Assert(&rootPartitions[0], DeepEquals, root)
890-}
891-
892-func (s *PartitionTestSuite) TestMountUnmountTracking(c *C) {
893- runCommand = mockRunCommand
894-
895- p := New()
896- c.Assert(p, NotNil)
897-
898- p.mountOtherRootfs(false)
899- expected := mountEntry{
900- source: "/dev/sda4",
901- target: mountTarget,
902- options: "",
903- bindMount: false,
904- }
905-
906- c.Assert(mounts, DeepEquals, mountEntryArray{expected})
907-
908- p.unmountOtherRootfs()
909- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
910-}
911-
912-func (s *PartitionTestSuite) TestUnmountRequiredFilesystems(c *C) {
913- runCommand = mockRunCommand
914- s.makeFakeGrubEnv(c)
915-
916- p := New()
917- c.Assert(c, NotNil)
918-
919- p.bindmountRequiredFilesystems()
920- c.Assert(mounts, DeepEquals, mountEntryArray{
921- mountEntry{source: "/dev", target: mountTarget + "/dev",
922- options: "bind", bindMount: true},
923-
924- mountEntry{source: "/proc", target: mountTarget + "/proc",
925- options: "bind", bindMount: true},
926-
927- mountEntry{source: "/sys", target: mountTarget + "/sys",
928- options: "bind", bindMount: true},
929- mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi",
930- options: "bind", bindMount: true},
931-
932- // Required to allow grub inside the chroot to access
933- // the "current" rootfs outside the chroot (used
934- // to generate the grub menuitems).
935- mountEntry{source: "/",
936- target: mountTarget + mountTarget,
937- options: "bind,ro", bindMount: true},
938- })
939- p.unmountRequiredFilesystems()
940- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
941-}
942-
943-func (s *PartitionTestSuite) TestUndoMounts(c *C) {
944- runCommand = mockRunCommand
945-
946- p := New()
947- c.Assert(c, NotNil)
948-
949- err := p.remountOther(RW)
950- c.Assert(err, IsNil)
951-
952- p.bindmountRequiredFilesystems()
953- c.Assert(mounts, DeepEquals, mountEntryArray{
954-
955- mountEntry{source: "/dev/sda4", target: mountTarget,
956- options: "", bindMount: false},
957-
958- mountEntry{source: "/dev", target: mountTarget + "/dev",
959- options: "bind", bindMount: true},
960-
961- mountEntry{source: "/proc", target: mountTarget + "/proc",
962- options: "bind", bindMount: true},
963-
964- mountEntry{source: "/sys", target: mountTarget + "/sys",
965- options: "bind", bindMount: true},
966-
967- mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi",
968- options: "bind", bindMount: true},
969-
970- mountEntry{source: "/",
971- target: mountTarget + mountTarget,
972- options: "bind,ro", bindMount: true},
973- })
974-
975- // should leave non-bind mounts
976- undoMounts(true)
977-
978- c.Assert(mounts, DeepEquals, mountEntryArray{
979- mountEntry{
980- source: "/dev/sda4",
981- target: mountTarget,
982- options: "",
983- bindMount: false,
984- },
985- })
986-
987- // should unmount everything
988- undoMounts(false)
989-
990- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
991-}
992-
993-func mockRunLsblkNoSnappy() (output []string, err error) {
994- dualData := `
995-NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
996-NAME="sda1" LABEL="meep" PKNAME="sda" MOUNTPOINT="/"
997-NAME="sr0" LABEL="" PKNAME="" MOUNTPOINT=""
998-`
999- return strings.Split(dualData, "\n"), err
1000-}
1001-
1002-func (s *PartitionTestSuite) TestSnappyNoSnappyPartitions(c *C) {
1003- runLsblk = mockRunLsblkNoSnappy
1004-
1005- p := New()
1006- err := p.getPartitionDetails()
1007- c.Assert(err, Equals, ErrPartitionDetection)
1008-
1009- c.Assert(p.dualRootPartitions(), Equals, false)
1010- c.Assert(p.singleRootPartition(), Equals, false)
1011-
1012- c.Assert(p.rootPartition(), IsNil)
1013- c.Assert(p.bootPartition(), IsNil)
1014- c.Assert(p.writablePartition(), IsNil)
1015- c.Assert(p.otherRootPartition(), IsNil)
1016 }
1017
1018 // mock bootloader for the tests
1019 type mockBootloader struct {
1020- ToggleRootFSCalled bool
1021- HandleAssetsCalled bool
1022- MarkCurrentBootSuccessfulCalled bool
1023- SyncBootFilesCalled bool
1024+ bootvars map[string]string
1025 }
1026
1027-func (b *mockBootloader) Name() bootloaderName {
1028- return "mocky"
1029-}
1030-func (b *mockBootloader) ToggleRootFS(otherRootfs string) error {
1031- b.ToggleRootFSCalled = true
1032- return nil
1033-}
1034-func (b *mockBootloader) SyncBootFiles(bootAssets map[string]string) error {
1035- b.SyncBootFilesCalled = true
1036- return nil
1037-}
1038-func (b *mockBootloader) HandleAssets() error {
1039- b.HandleAssetsCalled = true
1040- return nil
1041-}
1042 func (b *mockBootloader) GetBootVar(name string) (string, error) {
1043- return "", nil
1044-}
1045-func (b *mockBootloader) GetNextBootRootFSName() (string, error) {
1046- return "", nil
1047-}
1048-func (b *mockBootloader) MarkCurrentBootSuccessful(currentRootfs string) error {
1049- b.MarkCurrentBootSuccessfulCalled = true
1050+ return b.bootvars[name], nil
1051+}
1052+func (b *mockBootloader) SetBootVar(key, value string) error {
1053+ b.bootvars[key] = value
1054 return nil
1055 }
1056 func (b *mockBootloader) BootDir() string {
1057- return ""
1058-}
1059-
1060-func (s *PartitionTestSuite) TestToggleBootloaderRootfs(c *C) {
1061- runCommand = mockRunCommand
1062- b := &mockBootloader{}
1063- bootloader = func(p *Partition) (bootLoader, error) {
1064- return b, nil
1065- }
1066-
1067- p := New()
1068- c.Assert(c, NotNil)
1069-
1070- err := p.toggleBootloaderRootfs()
1071- c.Assert(err, IsNil)
1072- c.Assert(b.ToggleRootFSCalled, Equals, true)
1073- c.Assert(b.HandleAssetsCalled, Equals, true)
1074-
1075- p.unmountOtherRootfs()
1076- c.Assert(mounts, DeepEquals, mountEntryArray(nil))
1077-}
1078-
1079-func (s *PartitionTestSuite) TestMarkBootSuccessful(c *C) {
1080- runCommand = mockRunCommand
1081- b := &mockBootloader{}
1082- bootloader = func(p *Partition) (bootLoader, error) {
1083- return b, nil
1084- }
1085-
1086- p := New()
1087- c.Assert(c, NotNil)
1088-
1089- err := p.MarkBootSuccessful()
1090- c.Assert(err, IsNil)
1091- c.Assert(b.MarkCurrentBootSuccessfulCalled, Equals, true)
1092-}
1093-
1094-func (s *PartitionTestSuite) TestSyncBootFiles(c *C) {
1095- runCommand = mockRunCommand
1096- b := &mockBootloader{}
1097- bootloader = func(p *Partition) (bootLoader, error) {
1098- return b, nil
1099- }
1100-
1101- p := New()
1102- c.Assert(c, NotNil)
1103-
1104- err := p.SyncBootloaderFiles(nil)
1105- c.Assert(err, IsNil)
1106- c.Assert(b.SyncBootFilesCalled, Equals, true)
1107+ return "/boot/mock"
1108+}
1109+
1110+func mockRunCommand(args ...string) (err error) {
1111+ return err
1112+}
1113+
1114+func (s *BootloaderTestSuite) TestMarkBootSuccessful(c *C) {
1115+ runCommand = mockRunCommand
1116+ b := &mockBootloader{
1117+ bootvars: map[string]string{
1118+ "snappy_os": "os1",
1119+ "snappy_kernel": "k1",
1120+ },
1121+ }
1122+ new = func() (bootLoader, error) {
1123+ return b, nil
1124+ }
1125+
1126+ err := MarkBootSuccessful()
1127+ c.Assert(err, IsNil)
1128+ c.Assert(b.bootvars["snappy_good_os"], Equals, "os1")
1129+ c.Assert(b.bootvars["snappy_good_kernel"], Equals, "k1")
1130+ c.Assert(b.bootvars["snappy_mode"], Equals, "regular")
1131+ c.Assert(b.bootvars["snappy_trial_boot"], Equals, "0")
1132+}
1133+
1134+func (s *BootloaderTestSuite) TestSetBootVar(c *C) {
1135+ runCommand = mockRunCommand
1136+ b := &mockBootloader{
1137+ bootvars: map[string]string{},
1138+ }
1139+ new = func() (bootLoader, error) {
1140+ return b, nil
1141+ }
1142+
1143+ err := SetBootVar("foo", "bar")
1144+ c.Assert(err, IsNil)
1145+ c.Assert(b.bootvars["foo"], Equals, "bar")
1146+}
1147+
1148+func (s *BootloaderTestSuite) TestGetBootVar(c *C) {
1149+ runCommand = mockRunCommand
1150+ b := &mockBootloader{
1151+ bootvars: map[string]string{"baz": "foobar"},
1152+ }
1153+ new = func() (bootLoader, error) {
1154+ return b, nil
1155+ }
1156+
1157+ v, err := GetBootVar("baz")
1158+ c.Assert(err, IsNil)
1159+ c.Assert(v, Equals, "foobar")
1160+}
1161+
1162+func (s *BootloaderTestSuite) TestGetBootDir(c *C) {
1163+ runCommand = mockRunCommand
1164+ b := &mockBootloader{
1165+ bootvars: map[string]string{"baz": "foobar"},
1166+ }
1167+ new = func() (bootLoader, error) {
1168+ return b, nil
1169+ }
1170+
1171+ d := Dir()
1172+ c.Assert(d, Equals, "/boot/mock")
1173+}
1174+
1175+func (s *BootloaderTestSuite) TestBootloaderIsGrub(c *C) {
1176+ s.makeFakeGrubEnv(c)
1177+
1178+ g, err := new()
1179+ c.Assert(err, IsNil)
1180+ c.Assert(g, FitsTypeOf, &grub{})
1181+}
1182+
1183+func (s *BootloaderTestSuite) TestBootloaderIsUboot(c *C) {
1184+ s.makeFakeUbootEnv(c)
1185+
1186+ u, err := new()
1187+ c.Assert(err, IsNil)
1188+ c.Assert(u, FitsTypeOf, &uboot{})
1189 }
1190
1191=== modified file 'bootloader/bootloader_uboot.go'
1192--- partition/bootloader_uboot.go 2015-07-24 12:00:01 +0000
1193+++ bootloader/bootloader_uboot.go 2015-09-03 08:14:56 +0000
1194@@ -17,252 +17,61 @@
1195 *
1196 */
1197
1198-package partition
1199+package bootloader
1200
1201 import (
1202- "bufio"
1203- "bytes"
1204- "fmt"
1205- "os"
1206- "strings"
1207-
1208 "launchpad.net/snappy/helpers"
1209
1210- "github.com/mvo5/goconfigparser"
1211 "github.com/mvo5/uboot-go/uenv"
1212 )
1213
1214 const (
1215- bootloaderUbootDirReal = "/boot/uboot"
1216- bootloaderUbootConfigFileReal = "/boot/uboot/uEnv.txt"
1217-
1218- // File created by u-boot itself when
1219- // bootloaderBootmodeTry == "try" which the
1220- // successfully booted system must remove to flag to u-boot that
1221- // this partition is "good".
1222- bootloaderUbootStampFileReal = "/boot/uboot/snappy-stamp.txt"
1223-
1224- // DEPRECATED:
1225- bootloaderUbootEnvFileReal = "/boot/uboot/snappy-system.txt"
1226-
1227- // the real uboot env
1228+ bootloaderUbootDirReal = "/boot/uboot"
1229 bootloaderUbootFwEnvFileReal = "/boot/uboot/uboot.env"
1230 )
1231
1232 // var to make it testable
1233 var (
1234- bootloaderUbootDir = bootloaderUbootDirReal
1235- bootloaderUbootConfigFile = bootloaderUbootConfigFileReal
1236- bootloaderUbootStampFile = bootloaderUbootStampFileReal
1237- bootloaderUbootEnvFile = bootloaderUbootEnvFileReal
1238- bootloaderUbootFwEnvFile = bootloaderUbootFwEnvFileReal
1239-
1240- atomicWriteFile = helpers.AtomicWriteFile
1241+ bootloaderUbootDir = bootloaderUbootDirReal
1242+ bootloaderUbootFwEnvFile = bootloaderUbootFwEnvFileReal
1243 )
1244
1245-const bootloaderNameUboot bootloaderName = "u-boot"
1246-
1247 type uboot struct {
1248- bootloaderType
1249-}
1250-
1251-// Stores a Name and a Value to be added as a name=value pair in a file.
1252-// TODO convert to map
1253-type configFileChange struct {
1254- Name string
1255- Value string
1256-}
1257-
1258-var setBootVar = func(name, value string) error { return nil }
1259-var getBootVar = func(name string) (string, error) { return "", nil }
1260+}
1261
1262 // newUboot create a new Uboot bootloader object
1263-func newUboot(partition *Partition) bootLoader {
1264- if !helpers.FileExists(bootloaderUbootConfigFile) {
1265- return nil
1266- }
1267-
1268- b := newBootLoader(partition, bootloaderUbootDir)
1269- if b == nil {
1270- return nil
1271- }
1272- u := uboot{bootloaderType: *b}
1273-
1274- if helpers.FileExists(bootloaderUbootFwEnvFile) {
1275- setBootVar = setBootVarFwEnv
1276- getBootVar = getBootVarFwEnv
1277- } else {
1278- setBootVar = setBootVarLegacy
1279- getBootVar = getBootVarLegacy
1280- }
1281-
1282- return &u
1283-}
1284-
1285-func (u *uboot) Name() bootloaderName {
1286- return bootloaderNameUboot
1287-}
1288-
1289-func (u *uboot) ToggleRootFS(otherRootfs string) (err error) {
1290- if err := setBootVar(bootloaderRootfsVar, string(otherRootfs)); err != nil {
1291- return err
1292- }
1293-
1294- return setBootVar(bootloaderBootmodeVar, bootloaderBootmodeTry)
1295-}
1296-
1297-func getBootVarLegacy(name string) (value string, err error) {
1298- cfg := goconfigparser.New()
1299- cfg.AllowNoSectionHeader = true
1300- if err := cfg.ReadFile(bootloaderUbootEnvFile); err != nil {
1301- return "", nil
1302- }
1303-
1304- return cfg.Get("", name)
1305-}
1306-
1307-func setBootVarLegacy(name, value string) error {
1308- curVal, err := getBootVarLegacy(name)
1309- if err == nil && curVal == value {
1310- return nil
1311- }
1312-
1313- changes := []configFileChange{
1314- configFileChange{
1315- Name: name,
1316- Value: value,
1317- },
1318- }
1319-
1320- return modifyNameValueFile(bootloaderUbootEnvFile, changes)
1321-}
1322-
1323-func setBootVarFwEnv(name, value string) error {
1324+func newUboot() bootLoader {
1325+ if !helpers.FileExists(bootloaderUbootFwEnvFile) {
1326+ return nil
1327+ }
1328+
1329+ return &uboot{}
1330+}
1331+
1332+func (u *uboot) GetBootVar(name string) (value string, err error) {
1333+ env, err := uenv.Open(bootloaderUbootFwEnvFile)
1334+ if err != nil {
1335+ return "", err
1336+ }
1337+
1338+ return env.Get(name), nil
1339+}
1340+
1341+func (u *uboot) SetBootVar(key, value string) error {
1342 env, err := uenv.Open(bootloaderUbootFwEnvFile)
1343 if err != nil {
1344 return err
1345 }
1346
1347 // already set, nothing to do
1348- if env.Get(name) == value {
1349+ if env.Get(key) == value {
1350 return nil
1351 }
1352
1353- env.Set(name, value)
1354+ env.Set(key, value)
1355 return env.Save()
1356 }
1357
1358-func getBootVarFwEnv(name string) (string, error) {
1359- env, err := uenv.Open(bootloaderUbootFwEnvFile)
1360- if err != nil {
1361- return "", err
1362- }
1363-
1364- return env.Get(name), nil
1365-}
1366-
1367-func (u *uboot) GetBootVar(name string) (value string, err error) {
1368- return getBootVar(name)
1369-}
1370-
1371-func (u *uboot) GetNextBootRootFSName() (label string, err error) {
1372- value, err := u.GetBootVar(bootloaderRootfsVar)
1373- if err != nil {
1374- // should never happen
1375- return "", err
1376- }
1377-
1378- return value, nil
1379-}
1380-
1381-// FIXME: this is super similar to grub now, refactor to extract the
1382-// common code
1383-func (u *uboot) MarkCurrentBootSuccessful(currentRootfs string) error {
1384- // Clear the variable set on boot to denote a good boot.
1385- if err := setBootVar(bootloaderTrialBootVar, "0"); err != nil {
1386- return err
1387- }
1388-
1389- if err := setBootVar(bootloaderRootfsVar, currentRootfs); err != nil {
1390- return err
1391- }
1392-
1393- if err := setBootVar(bootloaderBootmodeVar, bootloaderBootmodeSuccess); err != nil {
1394- return err
1395- }
1396-
1397- // legacy support, does not error if the file is not there
1398- return os.RemoveAll(bootloaderUbootStampFile)
1399-}
1400-
1401 func (u *uboot) BootDir() string {
1402 return bootloaderUbootDir
1403 }
1404-
1405-// Rewrite the specified file, applying the specified set of changes.
1406-// Lines not in the changes slice are left alone.
1407-// If the original file does not contain any of the name entries (from
1408-// the corresponding configFileChange objects), those entries are
1409-// appended to the file.
1410-//
1411-// FIXME: put into utils package
1412-// FIXME: improve logic
1413-func modifyNameValueFile(path string, changes []configFileChange) error {
1414- var updated []configFileChange
1415-
1416- // we won't write to a file if we don't need to.
1417- updateNeeded := false
1418-
1419- file, err := os.Open(path)
1420- if err != nil {
1421- return err
1422- }
1423- defer file.Close()
1424-
1425- buf := bytes.NewBuffer(nil)
1426- scanner := bufio.NewScanner(file)
1427- for scanner.Scan() {
1428- line := scanner.Text()
1429- for _, change := range changes {
1430- if strings.HasPrefix(line, fmt.Sprintf("%s=", change.Name)) {
1431- value := strings.SplitN(line, "=", 2)[1]
1432- // updated is used later to see if you had the originally requested
1433- // value.
1434- updated = append(updated, change)
1435- if value != change.Value {
1436- line = fmt.Sprintf("%s=%s", change.Name, change.Value)
1437- updateNeeded = true
1438- }
1439- }
1440- }
1441- if _, err := fmt.Fprintln(buf, line); err != nil {
1442- return err
1443- }
1444- }
1445-
1446- for _, change := range changes {
1447- got := false
1448- for _, update := range updated {
1449- if update.Name == change.Name {
1450- got = true
1451- break
1452- }
1453- }
1454-
1455- if !got {
1456- updateNeeded = true
1457-
1458- // name/value pair did not exist in original
1459- // file, so append
1460- if _, err := fmt.Fprintf(buf, "%s=%s\n", change.Name, change.Value); err != nil {
1461- return err
1462- }
1463- }
1464- }
1465-
1466- if updateNeeded {
1467- return atomicWriteFile(path, buf.Bytes(), 0644)
1468- }
1469-
1470- return nil
1471-}
1472
1473=== modified file 'bootloader/bootloader_uboot_test.go'
1474--- partition/bootloader_uboot_test.go 2015-07-24 11:58:04 +0000
1475+++ bootloader/bootloader_uboot_test.go 2015-09-03 08:14:56 +0000
1476@@ -17,17 +17,14 @@
1477 *
1478 */
1479
1480-package partition
1481+package bootloader
1482
1483 import (
1484- "io/ioutil"
1485 "os"
1486- "path/filepath"
1487 "strings"
1488 "time"
1489
1490 . "gopkg.in/check.v1"
1491- "launchpad.net/snappy/helpers"
1492
1493 "github.com/mvo5/uboot-go/uenv"
1494 )
1495@@ -66,319 +63,84 @@
1496 snappy_boot=if test "${snappy_mode}" = "try"; then if test -e mmc ${bootpart} ${snappy_stamp}; then if test "${snappy_ab}" = "a"; then setenv snappy_ab "b"; else setenv snappy_ab "a"; fi; else fatwrite mmc ${mmcdev}:${mmcpart} 0x0 ${snappy_stamp} 0; fi; fi; run loadfiles; setenv mmcroot /dev/disk/by-label/system-${snappy_ab} ${snappy_cmdline}; run mmcargs; bootz ${loadaddr} ${initrd_addr}:${initrd_size} ${fdtaddr}
1497 `
1498
1499-func (s *PartitionTestSuite) makeFakeUbootEnv(c *C) {
1500+func (s *BootloaderTestSuite) makeFakeUbootEnv(c *C) {
1501 err := os.MkdirAll(bootloaderUbootDir, 0755)
1502 c.Assert(err, IsNil)
1503
1504- // this file just needs to exist
1505- err = ioutil.WriteFile(bootloaderUbootConfigFile, []byte(""), 0644)
1506+ env, err := uenv.Create(bootloaderUbootFwEnvFile, 4096)
1507 c.Assert(err, IsNil)
1508-
1509- // this file needs specific data
1510- err = ioutil.WriteFile(bootloaderUbootEnvFile, []byte(fakeUbootEnvData), 0644)
1511+ env.Set("snappy_ab", "a")
1512+ env.Set("snappy_mode", "regular")
1513+ err = env.Save()
1514 c.Assert(err, IsNil)
1515 }
1516
1517-func (s *PartitionTestSuite) TestNewUbootNoUbootReturnsNil(c *C) {
1518- partition := New()
1519- u := newUboot(partition)
1520+func (s *BootloaderTestSuite) TestNewUbootNoUbootReturnsNil(c *C) {
1521+ u := newUboot()
1522 c.Assert(u, IsNil)
1523 }
1524
1525-func (s *PartitionTestSuite) TestNewUboot(c *C) {
1526- s.makeFakeUbootEnv(c)
1527-
1528- partition := New()
1529- u := newUboot(partition)
1530- c.Assert(u, NotNil)
1531- c.Assert(u.Name(), Equals, bootloaderNameUboot)
1532-}
1533-
1534-func (s *PartitionTestSuite) TestUbootGetBootVar(c *C) {
1535- s.makeFakeUbootEnv(c)
1536-
1537- partition := New()
1538- u := newUboot(partition)
1539-
1540- nextBoot, err := u.GetBootVar(bootloaderRootfsVar)
1541- c.Assert(err, IsNil)
1542- // the https://developer.ubuntu.com/en/snappy/porting guide says
1543- // we always use the short names
1544- c.Assert(nextBoot, Equals, "a")
1545-
1546- // ensure that nextBootIsOther works too
1547- c.Assert(partition.IsNextBootOther(), Equals, false)
1548-}
1549-
1550-func (s *PartitionTestSuite) TestUbootToggleRootFS(c *C) {
1551- s.makeFakeUbootEnv(c)
1552-
1553- partition := New()
1554- u := newUboot(partition)
1555- c.Assert(u, NotNil)
1556-
1557- err := u.ToggleRootFS("b")
1558- c.Assert(err, IsNil)
1559-
1560- nextBoot, err := u.GetBootVar(bootloaderRootfsVar)
1561- c.Assert(err, IsNil)
1562- c.Assert(nextBoot, Equals, "b")
1563-
1564- // ensure that nextBootIsOther works too
1565- c.Assert(partition.IsNextBootOther(), Equals, true)
1566-}
1567-
1568-func (s *PartitionTestSuite) TestUbootGetEnvVar(c *C) {
1569- s.makeFakeUbootEnv(c)
1570-
1571- partition := New()
1572- u := newUboot(partition)
1573+func (s *BootloaderTestSuite) TestNewUboot(c *C) {
1574+ s.makeFakeUbootEnv(c)
1575+
1576+ u := newUboot()
1577+ c.Assert(u, NotNil)
1578+}
1579+
1580+func (s *BootloaderTestSuite) TestUbootGetEnvVar(c *C) {
1581+ s.makeFakeUbootEnv(c)
1582+
1583+ u := newUboot()
1584 c.Assert(u, NotNil)
1585
1586 v, err := u.GetBootVar(bootloaderBootmodeVar)
1587 c.Assert(err, IsNil)
1588 c.Assert(v, Equals, "regular")
1589
1590- v, err = u.GetBootVar(bootloaderRootfsVar)
1591+ v, err = u.GetBootVar("snappy_ab")
1592 c.Assert(err, IsNil)
1593 c.Assert(v, Equals, "a")
1594 }
1595
1596-func (s *PartitionTestSuite) TestGetBootloaderWithUboot(c *C) {
1597- s.makeFakeUbootEnv(c)
1598- p := New()
1599- bootloader, err := bootloader(p)
1600- c.Assert(err, IsNil)
1601- c.Assert(bootloader.Name(), Equals, bootloaderNameUboot)
1602-}
1603-
1604-func makeMockAssetsDir(c *C) {
1605- for _, f := range []string{"assets/vmlinuz", "assets/initrd.img", "assets/dtbs/foo.dtb", "assets/dtbs/bar.dtb"} {
1606- p := filepath.Join(cacheDir, f)
1607- os.MkdirAll(filepath.Dir(p), 0755)
1608- err := ioutil.WriteFile(p, []byte(f), 0644)
1609- c.Assert(err, IsNil)
1610- }
1611-}
1612-
1613-func (s *PartitionTestSuite) TestHandleAssets(c *C) {
1614- s.makeFakeUbootEnv(c)
1615- p := New()
1616- bootloader, err := bootloader(p)
1617- c.Assert(err, IsNil)
1618-
1619- // mock the hardwareYaml and the cacheDir
1620- hardwareSpecFile = makeHardwareYaml(c, "")
1621- cacheDir = c.MkDir()
1622-
1623- // create mock assets/
1624- makeMockAssetsDir(c)
1625-
1626- // run the handle assets code
1627- err = bootloader.HandleAssets()
1628- c.Assert(err, IsNil)
1629-
1630- // ensure the files are where we expect them
1631- otherBootPath := bootloader.(*uboot).otherBootPath
1632- for _, f := range []string{"vmlinuz", "initrd.img", "dtbs/foo.dtb", "dtbs/bar.dtb"} {
1633- content, err := ioutil.ReadFile(filepath.Join(otherBootPath, f))
1634- c.Assert(err, IsNil)
1635- // match content
1636- c.Assert(strings.HasSuffix(string(content), f), Equals, true)
1637- }
1638-
1639- // ensure nothing left behind
1640- c.Assert(helpers.FileExists(filepath.Join(cacheDir, "assets")), Equals, false)
1641- c.Assert(helpers.FileExists(hardwareSpecFile), Equals, false)
1642-}
1643-
1644-func (s *PartitionTestSuite) TestHandleAssetsVerifyBootloader(c *C) {
1645- s.makeFakeUbootEnv(c)
1646- p := New()
1647- bootloader, err := bootloader(p)
1648- c.Assert(err, IsNil)
1649-
1650- // mock the hardwareYaml and the cacheDir
1651- hardwareSpecFile = makeHardwareYaml(c, "bootloader: grub")
1652- cacheDir = c.MkDir()
1653-
1654- err = bootloader.HandleAssets()
1655- c.Assert(err, NotNil)
1656-}
1657-
1658-func (s *PartitionTestSuite) TestHandleAssetsFailVerifyPartitionLayout(c *C) {
1659- s.makeFakeUbootEnv(c)
1660- p := New()
1661- bootloader, err := bootloader(p)
1662- c.Assert(err, IsNil)
1663-
1664- // mock the hardwareYaml and the cacheDir
1665- hardwareSpecFile = makeHardwareYaml(c, `
1666-bootloader: u-boot
1667-partition-layout: inplace
1668-`)
1669- err = bootloader.HandleAssets()
1670- c.Assert(err, NotNil)
1671-}
1672-
1673-func (s *PartitionTestSuite) TestHandleAssetsNoHardwareYaml(c *C) {
1674- s.makeFakeUbootEnv(c)
1675- hardwareSpecFile = filepath.Join(c.MkDir(), "non-existent.yaml")
1676-
1677- p := New()
1678- bootloader, err := bootloader(p)
1679- c.Assert(err, IsNil)
1680-
1681- c.Assert(bootloader.HandleAssets(), IsNil)
1682-}
1683-
1684-func (s *PartitionTestSuite) TestHandleAssetsBadHardwareYaml(c *C) {
1685- s.makeFakeUbootEnv(c)
1686- p := New()
1687- bootloader, err := bootloader(p)
1688- c.Assert(err, IsNil)
1689-
1690- hardwareSpecFile = makeHardwareYaml(c, `
1691-bootloader u-boot
1692-`)
1693-
1694- c.Assert(bootloader.HandleAssets(), NotNil)
1695-}
1696-
1697-func (s *PartitionTestSuite) TestUbootMarkCurrentBootSuccessful(c *C) {
1698- s.makeFakeUbootEnv(c)
1699-
1700- // To simulate what uboot does for a "try" mode boot, create a
1701- // stamp file. uboot will expect this file to be removed by
1702- // "snappy booted" if the system boots successfully. If this
1703- // file exists when uboot starts, it will know that the previous
1704- // boot failed, and will therefore toggle to the other rootfs.
1705- err := ioutil.WriteFile(bootloaderUbootStampFile, []byte(""), 0640)
1706- c.Assert(err, IsNil)
1707- c.Assert(helpers.FileExists(bootloaderUbootStampFile), Equals, true)
1708-
1709- partition := New()
1710- u := newUboot(partition)
1711- c.Assert(u, NotNil)
1712-
1713- // enter "try" mode so that we check to ensure that snappy
1714- // correctly modifies the snappy_mode variable from "try" to
1715- // "regular" to denote a good boot.
1716- err = u.ToggleRootFS("b")
1717- c.Assert(err, IsNil)
1718-
1719- c.Assert(helpers.FileExists(bootloaderUbootEnvFile), Equals, true)
1720- bytes, err := ioutil.ReadFile(bootloaderUbootEnvFile)
1721- c.Assert(err, IsNil)
1722- c.Assert(strings.Contains(string(bytes), "snappy_mode=try"), Equals, true)
1723- c.Assert(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, false)
1724- c.Assert(strings.Contains(string(bytes), "snappy_ab=b"), Equals, true)
1725-
1726- err = u.MarkCurrentBootSuccessful("b")
1727- c.Assert(err, IsNil)
1728-
1729- c.Assert(helpers.FileExists(bootloaderUbootStampFile), Equals, false)
1730- c.Assert(helpers.FileExists(bootloaderUbootEnvFile), Equals, true)
1731-
1732- bytes, err = ioutil.ReadFile(bootloaderUbootEnvFile)
1733- c.Assert(err, IsNil)
1734- c.Assert(strings.Contains(string(bytes), "snappy_mode=try"), Equals, false)
1735- c.Assert(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, true)
1736- c.Assert(strings.Contains(string(bytes), "snappy_ab=b"), Equals, true)
1737-}
1738-
1739-func (s *PartitionTestSuite) TestNoWriteNotNeeded(c *C) {
1740- s.makeFakeUbootEnv(c)
1741-
1742- atomiCall := false
1743- atomicWriteFile = func(a string, b []byte, c os.FileMode) error {
1744- atomiCall = true
1745- return helpers.AtomicWriteFile(a, b, c)
1746- }
1747-
1748- partition := New()
1749- u := newUboot(partition)
1750- c.Assert(u, NotNil)
1751-
1752- c.Check(u.MarkCurrentBootSuccessful("a"), IsNil)
1753- c.Assert(atomiCall, Equals, false)
1754-}
1755-
1756-func (s *PartitionTestSuite) TestWriteDueToMissingValues(c *C) {
1757- s.makeFakeUbootEnv(c)
1758-
1759- // this file needs specific data
1760- c.Assert(ioutil.WriteFile(bootloaderUbootEnvFile, []byte(""), 0644), IsNil)
1761-
1762- atomiCall := false
1763- atomicWriteFile = func(a string, b []byte, c os.FileMode) error {
1764- atomiCall = true
1765- return helpers.AtomicWriteFile(a, b, c)
1766- }
1767-
1768- partition := New()
1769- u := newUboot(partition)
1770- c.Assert(u, NotNil)
1771-
1772- c.Check(u.MarkCurrentBootSuccessful("a"), IsNil)
1773- c.Assert(atomiCall, Equals, true)
1774-
1775- bytes, err := ioutil.ReadFile(bootloaderUbootEnvFile)
1776- c.Assert(err, IsNil)
1777- c.Check(strings.Contains(string(bytes), "snappy_mode=try"), Equals, false)
1778- c.Check(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, true)
1779- c.Check(strings.Contains(string(bytes), "snappy_ab=a"), Equals, true)
1780-}
1781-
1782-func (s *PartitionTestSuite) TestUbootMarkCurrentBootSuccessfulFwEnv(c *C) {
1783- s.makeFakeUbootEnv(c)
1784-
1785- env, err := uenv.Create(bootloaderUbootFwEnvFile, 4096)
1786- c.Assert(err, IsNil)
1787- env.Set("snappy_ab", "b")
1788- env.Set("snappy_mode", "try")
1789- env.Set("snappy_trial_boot", "1")
1790- err = env.Save()
1791- c.Assert(err, IsNil)
1792-
1793- partition := New()
1794- u := newUboot(partition)
1795- c.Assert(u, NotNil)
1796-
1797- err = u.MarkCurrentBootSuccessful("b")
1798- c.Assert(err, IsNil)
1799-
1800- env, err = uenv.Open(bootloaderUbootFwEnvFile)
1801- c.Assert(err, IsNil)
1802- c.Assert(env.String(), Equals, "snappy_ab=b\nsnappy_mode=regular\nsnappy_trial_boot=0\n")
1803-}
1804-
1805-func (s *PartitionTestSuite) TestUbootSetEnvNoUselessWrites(c *C) {
1806- s.makeFakeUbootEnv(c)
1807-
1808- env, err := uenv.Create(bootloaderUbootFwEnvFile, 4096)
1809- c.Assert(err, IsNil)
1810- env.Set("snappy_ab", "b")
1811- env.Set("snappy_mode", "regular")
1812- err = env.Save()
1813- c.Assert(err, IsNil)
1814+func (s *BootloaderTestSuite) TestUbootSetEnvVar(c *C) {
1815+ s.makeFakeUbootEnv(c)
1816+
1817+ u := newUboot()
1818+ c.Assert(u, NotNil)
1819+
1820+ err := u.SetBootVar("snappy_os", "123")
1821+ c.Assert(err, IsNil)
1822+
1823+ v, err := u.GetBootVar("snappy_os")
1824+ c.Assert(err, IsNil)
1825+ c.Assert(v, Equals, "123")
1826+}
1827+
1828+func (s *BootloaderTestSuite) TestUbootSetEnvNoUselessWrites(c *C) {
1829+ s.makeFakeUbootEnv(c)
1830
1831 st, err := os.Stat(bootloaderUbootFwEnvFile)
1832 c.Assert(err, IsNil)
1833 time.Sleep(100 * time.Millisecond)
1834
1835- partition := New()
1836- u := newUboot(partition)
1837+ u := newUboot()
1838 c.Assert(u, NotNil)
1839
1840- err = setBootVar(bootloaderRootfsVar, "b")
1841+ err = u.SetBootVar("snappy_ab", "a")
1842 c.Assert(err, IsNil)
1843
1844- env, err = uenv.Open(bootloaderUbootFwEnvFile)
1845+ env, err := uenv.Open(bootloaderUbootFwEnvFile)
1846 c.Assert(err, IsNil)
1847- c.Assert(env.String(), Equals, "snappy_ab=b\nsnappy_mode=regular\n")
1848+ c.Assert(env.String(), Equals, "snappy_ab=a\nsnappy_mode=regular\n")
1849
1850 st2, err := os.Stat(bootloaderUbootFwEnvFile)
1851 c.Assert(err, IsNil)
1852 c.Assert(st.ModTime(), Equals, st2.ModTime())
1853 }
1854+
1855+func (s *BootloaderTestSuite) TestUbootBootdir(c *C) {
1856+ s.makeFakeUbootEnv(c)
1857+
1858+ u := newUboot()
1859+ c.Assert(strings.HasSuffix(u.BootDir(), "/boot/uboot"), Equals, true)
1860+}
1861
1862=== added file 'bootloader/utils.go'
1863--- bootloader/utils.go 1970-01-01 00:00:00 +0000
1864+++ bootloader/utils.go 2015-09-03 08:14:56 +0000
1865@@ -0,0 +1,63 @@
1866+// -*- Mode: Go; indent-tabs-mode: t -*-
1867+
1868+/*
1869+ * Copyright (C) 2014-2015 Canonical Ltd
1870+ *
1871+ * This program is free software: you can redistribute it and/or modify
1872+ * it under the terms of the GNU General Public License version 3 as
1873+ * published by the Free Software Foundation.
1874+ *
1875+ * This program is distributed in the hope that it will be useful,
1876+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1877+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1878+ * GNU General Public License for more details.
1879+ *
1880+ * You should have received a copy of the GNU General Public License
1881+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1882+ *
1883+ */
1884+
1885+package bootloader
1886+
1887+import (
1888+ "errors"
1889+ "fmt"
1890+ "os/exec"
1891+ "strings"
1892+)
1893+
1894+// FIXME: would it make sense to differenciate between launch errors and
1895+// exit code? (i.e. something like (returnCode, error) ?)
1896+func runCommandImpl(args ...string) (err error) {
1897+ if len(args) == 0 {
1898+ return errors.New("no command specified")
1899+ }
1900+
1901+ if out, err := exec.Command(args[0], args[1:]...).CombinedOutput(); err != nil {
1902+ cmdline := strings.Join(args, " ")
1903+ return fmt.Errorf("Failed to run command '%s': %s (%s)",
1904+ cmdline, out, err)
1905+ }
1906+ return nil
1907+}
1908+
1909+// Run the command specified by args
1910+// This is a var instead of a function to making mocking in the tests easier
1911+var runCommand = runCommandImpl
1912+
1913+// Run command specified by args and return the output
1914+func runCommandWithStdoutImpl(args ...string) (output string, err error) {
1915+ if len(args) == 0 {
1916+ return "", errors.New("no command specified")
1917+ }
1918+
1919+ bytes, err := exec.Command(args[0], args[1:]...).Output()
1920+ if err != nil {
1921+ return "", err
1922+ }
1923+
1924+ return string(bytes), err
1925+}
1926+
1927+// This is a var instead of a function to making mocking in the tests easier
1928+var runCommandWithStdout = runCommandWithStdoutImpl
1929
1930=== added file 'bootloader/utils_test.go'
1931--- bootloader/utils_test.go 1970-01-01 00:00:00 +0000
1932+++ bootloader/utils_test.go 2015-09-03 08:14:56 +0000
1933@@ -0,0 +1,47 @@
1934+// -*- Mode: Go; indent-tabs-mode: t -*-
1935+
1936+/*
1937+ * Copyright (C) 2014-2015 Canonical Ltd
1938+ *
1939+ * This program is free software: you can redistribute it and/or modify
1940+ * it under the terms of the GNU General Public License version 3 as
1941+ * published by the Free Software Foundation.
1942+ *
1943+ * This program is distributed in the hope that it will be useful,
1944+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1945+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1946+ * GNU General Public License for more details.
1947+ *
1948+ * You should have received a copy of the GNU General Public License
1949+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1950+ *
1951+ */
1952+
1953+package bootloader
1954+
1955+import (
1956+ . "gopkg.in/check.v1"
1957+)
1958+
1959+type UtilsTestSuite struct {
1960+}
1961+
1962+var _ = Suite(&UtilsTestSuite{})
1963+
1964+func (s *UtilsTestSuite) SetUpTest(c *C) {
1965+}
1966+
1967+func (s *UtilsTestSuite) TestRunCommand(c *C) {
1968+ err := runCommandImpl("false")
1969+ c.Assert(err, NotNil)
1970+
1971+ err = runCommandImpl("no-such-command")
1972+ c.Assert(err, NotNil)
1973+}
1974+
1975+func (s *UtilsTestSuite) TestRunCommandWithStdout(c *C) {
1976+ runCommandWithStdout = runCommandWithStdoutImpl
1977+ output, err := runCommandWithStdout("sh", "-c", "printf 'foo\nbar'")
1978+ c.Assert(err, IsNil)
1979+ c.Assert(output, DeepEquals, "foo\nbar")
1980+}
1981
1982=== modified file 'cmd/snappy/cmd_booted.go'
1983--- cmd/snappy/cmd_booted.go 2015-06-15 07:38:57 +0000
1984+++ cmd/snappy/cmd_booted.go 2015-09-03 08:14:56 +0000
1985@@ -20,9 +20,8 @@
1986 package main
1987
1988 import (
1989+ "launchpad.net/snappy/bootloader"
1990 "launchpad.net/snappy/logger"
1991- "launchpad.net/snappy/pkg"
1992- "launchpad.net/snappy/snappy"
1993 )
1994
1995 type cmdBooted struct {
1996@@ -43,10 +42,5 @@
1997 }
1998
1999 func (x *cmdBooted) doBooted() error {
2000- parts, err := snappy.ActiveSnapsByType(pkg.TypeCore)
2001- if err != nil {
2002- return err
2003- }
2004-
2005- return parts[0].(*snappy.SystemImagePart).MarkBootSuccessful()
2006+ return bootloader.MarkBootSuccessful()
2007 }
2008
2009=== modified file 'gen-coverage.sh'
2010--- gen-coverage.sh 2015-06-09 13:02:49 +0000
2011+++ gen-coverage.sh 2015-09-03 08:14:56 +0000
2012@@ -10,8 +10,8 @@
2013
2014 (cd snappy &&
2015 $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-snappy.html)
2016-(cd partition &&
2017- $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-partition.html)
2018+(cd bootloader &&
2019+ $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-bootloader.html)
2020 (cd logger &&
2021 $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-logger.html)
2022 (cd helpers &&
2023
2024=== removed file 'partition/assets.go'
2025--- partition/assets.go 2015-06-11 13:08:19 +0000
2026+++ partition/assets.go 1970-01-01 00:00:00 +0000
2027@@ -1,87 +0,0 @@
2028-// -*- Mode: Go; indent-tabs-mode: t -*-
2029-
2030-/*
2031- * Copyright (C) 2014-2015 Canonical Ltd
2032- *
2033- * This program is free software: you can redistribute it and/or modify
2034- * it under the terms of the GNU General Public License version 3 as
2035- * published by the Free Software Foundation.
2036- *
2037- * This program is distributed in the hope that it will be useful,
2038- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2039- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2040- * GNU General Public License for more details.
2041- *
2042- * You should have received a copy of the GNU General Public License
2043- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2044- *
2045- */
2046-
2047-package partition
2048-
2049-import (
2050- "errors"
2051- "io/ioutil"
2052- "os"
2053- "path/filepath"
2054-
2055- "gopkg.in/yaml.v2"
2056-)
2057-
2058-// Representation of the yaml in the hardwareSpecFile
2059-type hardwareSpecType struct {
2060- Kernel string `yaml:"kernel"`
2061- Initrd string `yaml:"initrd"`
2062- DtbDir string `yaml:"dtbs"`
2063- PartitionLayout string `yaml:"partition-layout"`
2064- Bootloader bootloaderName `yaml:"bootloader"`
2065-}
2066-
2067-var (
2068- // ErrNoHardwareYaml is returned when no hardware yaml is found in
2069- // the update, this means that there is nothing to process with regards
2070- // to device parts.
2071- ErrNoHardwareYaml = errors.New("no hardware.yaml")
2072-
2073- // Declarative specification of the type of system which specifies such
2074- // details as:
2075- //
2076- // - the location of initrd+kernel within the system-image archive.
2077- // - the location of hardware-specific .dtb files within the
2078- // system-image archive.
2079- // - the type of bootloader that should be used for this system.
2080- // - expected system partition layout (single or dual rootfs's).
2081- hardwareSpecFileReal = filepath.Join(cacheDir, "hardware.yaml")
2082-
2083- // useful to override in the tests
2084- hardwareSpecFile = hardwareSpecFileReal
2085-
2086- // Directory that _may_ get automatically created on unpack that
2087- // contains updated hardware-specific boot assets (such as initrd,
2088- // kernel)
2089- assetsDir = filepath.Join(cacheDir, "assets")
2090-
2091- // Directory that _may_ get automatically created on unpack that
2092- // contains updated hardware-specific assets that require flashing
2093- // to the disk (such as uBoot, MLO)
2094- flashAssetsDir = filepath.Join(cacheDir, "flashtool-assets")
2095-)
2096-
2097-func readHardwareSpec() (*hardwareSpecType, error) {
2098- var h hardwareSpecType
2099-
2100- data, err := ioutil.ReadFile(hardwareSpecFile)
2101- // if hardware.yaml does not exist it just means that there was no
2102- // device part in the update.
2103- if os.IsNotExist(err) {
2104- return nil, ErrNoHardwareYaml
2105- } else if err != nil {
2106- return nil, err
2107- }
2108-
2109- if err := yaml.Unmarshal([]byte(data), &h); err != nil {
2110- return nil, err
2111- }
2112-
2113- return &h, nil
2114-}
2115
2116=== removed file 'partition/assets_test.go'
2117--- partition/assets_test.go 2015-06-11 07:06:21 +0000
2118+++ partition/assets_test.go 1970-01-01 00:00:00 +0000
2119@@ -1,36 +0,0 @@
2120-// -*- Mode: Go; indent-tabs-mode: t -*-
2121-
2122-/*
2123- * Copyright (C) 2014-2015 Canonical Ltd
2124- *
2125- * This program is free software: you can redistribute it and/or modify
2126- * it under the terms of the GNU General Public License version 3 as
2127- * published by the Free Software Foundation.
2128- *
2129- * This program is distributed in the hope that it will be useful,
2130- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2131- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2132- * GNU General Public License for more details.
2133- *
2134- * You should have received a copy of the GNU General Public License
2135- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2136- *
2137- */
2138-
2139-package partition
2140-
2141-import (
2142- . "gopkg.in/check.v1"
2143-)
2144-
2145-func (s *PartitionTestSuite) TestHardwareSpec(c *C) {
2146-
2147- hardwareSpecFile = makeHardwareYaml(c, "")
2148- hw, err := readHardwareSpec()
2149- c.Assert(err, IsNil)
2150- c.Assert(hw.Kernel, Equals, "assets/vmlinuz")
2151- c.Assert(hw.Initrd, Equals, "assets/initrd.img")
2152- c.Assert(hw.DtbDir, Equals, "assets/dtbs")
2153- c.Assert(hw.PartitionLayout, Equals, bootloaderSystemAB)
2154- c.Assert(hw.Bootloader, Equals, bootloaderNameUboot)
2155-}
2156
2157=== removed file 'partition/dirs.go'
2158--- partition/dirs.go 2015-06-11 08:05:14 +0000
2159+++ partition/dirs.go 1970-01-01 00:00:00 +0000
2160@@ -1,41 +0,0 @@
2161-// -*- Mode: Go; indent-tabs-mode: t -*-
2162-
2163-/*
2164- * Copyright (C) 2014-2015 Canonical Ltd
2165- *
2166- * This program is free software: you can redistribute it and/or modify
2167- * it under the terms of the GNU General Public License version 3 as
2168- * published by the Free Software Foundation.
2169- *
2170- * This program is distributed in the hope that it will be useful,
2171- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2172- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2173- * GNU General Public License for more details.
2174- *
2175- * You should have received a copy of the GNU General Public License
2176- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2177- *
2178- */
2179-
2180-package partition
2181-
2182-import (
2183- "path/filepath"
2184-)
2185-
2186-// The full path to the cache directory, which is used as a
2187-// scratch pad, for downloading new images to and bind mounting the
2188-// rootfs.
2189-const cacheDirReal = "/writable/cache"
2190-
2191-var (
2192- // useful for overwriting in the tests
2193- cacheDir = cacheDirReal
2194-
2195- // Directory to mount writable root filesystem below the cache
2196- // diretory.
2197- mountTargetReal = filepath.Join(cacheDir, "system")
2198-
2199- // useful to override in tests
2200- mountTarget = mountTargetReal
2201-)
2202
2203=== removed file 'partition/mount.go'
2204--- partition/mount.go 2015-06-11 13:08:19 +0000
2205+++ partition/mount.go 1970-01-01 00:00:00 +0000
2206@@ -1,153 +0,0 @@
2207-// -*- Mode: Go; indent-tabs-mode: t -*-
2208-
2209-/*
2210- * Copyright (C) 2014-2015 Canonical Ltd
2211- *
2212- * This program is free software: you can redistribute it and/or modify
2213- * it under the terms of the GNU General Public License version 3 as
2214- * published by the Free Software Foundation.
2215- *
2216- * This program is distributed in the hope that it will be useful,
2217- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2218- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2219- * GNU General Public License for more details.
2220- *
2221- * You should have received a copy of the GNU General Public License
2222- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2223- *
2224- */
2225-
2226-package partition
2227-
2228-import (
2229- "fmt"
2230- "sort"
2231-)
2232-
2233-// MountOption represents how the partition should be mounted, currently
2234-// RO (read-only) and RW (read-write) are supported
2235-type MountOption int
2236-
2237-const (
2238- // RO mounts the partition read-only
2239- RO MountOption = iota
2240- // RW mounts the partition read-only
2241- RW
2242-)
2243-
2244-// mountEntry represents a mount this package has created.
2245-type mountEntry struct {
2246- source string
2247- target string
2248-
2249- options string
2250-
2251- // true if target refers to a bind mount. We could derive this
2252- // from options, but this field saves the effort.
2253- bindMount bool
2254-}
2255-
2256-// mountEntryArray represents an array of mountEntry objects.
2257-type mountEntryArray []mountEntry
2258-
2259-// current mounts that this package has created.
2260-var mounts mountEntryArray
2261-
2262-// Len is part of the sort interface, required to allow sort to work
2263-// with an array of Mount objects.
2264-func (mounts mountEntryArray) Len() int {
2265- return len(mounts)
2266-}
2267-
2268-// Less is part of the sort interface, required to allow sort to work
2269-// with an array of Mount objects.
2270-func (mounts mountEntryArray) Less(i, j int) bool {
2271- return mounts[i].target < mounts[j].target
2272-}
2273-
2274-// Swap is part of the sort interface, required to allow sort to work
2275-// with an array of Mount objects.
2276-func (mounts mountEntryArray) Swap(i, j int) {
2277- mounts[i], mounts[j] = mounts[j], mounts[i]
2278-}
2279-
2280-// removeMountByTarget removes the Mount specified by the target from
2281-// the global mounts array.
2282-func removeMountByTarget(mnts mountEntryArray, target string) (results mountEntryArray) {
2283-
2284- for _, m := range mnts {
2285- if m.target != target {
2286- results = append(results, m)
2287- }
2288- }
2289-
2290- return results
2291-}
2292-
2293-// undoMounts unmounts all mounts this package has mounted optionally
2294-// only unmounting bind mounts and leaving all remaining mounts.
2295-func undoMounts(bindMountsOnly bool) error {
2296-
2297- mountsCopy := make(mountEntryArray, len(mounts), cap(mounts))
2298- copy(mountsCopy, mounts)
2299-
2300- // reverse sort to ensure unmounts are handled in the correct
2301- // order.
2302- sort.Sort(sort.Reverse(mountsCopy))
2303-
2304- // Iterate backwards since we want a reverse-sorted list of
2305- // mounts to ensure we can unmount in order.
2306- for _, mount := range mountsCopy {
2307- if bindMountsOnly && !mount.bindMount {
2308- continue
2309- }
2310-
2311- if err := unmountAndRemoveFromGlobalMountList(mount.target); err != nil {
2312- return err
2313- }
2314- }
2315-
2316- return nil
2317-}
2318-
2319-// FIXME: use syscall.Mount() here
2320-func mount(source, target, options string) (err error) {
2321- var args []string
2322-
2323- args = append(args, "/bin/mount")
2324- if options != "" {
2325- args = append(args, fmt.Sprintf("-o%s", options))
2326- }
2327-
2328- args = append(args, source)
2329- args = append(args, target)
2330-
2331- return runCommand(args...)
2332-}
2333-
2334-// Mount the given directory and add it to the global mounts slice
2335-func mountAndAddToGlobalMountList(m mountEntry) (err error) {
2336-
2337- err = mount(m.source, m.target, m.options)
2338- if err == nil {
2339- mounts = append(mounts, m)
2340- }
2341-
2342- return err
2343-}
2344-
2345-// Unmount the given directory and remove it from the global "mounts" slice
2346-func unmountAndRemoveFromGlobalMountList(target string) (err error) {
2347- err = runCommand("/bin/umount", target)
2348- if err != nil {
2349- return err
2350-
2351- }
2352-
2353- results := removeMountByTarget(mounts, target)
2354-
2355- // Update global
2356- mounts = results
2357-
2358- return nil
2359-}
2360
2361=== removed file 'partition/mount_test.go'
2362--- partition/mount_test.go 2015-06-11 08:15:22 +0000
2363+++ partition/mount_test.go 1970-01-01 00:00:00 +0000
2364@@ -1,64 +0,0 @@
2365-// -*- Mode: Go; indent-tabs-mode: t -*-
2366-
2367-/*
2368- * Copyright (C) 2014-2015 Canonical Ltd
2369- *
2370- * This program is free software: you can redistribute it and/or modify
2371- * it under the terms of the GNU General Public License version 3 as
2372- * published by the Free Software Foundation.
2373- *
2374- * This program is distributed in the hope that it will be useful,
2375- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2376- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2377- * GNU General Public License for more details.
2378- *
2379- * You should have received a copy of the GNU General Public License
2380- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2381- *
2382- */
2383-
2384-package partition
2385-
2386-import (
2387- . "gopkg.in/check.v1"
2388-)
2389-
2390-func (s *PartitionTestSuite) TestMountEntryArray(c *C) {
2391- mea := mountEntryArray{}
2392-
2393- c.Assert(mea.Len(), Equals, 0)
2394-
2395- me := mountEntry{source: "/dev",
2396- target: "/dev",
2397- options: "bind",
2398- bindMount: true}
2399-
2400- mea = append(mea, me)
2401- c.Assert(mea.Len(), Equals, 1)
2402-
2403- me = mountEntry{source: "/foo",
2404- target: "/foo",
2405- options: "",
2406- bindMount: false}
2407-
2408- mea = append(mea, me)
2409- c.Assert(mea.Len(), Equals, 2)
2410-
2411- c.Assert(mea.Less(0, 1), Equals, true)
2412- c.Assert(mea.Less(1, 0), Equals, false)
2413-
2414- mea.Swap(0, 1)
2415- c.Assert(mea.Less(0, 1), Equals, false)
2416- c.Assert(mea.Less(1, 0), Equals, true)
2417-
2418- results := removeMountByTarget(mea, "invalid")
2419-
2420- // No change expected
2421- c.Assert(results, DeepEquals, mea)
2422-
2423- results = removeMountByTarget(mea, "/dev")
2424-
2425- c.Assert(len(results), Equals, 1)
2426- c.Assert(results[0], Equals, mountEntry{source: "/foo",
2427- target: "/foo", options: "", bindMount: false})
2428-}
2429
2430=== removed file 'partition/partition.go'
2431--- partition/partition.go 2015-07-15 07:53:43 +0000
2432+++ partition/partition.go 1970-01-01 00:00:00 +0000
2433@@ -1,622 +0,0 @@
2434-// -*- Mode: Go; indent-tabs-mode: t -*-
2435-
2436-/*
2437- * Copyright (C) 2014-2015 Canonical Ltd
2438- *
2439- * This program is free software: you can redistribute it and/or modify
2440- * it under the terms of the GNU General Public License version 3 as
2441- * published by the Free Software Foundation.
2442- *
2443- * This program is distributed in the hope that it will be useful,
2444- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2445- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2446- * GNU General Public License for more details.
2447- *
2448- * You should have received a copy of the GNU General Public License
2449- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2450- *
2451- */
2452-
2453-// Package partition manipulate snappy disk partitions
2454-package partition
2455-
2456-import (
2457- "errors"
2458- "fmt"
2459- "os"
2460- "os/signal"
2461- "path/filepath"
2462- "regexp"
2463- "strings"
2464- "sync"
2465- "syscall"
2466-
2467- "launchpad.net/snappy/logger"
2468-)
2469-
2470-const (
2471- // Name of writable user data partition label as created by
2472- // ubuntu-device-flash(1).
2473- writablePartitionLabel = "writable"
2474-
2475- // Name of primary root filesystem partition label as created by
2476- // ubuntu-device-flash(1).
2477- rootfsAlabel = "system-a"
2478-
2479- // Name of primary root filesystem partition label as created by
2480- // ubuntu-device-flash(1). Note that this partition will
2481- // only be present if this is an A/B upgrade system.
2482- rootfsBlabel = "system-b"
2483-
2484- // name of boot partition label as created by ubuntu-device-flash(1).
2485- bootPartitionLabel = "system-boot"
2486-
2487- // File creation mode used when any directories are created
2488- dirMode = 0750
2489-)
2490-
2491-var (
2492- // ErrBootloader is returned if the bootloader can not be determined
2493- ErrBootloader = errors.New("Unable to determine bootloader")
2494-
2495- // ErrPartitionDetection is returned if the partition type can not
2496- // be detected
2497- ErrPartitionDetection = errors.New("Failed to detect system type")
2498-
2499- // ErrNoDualPartition is returned if you try to use a dual
2500- // partition feature on a single partition
2501- ErrNoDualPartition = errors.New("No dual partition")
2502-)
2503-
2504-// Interface provides the interface to interact with a partition
2505-type Interface interface {
2506- ToggleNextBoot() error
2507-
2508- MarkBootSuccessful() error
2509- // FIXME: could we make SyncBootloaderFiles part of ToogleBootloader
2510- // to expose even less implementation details?
2511- SyncBootloaderFiles(bootAssets map[string]string) error
2512- IsNextBootOther() bool
2513-
2514- // run the function f with the otherRoot mounted
2515- RunWithOther(rw MountOption, f func(otherRoot string) (err error)) (err error)
2516-}
2517-
2518-// Partition is the type to interact with the partition
2519-type Partition struct {
2520- // all partitions
2521- partitions []blockDevice
2522-
2523- // just root partitions
2524- roots []string
2525-}
2526-
2527-type blockDevice struct {
2528- // label for partition
2529- name string
2530-
2531- // the last char of the partition label
2532- shortName string
2533-
2534- // full path to device on which partition exists
2535- // (for example "/dev/sda3")
2536- device string
2537-
2538- // full path to disk device (for example "/dev/sda")
2539- parentName string
2540-
2541- // mountpoint (or nil if not mounted)
2542- mountpoint string
2543-}
2544-
2545-var once sync.Once
2546-
2547-func init() {
2548- once.Do(setupSignalHandler)
2549-}
2550-
2551-func signalHandler(sig os.Signal) {
2552- err := undoMounts(false)
2553- if err != nil {
2554- logger.Noticef("Failed to unmount: %v", err)
2555- }
2556-}
2557-
2558-func setupSignalHandler() {
2559- ch := make(chan os.Signal, 1)
2560-
2561- // add the signals we care about
2562- signal.Notify(ch, os.Interrupt)
2563- signal.Notify(ch, syscall.SIGTERM)
2564-
2565- go func() {
2566- // block waiting for a signal
2567- sig := <-ch
2568-
2569- // handle it
2570- signalHandler(sig)
2571- os.Exit(1)
2572- }()
2573-}
2574-
2575-// Returns a list of root filesystem partition labels
2576-func rootPartitionLabels() []string {
2577- return []string{rootfsAlabel, rootfsBlabel}
2578-}
2579-
2580-// Returns a list of all recognised partition labels
2581-func allPartitionLabels() []string {
2582- var labels []string
2583-
2584- labels = rootPartitionLabels()
2585- labels = append(labels, bootPartitionLabel)
2586- labels = append(labels, writablePartitionLabel)
2587-
2588- return labels
2589-}
2590-
2591-var runLsblk = func() (out []string, err error) {
2592- output, err := runCommandWithStdout(
2593- "/bin/lsblk",
2594- "--ascii",
2595- "--output=NAME,LABEL,PKNAME,MOUNTPOINT",
2596- "--pairs")
2597- if err != nil {
2598- return out, err
2599- }
2600-
2601- return strings.Split(output, "\n"), nil
2602-}
2603-
2604-// Determine details of the recognised disk partitions
2605-// available on the system via lsblk
2606-func loadPartitionDetails() (partitions []blockDevice, err error) {
2607- recognised := allPartitionLabels()
2608-
2609- lines, err := runLsblk()
2610- if err != nil {
2611- return partitions, err
2612- }
2613- pattern := regexp.MustCompile(`(?:[^\s"]|"(?:[^"])*")+`)
2614-
2615- for _, line := range lines {
2616- fields := make(map[string]string)
2617-
2618- // split the line into 'NAME="quoted value"' fields
2619- matches := pattern.FindAllString(line, -1)
2620-
2621- for _, match := range matches {
2622- tmp := strings.Split(match, "=")
2623- name := tmp[0]
2624-
2625- // remove quotes
2626- value := strings.Trim(tmp[1], "\"")
2627-
2628- // store
2629- fields[name] = value
2630- }
2631-
2632- // Look for expected partition labels
2633- name, ok := fields["LABEL"]
2634- if !ok {
2635- continue
2636- }
2637-
2638- if name == "" || name == "\"\"" {
2639- continue
2640- }
2641-
2642- pos := stringInSlice(recognised, name)
2643- if pos < 0 {
2644- // ignore unrecognised partitions
2645- continue
2646- }
2647-
2648- // reconstruct full path to disk partition device
2649- device := fmt.Sprintf("/dev/%s", fields["NAME"])
2650-
2651- // FIXME: we should have a way to mock the "/dev" dir
2652- // or we skip this test lsblk never returns non-existing
2653- // devices
2654- /*
2655- if err := FileExists(device); err != nil {
2656- continue
2657- }
2658- */
2659- // reconstruct full path to entire disk device
2660- disk := fmt.Sprintf("/dev/%s", fields["PKNAME"])
2661-
2662- // FIXME: we should have a way to mock the "/dev" dir
2663- // or we skip this test lsblk never returns non-existing
2664- // files
2665- /*
2666- if err := FileExists(disk); err != nil {
2667- continue
2668- }
2669- */
2670- shortName := string(name[len(name)-1])
2671- bd := blockDevice{
2672- name: fields["LABEL"],
2673- shortName: shortName,
2674- device: device,
2675- mountpoint: fields["MOUNTPOINT"],
2676- parentName: disk,
2677- }
2678-
2679- partitions = append(partitions, bd)
2680- }
2681-
2682- return partitions, nil
2683-}
2684-
2685-// New creates a new partition type
2686-func New() *Partition {
2687- p := new(Partition)
2688-
2689- p.getPartitionDetails()
2690-
2691- return p
2692-}
2693-
2694-// RunWithOther mount the other rootfs partition, execute the
2695-// specified function and unmount "other" before returning. If "other"
2696-// is mounted read-write, /proc, /sys and /dev will also be
2697-// bind-mounted at the time the specified function is called.
2698-func (p *Partition) RunWithOther(option MountOption, f func(otherRoot string) (err error)) (err error) {
2699- dual := p.dualRootPartitions()
2700-
2701- if !dual {
2702- return ErrNoDualPartition
2703- }
2704-
2705- if option == RW {
2706- if err := p.remountOther(RW); err != nil {
2707- return err
2708- }
2709-
2710- defer func() {
2711- // we can't reuse err here as this will override
2712- // the error value we got from calling "f()"
2713- derr := p.remountOther(RO)
2714- if derr != nil && err == nil {
2715- err = derr
2716- }
2717- }()
2718-
2719- if err := p.bindmountRequiredFilesystems(); err != nil {
2720- return err
2721- }
2722-
2723- defer func() {
2724- // we can't reuse err here as this will override
2725- // the error value we got from calling "f()"
2726- derr := p.unmountRequiredFilesystems()
2727- if derr != nil && err == nil {
2728- err = derr
2729- }
2730- }()
2731- }
2732-
2733- err = f(mountTarget)
2734- return err
2735-}
2736-
2737-// SyncBootloaderFiles syncs the bootloader files
2738-//
2739-// We need this code solve the following scenario:
2740-//
2741-// 1. start with: /boot/a/k1, /boot/b/k1
2742-// 2. upgrade with new kernel k2: /boot/a/k1, /boot/b/k2
2743-// 3. reboot into b, running with /boot/b/k2
2744-// 4. new update without a changed kernel, system-a updated
2745-//
2746-// 6. reboot to system-a with /boot/a/k1 (WRONG!)
2747-// But it should be system-a with /boot/a/k2 it was just not part of
2748-// the s-i delta as we already got it. So as step (5) above we do the
2749-// SyncBootloaderFiles that copies /boot/b/k2 -> /boot/a/
2750-// (and that is ok because we know /boot/b/k2 works)
2751-//
2752-func (p *Partition) SyncBootloaderFiles(bootAssets map[string]string) (err error) {
2753- bootloader, err := bootloader(p)
2754- if err != nil {
2755- return err
2756- }
2757-
2758- return bootloader.SyncBootFiles(bootAssets)
2759-}
2760-
2761-// ToggleNextBoot toggles the roofs that should be used on the next boot
2762-func (p *Partition) ToggleNextBoot() (err error) {
2763- if p.dualRootPartitions() {
2764- return p.toggleBootloaderRootfs()
2765- }
2766- return err
2767-}
2768-
2769-// MarkBootSuccessful marks the boot as successful
2770-func (p *Partition) MarkBootSuccessful() (err error) {
2771- bootloader, err := bootloader(p)
2772- if err != nil {
2773- return err
2774- }
2775-
2776- currentRootfs := p.rootPartition().shortName
2777- return bootloader.MarkCurrentBootSuccessful(currentRootfs)
2778-}
2779-
2780-// IsNextBootOther return true if the next boot will use the other rootfs
2781-// partition.
2782-func (p *Partition) IsNextBootOther() bool {
2783- bootloader, err := bootloader(p)
2784- if err != nil {
2785- return false
2786- }
2787-
2788- value, err := bootloader.GetBootVar(bootloaderBootmodeVar)
2789- if err != nil {
2790- return false
2791- }
2792-
2793- if value != bootloaderBootmodeTry {
2794- return false
2795- }
2796-
2797- fsname, err := bootloader.GetNextBootRootFSName()
2798- if err != nil {
2799- return false
2800- }
2801-
2802- otherRootfs := p.otherRootPartition().shortName
2803- if fsname == otherRootfs {
2804- return true
2805- }
2806-
2807- return false
2808-}
2809-
2810-func (p *Partition) getPartitionDetails() (err error) {
2811- p.partitions, err = loadPartitionDetails()
2812- if err != nil {
2813- return err
2814- }
2815-
2816- if !p.dualRootPartitions() && !p.singleRootPartition() {
2817- return ErrPartitionDetection
2818- }
2819-
2820- if p.dualRootPartitions() {
2821- // XXX: this will soon be handled automatically at boot by
2822- // initramfs-tools-ubuntu-core.
2823- return p.ensureOtherMountedRO()
2824- }
2825-
2826- return err
2827-}
2828-
2829-// Return array of blockDevices representing available root partitions
2830-func (p *Partition) rootPartitions() (roots []blockDevice) {
2831- for _, part := range p.partitions {
2832- pos := stringInSlice(rootPartitionLabels(), part.name)
2833- if pos >= 0 {
2834- roots = append(roots, part)
2835- }
2836- }
2837-
2838- return roots
2839-}
2840-
2841-// Return true if system has dual root partitions configured in the
2842-// expected manner for a snappy system.
2843-func (p *Partition) dualRootPartitions() bool {
2844- return len(p.rootPartitions()) == 2
2845-}
2846-
2847-// Return true if system has a single root partition configured in the
2848-// expected manner for a snappy system.
2849-func (p *Partition) singleRootPartition() bool {
2850- return len(p.rootPartitions()) == 1
2851-}
2852-
2853-// Return pointer to blockDevice representing writable partition
2854-func (p *Partition) writablePartition() (result *blockDevice) {
2855- for _, part := range p.partitions {
2856- if part.name == writablePartitionLabel {
2857- return &part
2858- }
2859- }
2860-
2861- return nil
2862-}
2863-
2864-// Return pointer to blockDevice representing boot partition (if any)
2865-func (p *Partition) bootPartition() (result *blockDevice) {
2866- for _, part := range p.partitions {
2867- if part.name == bootPartitionLabel {
2868- return &part
2869- }
2870- }
2871-
2872- return nil
2873-}
2874-
2875-// Return pointer to blockDevice representing currently mounted root
2876-// filesystem
2877-func (p *Partition) rootPartition() (result *blockDevice) {
2878- for _, part := range p.rootPartitions() {
2879- if part.mountpoint == "/" {
2880- return &part
2881- }
2882- }
2883-
2884- return nil
2885-}
2886-
2887-// Return pointer to blockDevice representing the "other" root
2888-// filesystem (which is not currently mounted)
2889-func (p *Partition) otherRootPartition() (result *blockDevice) {
2890- for _, part := range p.rootPartitions() {
2891- if part.mountpoint != "/" {
2892- return &part
2893- }
2894- }
2895-
2896- return nil
2897-}
2898-
2899-// Mount the "other" root filesystem
2900-func (p *Partition) mountOtherRootfs(readOnly bool) (err error) {
2901- var other *blockDevice
2902-
2903- if err := os.MkdirAll(mountTarget, dirMode); err != nil {
2904- return err
2905- }
2906-
2907- other = p.otherRootPartition()
2908-
2909- m := mountEntry{source: other.device, target: mountTarget}
2910-
2911- if readOnly {
2912- m.options = "ro"
2913- err = mountAndAddToGlobalMountList(m)
2914- } else {
2915- err = fsck(m.source)
2916- if err != nil {
2917- return err
2918- }
2919- err = mountAndAddToGlobalMountList(m)
2920- }
2921-
2922- return err
2923-}
2924-
2925-// Create a read-only bindmount of the currently-mounted rootfs at the
2926-// specified mountpoint location (which must already exist).
2927-func (p *Partition) bindmountThisRootfsRO(target string) (err error) {
2928- return mountAndAddToGlobalMountList(mountEntry{source: "/",
2929- target: target,
2930- options: "bind,ro",
2931- bindMount: true})
2932-}
2933-
2934-// Ensure the other partition is mounted read-only.
2935-func (p *Partition) ensureOtherMountedRO() (err error) {
2936- if err = runCommand("/bin/mountpoint", mountTarget); err == nil {
2937- // already mounted
2938- return err
2939- }
2940-
2941- return p.mountOtherRootfs(true)
2942-}
2943-
2944-// Remount the already-mounted other partition. Whether the mount
2945-// should become writable is specified by the writable argument.
2946-//
2947-// XXX: Note that in the case where writable=true, this isn't a simple
2948-// toggle - if the partition is already mounted read-only, it needs to
2949-// be unmounted, fsck(8)'d, then (re-)mounted read-write.
2950-func (p *Partition) remountOther(option MountOption) (err error) {
2951- other := p.otherRootPartition()
2952-
2953- if option == RW {
2954- // r/o -> r/w: initially r/o, so no need to fsck before
2955- // switching to r/w.
2956- err = p.unmountOtherRootfs()
2957- if err != nil {
2958- return err
2959- }
2960-
2961- err = fsck(other.device)
2962- if err != nil {
2963- return err
2964- }
2965-
2966- return mountAndAddToGlobalMountList(mountEntry{
2967- source: other.device,
2968- target: mountTarget})
2969- }
2970- // r/w -> r/o: no fsck required.
2971- return mount(other.device, mountTarget, "remount,ro")
2972-}
2973-
2974-func (p *Partition) unmountOtherRootfs() (err error) {
2975- return unmountAndRemoveFromGlobalMountList(mountTarget)
2976-}
2977-
2978-// The bootloader requires a few filesystems to be mounted when
2979-// run from within a chroot.
2980-func (p *Partition) bindmountRequiredFilesystems() (err error) {
2981-
2982- // we always requires these
2983- requiredChrootMounts := []string{"/dev", "/proc", "/sys"}
2984-
2985- // if there is a boot partition we also bind-mount it
2986- boot := p.bootPartition()
2987- if boot != nil && boot.mountpoint != "" {
2988- requiredChrootMounts = append(requiredChrootMounts, boot.mountpoint)
2989- }
2990-
2991- for _, fs := range requiredChrootMounts {
2992- target := filepath.Join(mountTarget, fs)
2993-
2994- err := mountAndAddToGlobalMountList(mountEntry{source: fs,
2995- target: target,
2996- options: "bind",
2997- bindMount: true})
2998- if err != nil {
2999- return err
3000- }
3001- }
3002-
3003- // Grub also requires access to both rootfs's when run from
3004- // within a chroot (to allow it to create menu entries for
3005- // both), so bindmount the real rootfs.
3006- targetInChroot := filepath.Join(mountTarget, mountTarget)
3007-
3008- // FIXME: we should really remove this after the unmount
3009-
3010- if err = os.MkdirAll(targetInChroot, dirMode); err != nil {
3011- return err
3012- }
3013-
3014- return p.bindmountThisRootfsRO(targetInChroot)
3015-}
3016-
3017-// Undo the effects of BindmountRequiredFilesystems()
3018-func (p *Partition) unmountRequiredFilesystems() (err error) {
3019- if err = undoMounts(true); err != nil {
3020- return err
3021- }
3022-
3023- return nil
3024-}
3025-
3026-func (p *Partition) toggleBootloaderRootfs() (err error) {
3027-
3028- if !p.dualRootPartitions() {
3029- return errors.New("System is not dual root")
3030- }
3031-
3032- bootloader, err := bootloader(p)
3033- if err != nil {
3034- return err
3035- }
3036-
3037- // ensure we have updated kernels etc
3038- if err := bootloader.HandleAssets(); err != nil {
3039- return err
3040- }
3041-
3042- otherRootfs := p.otherRootPartition().shortName
3043- return bootloader.ToggleRootFS(otherRootfs)
3044-}
3045-
3046-// BootloaderDir returns the full path to the (mounted and writable)
3047-// bootloader-specific boot directory.
3048-func (p *Partition) BootloaderDir() string {
3049- bootloader, err := bootloader(p)
3050- if err != nil {
3051- return ""
3052- }
3053-
3054- return bootloader.BootDir()
3055-}
3056
3057=== removed file 'partition/utils.go'
3058--- partition/utils.go 2015-07-08 10:21:02 +0000
3059+++ partition/utils.go 1970-01-01 00:00:00 +0000
3060@@ -1,82 +0,0 @@
3061-// -*- Mode: Go; indent-tabs-mode: t -*-
3062-
3063-/*
3064- * Copyright (C) 2014-2015 Canonical Ltd
3065- *
3066- * This program is free software: you can redistribute it and/or modify
3067- * it under the terms of the GNU General Public License version 3 as
3068- * published by the Free Software Foundation.
3069- *
3070- * This program is distributed in the hope that it will be useful,
3071- * but WITHOUT ANY WARRANTY; without even the implied warranty of
3072- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3073- * GNU General Public License for more details.
3074- *
3075- * You should have received a copy of the GNU General Public License
3076- * along with this program. If not, see <http://www.gnu.org/licenses/>.
3077- *
3078- */
3079-
3080-package partition
3081-
3082-import (
3083- "errors"
3084- "fmt"
3085- "os/exec"
3086- "strings"
3087-)
3088-
3089-// FIXME: would it make sense to differenciate between launch errors and
3090-// exit code? (i.e. something like (returnCode, error) ?)
3091-func runCommandImpl(args ...string) (err error) {
3092- if len(args) == 0 {
3093- return errors.New("no command specified")
3094- }
3095-
3096- if out, err := exec.Command(args[0], args[1:]...).CombinedOutput(); err != nil {
3097- cmdline := strings.Join(args, " ")
3098- return fmt.Errorf("Failed to run command '%s': %s (%s)",
3099- cmdline, out, err)
3100- }
3101- return nil
3102-}
3103-
3104-// Run the command specified by args
3105-// This is a var instead of a function to making mocking in the tests easier
3106-var runCommand = runCommandImpl
3107-
3108-// Run command specified by args and return the output
3109-func runCommandWithStdoutImpl(args ...string) (output string, err error) {
3110- if len(args) == 0 {
3111- return "", errors.New("no command specified")
3112- }
3113-
3114- bytes, err := exec.Command(args[0], args[1:]...).Output()
3115- if err != nil {
3116- return "", err
3117- }
3118-
3119- return string(bytes), err
3120-}
3121-
3122-// This is a var instead of a function to making mocking in the tests easier
3123-var runCommandWithStdout = runCommandWithStdoutImpl
3124-
3125-// Run fsck(8) on specified device.
3126-func fsck(device string) (err error) {
3127- return runCommand(
3128- "/sbin/fsck",
3129- "-M", // Paranoia - don't fsck if already mounted
3130- "-av", device)
3131-}
3132-
3133-// Returns the position of the string in the given slice or -1 if its not found
3134-func stringInSlice(slice []string, value string) int {
3135- for i, s := range slice {
3136- if s == value {
3137- return i
3138- }
3139- }
3140-
3141- return -1
3142-}
3143
3144=== removed file 'partition/utils_test.go'
3145--- partition/utils_test.go 2015-06-02 20:46:07 +0000
3146+++ partition/utils_test.go 1970-01-01 00:00:00 +0000
3147@@ -1,47 +0,0 @@
3148-// -*- Mode: Go; indent-tabs-mode: t -*-
3149-
3150-/*
3151- * Copyright (C) 2014-2015 Canonical Ltd
3152- *
3153- * This program is free software: you can redistribute it and/or modify
3154- * it under the terms of the GNU General Public License version 3 as
3155- * published by the Free Software Foundation.
3156- *
3157- * This program is distributed in the hope that it will be useful,
3158- * but WITHOUT ANY WARRANTY; without even the implied warranty of
3159- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3160- * GNU General Public License for more details.
3161- *
3162- * You should have received a copy of the GNU General Public License
3163- * along with this program. If not, see <http://www.gnu.org/licenses/>.
3164- *
3165- */
3166-
3167-package partition
3168-
3169-import (
3170- . "gopkg.in/check.v1"
3171-)
3172-
3173-type UtilsTestSuite struct {
3174-}
3175-
3176-var _ = Suite(&UtilsTestSuite{})
3177-
3178-func (s *UtilsTestSuite) SetUpTest(c *C) {
3179-}
3180-
3181-func (s *UtilsTestSuite) TestRunCommand(c *C) {
3182- err := runCommandImpl("false")
3183- c.Assert(err, NotNil)
3184-
3185- err = runCommandImpl("no-such-command")
3186- c.Assert(err, NotNil)
3187-}
3188-
3189-func (s *UtilsTestSuite) TestRunCommandWithStdout(c *C) {
3190- runCommandWithStdout = runCommandWithStdoutImpl
3191- output, err := runCommandWithStdout("sh", "-c", "printf 'foo\nbar'")
3192- c.Assert(err, IsNil)
3193- c.Assert(output, DeepEquals, "foo\nbar")
3194-}
3195
3196=== modified file 'pkg/types.go'
3197--- pkg/types.go 2015-05-19 19:34:13 +0000
3198+++ pkg/types.go 2015-09-03 08:14:56 +0000
3199@@ -34,6 +34,8 @@
3200 TypeCore Type = "core"
3201 TypeFramework Type = "framework"
3202 TypeOem Type = "oem"
3203+ TypeOS Type = "os"
3204+ TypeKernel Type = "kernel"
3205 )
3206
3207 // MarshalJSON returns *m as the JSON encoding of m.
3208
3209=== modified file 'snappy/click.go'
3210--- snappy/click.go 2015-08-19 15:21:16 +0000
3211+++ snappy/click.go 2015-09-03 08:14:56 +0000
3212@@ -718,7 +718,8 @@
3213 if err != nil {
3214 return "", err
3215 }
3216- defer part.deb.Close()
3217+ // FIXME: NewSnapPartFromSnapFile is ugly that it needs this close
3218+ defer part.debClose()
3219
3220 return part.Install(inter, flags)
3221 }
3222
3223=== modified file 'snappy/dirs.go'
3224--- snappy/dirs.go 2015-06-30 12:36:00 +0000
3225+++ snappy/dirs.go 2015-09-03 08:14:56 +0000
3226@@ -27,6 +27,8 @@
3227
3228 snapAppsDir string
3229 snapOemDir string
3230+ snapOsDir string
3231+ snapKernelDir string
3232 snapDataDir string
3233 snapDataHomeGlob string
3234 snapAppArmorDir string
3235@@ -53,6 +55,8 @@
3236
3237 snapAppsDir = filepath.Join(rootdir, "/apps")
3238 snapOemDir = filepath.Join(rootdir, "/oem")
3239+ snapOsDir = filepath.Join(rootdir, "/os")
3240+ snapKernelDir = filepath.Join(rootdir, "/kernel")
3241 snapDataDir = filepath.Join(rootdir, "/var/lib/apps")
3242 snapDataHomeGlob = filepath.Join(rootdir, "/home/*/apps/")
3243 snapAppArmorDir = filepath.Join(rootdir, "/var/lib/apparmor/clicks")
3244
3245=== modified file 'snappy/install.go'
3246--- snappy/install.go 2015-06-11 09:38:18 +0000
3247+++ snappy/install.go 2015-09-03 08:14:56 +0000
3248@@ -24,8 +24,8 @@
3249 "os"
3250 "sort"
3251
3252+ "launchpad.net/snappy/bootloader"
3253 "launchpad.net/snappy/logger"
3254- "launchpad.net/snappy/partition"
3255 "launchpad.net/snappy/progress"
3256 "launchpad.net/snappy/provisioning"
3257 )
3258@@ -97,7 +97,7 @@
3259 // FIXME: this is terrible, we really need a single
3260 // bootloader dir like /boot or /boot/loader
3261 // instead of having to query the partition code
3262- if provisioning.InDeveloperMode(partition.BootloaderDir()) {
3263+ if provisioning.InDeveloperMode(bootloader.Dir()) {
3264 flags |= AllowUnauthenticated
3265 }
3266
3267
3268=== modified file 'snappy/install_test.go'
3269--- snappy/install_test.go 2015-06-29 20:21:38 +0000
3270+++ snappy/install_test.go 2015-09-03 08:14:56 +0000
3271@@ -30,7 +30,6 @@
3272 "path/filepath"
3273
3274 . "gopkg.in/check.v1"
3275- "launchpad.net/snappy/partition"
3276 "launchpad.net/snappy/progress"
3277 )
3278
3279@@ -172,94 +171,3 @@
3280 _, err = Install("hello-app.potato", 0, ag)
3281 c.Assert(err, ErrorMatches, ".*"+ErrPackageNameAlreadyInstalled.Error())
3282 }
3283-
3284-func (s *SnapTestSuite) TestUpdate(c *C) {
3285- snapPackagev1 := makeTestSnapPackage(c, "name: foo\nversion: 1\nvendor: foo")
3286- name, err := Install(snapPackagev1, AllowUnauthenticated|DoInstallGC, &progress.NullProgress{})
3287- c.Assert(err, IsNil)
3288- c.Assert(name, Equals, "foo")
3289-
3290- snapPackagev2 := makeTestSnapPackage(c, "name: foo\nversion: 2\nvendor: foo")
3291-
3292- snapR, err := os.Open(snapPackagev2)
3293- c.Assert(err, IsNil)
3294- defer snapR.Close()
3295-
3296- // details
3297- var dlURL, iconURL string
3298- mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3299- switch r.URL.Path {
3300- case "/details/foo":
3301- io.WriteString(w, `{
3302-"package_name": "foo",
3303-"version": "2",
3304-"origin": "sideload",
3305-"anon_download_url": "`+dlURL+`",
3306-"icon_url": "`+iconURL+`"
3307-}`)
3308- case "/dl":
3309- snapR.Seek(0, 0)
3310- io.Copy(w, snapR)
3311- case "/icon":
3312- fmt.Fprintf(w, "")
3313- default:
3314- panic("unexpected url path: " + r.URL.Path)
3315- }
3316- }))
3317- c.Assert(mockServer, NotNil)
3318- defer mockServer.Close()
3319-
3320- dlURL = mockServer.URL + "/dl"
3321- iconURL = mockServer.URL + "/icon"
3322-
3323- storeDetailsURI, err = url.Parse(mockServer.URL + "/details/")
3324- c.Assert(err, IsNil)
3325-
3326- // bulk
3327- mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3328- io.WriteString(w, `[{
3329- "package_name": "foo",
3330- "version": "2",
3331- "origin": "sideload",
3332- "anon_download_url": "`+dlURL+`",
3333- "icon_url": "`+iconURL+`"
3334-}]`)
3335- }))
3336-
3337- storeBulkURI, err = url.Parse(mockServer.URL)
3338- c.Assert(err, IsNil)
3339-
3340- c.Assert(mockServer, NotNil)
3341- defer mockServer.Close()
3342-
3343- // system image
3344- newPartition = func() (p partition.Interface) {
3345- return new(MockPartition)
3346- }
3347- defer func() { newPartition = newPartitionImpl }()
3348-
3349- tempdir := c.MkDir()
3350- systemImageRoot = tempdir
3351-
3352- makeFakeSystemImageChannelConfig(c, filepath.Join(tempdir, systemImageChannelConfig), "1")
3353- // setup fake /other partition
3354- makeFakeSystemImageChannelConfig(c, filepath.Join(tempdir, "other", systemImageChannelConfig), "2")
3355-
3356- siServer := runMockSystemImageWebServer()
3357- defer siServer.Close()
3358-
3359- mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3360- io.WriteString(w, fmt.Sprintf(mockSystemImageIndexJSONTemplate, "1"))
3361- }))
3362- c.Assert(mockServer, NotNil)
3363- defer mockServer.Close()
3364-
3365- systemImageServer = mockServer.URL
3366-
3367- // the test
3368- updates, err := Update(0, &progress.NullProgress{})
3369- c.Assert(err, IsNil)
3370- c.Assert(updates, HasLen, 1)
3371- c.Check(updates[0].Name(), Equals, "foo")
3372- c.Check(updates[0].Version(), Equals, "2")
3373-}
3374
3375=== added file 'snappy/kernel.go'
3376--- snappy/kernel.go 1970-01-01 00:00:00 +0000
3377+++ snappy/kernel.go 2015-09-03 08:14:56 +0000
3378@@ -0,0 +1,103 @@
3379+// -*- Mode: Go; indent-tabs-mode: t -*-
3380+
3381+/*
3382+ * Copyright (C) 2014-2015 Canonical Ltd
3383+ *
3384+ * This program is free software: you can redistribute it and/or modify
3385+ * it under the terms of the GNU General Public License version 3 as
3386+ * published by the Free Software Foundation.
3387+ *
3388+ * This program is distributed in the hope that it will be useful,
3389+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3390+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3391+ * GNU General Public License for more details.
3392+ *
3393+ * You should have received a copy of the GNU General Public License
3394+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3395+ *
3396+ */
3397+
3398+package snappy
3399+
3400+import (
3401+ "os"
3402+ "path/filepath"
3403+
3404+ "launchpad.net/snappy/bootloader"
3405+ "launchpad.net/snappy/helpers"
3406+)
3407+
3408+type KernelSnap struct {
3409+ SnapPart
3410+}
3411+
3412+func (s *KernelSnap) NeedsReboot() bool {
3413+ snappyKernel, _ := bootloader.GetBootVar("snappy_kernel")
3414+ if !s.IsActive() && snappyKernel == s.Version() {
3415+ return true
3416+ }
3417+
3418+ return false
3419+}
3420+
3421+/*
3422+func (s *KernelSnap) dir() string {
3423+ fullName := s.SnapPart.m.qualifiedName(s.origin)
3424+ return filepath.Join(snapKernelDir, fullName, s.m.Version)
3425+}
3426+*/
3427+
3428+func (s *KernelSnap) activate(inhibitHooks bool, inter interacter) error {
3429+ if err := s.SnapPart.activate(inhibitHooks, inter); err != nil {
3430+ return err
3431+ }
3432+
3433+ if err := bootloader.SetBootVar("snappy_kernel", s.Version()); err != nil {
3434+ return err
3435+ }
3436+
3437+ return bootloader.SetBootVar("snappy_mode", "try")
3438+}
3439+
3440+func unpackKernel(s *SnapPart) error {
3441+ // FIXME: do we need the globalRootDir here?
3442+ if err := s.deb.Unpack(s.basedir); err != nil {
3443+ return err
3444+ }
3445+
3446+ // FIXME: create special unpack that diverts kernel/initrd/dtbs
3447+ // to the right place, this will currently copy the files
3448+ // (duplicate data)
3449+ bootdir := bootloader.Dir()
3450+ if err := os.MkdirAll(filepath.Join(bootdir, s.Version()), 0755); err != nil {
3451+ return err
3452+ }
3453+ if s.m.Kernel != "" {
3454+ src := filepath.Join(s.basedir, s.m.Kernel)
3455+ dst := filepath.Join(bootdir, s.Version(), normalizeKernelInitrdName(s.m.Kernel))
3456+ if err := helpers.CopyFile(src, dst, helpers.CopyFlagDefault); err != nil {
3457+ return err
3458+ }
3459+ }
3460+ if s.m.Initrd != "" {
3461+ src := filepath.Join(s.basedir, s.m.Initrd)
3462+ dst := filepath.Join(bootdir, s.Version(), normalizeKernelInitrdName(s.m.Initrd))
3463+ if err := helpers.CopyFile(src, dst, helpers.CopyFlagDefault); err != nil {
3464+ return err
3465+ }
3466+ }
3467+ if s.m.Dtbs != "" {
3468+ src := filepath.Join(s.basedir, s.m.Dtbs)
3469+ dst := filepath.Join(bootdir, s.Version(), s.m.Dtbs)
3470+ // FIXME: a simple "cp -a" is what we need here
3471+ if err := helpers.RSyncWithDelete(src, dst); err != nil {
3472+ return err
3473+ }
3474+ }
3475+
3476+ if err := bootloader.SetBootVar("snappy_kernel", s.Version()); err != nil {
3477+ return err
3478+ }
3479+
3480+ return bootloader.SetBootVar("snappy_mode", "trial")
3481+}
3482
3483=== modified file 'snappy/oem.go'
3484--- snappy/oem.go 2015-07-01 14:48:33 +0000
3485+++ snappy/oem.go 2015-09-03 08:14:56 +0000
3486@@ -291,3 +291,14 @@
3487
3488 return nil
3489 }
3490+
3491+type OemSnap struct {
3492+ SnapPart
3493+}
3494+
3495+/*
3496+func (s *OemSnap) dir() string {
3497+ fullName := s.SnapPart.m.qualifiedName(s.origin)
3498+ return filepath.Join(snapOemDir, fullName, s.m.Version)
3499+}
3500+*/
3501
3502=== added file 'snappy/os.go'
3503--- snappy/os.go 1970-01-01 00:00:00 +0000
3504+++ snappy/os.go 2015-09-03 08:14:56 +0000
3505@@ -0,0 +1,68 @@
3506+// -*- Mode: Go; indent-tabs-mode: t -*-
3507+
3508+/*
3509+ * Copyright (C) 2014-2015 Canonical Ltd
3510+ *
3511+ * This program is free software: you can redistribute it and/or modify
3512+ * it under the terms of the GNU General Public License version 3 as
3513+ * published by the Free Software Foundation.
3514+ *
3515+ * This program is distributed in the hope that it will be useful,
3516+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3517+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3518+ * GNU General Public License for more details.
3519+ *
3520+ * You should have received a copy of the GNU General Public License
3521+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3522+ *
3523+ */
3524+
3525+package snappy
3526+
3527+import (
3528+ "launchpad.net/snappy/bootloader"
3529+)
3530+
3531+type OsSnap struct {
3532+ SnapPart
3533+}
3534+
3535+func (s *OsSnap) NeedsReboot() bool {
3536+ snappyOS, _ := bootloader.GetBootVar("snappy_os")
3537+ if !s.IsActive() && snappyOS == s.Version() {
3538+ return true
3539+ }
3540+
3541+ return false
3542+}
3543+
3544+/*
3545+func (s *OsSnap) dir() string {
3546+ fullName := s.SnapPart.m.qualifiedName(s.origin)
3547+ return filepath.Join(snapOsDir, fullName, s.m.Version)
3548+}
3549+*/
3550+
3551+func (s *OsSnap) activate(inhibitHooks bool, inter interacter) error {
3552+ if err := s.SnapPart.activate(inhibitHooks, inter); err != nil {
3553+ return err
3554+ }
3555+ if err := bootloader.SetBootVar("snappy_os", s.Version()); err != nil {
3556+ return err
3557+ }
3558+
3559+ return bootloader.SetBootVar("snappy_mode", "try")
3560+}
3561+
3562+func unpackOS(s *SnapPart) error {
3563+ // FIXME: do we need the globalRootDir here?
3564+ if err := s.deb.Unpack(s.basedir); err != nil {
3565+ return err
3566+ }
3567+
3568+ if err := bootloader.SetBootVar("snappy_os", s.Version()); err != nil {
3569+ return err
3570+ }
3571+
3572+ return bootloader.SetBootVar("snappy_mode", "trial")
3573+}
3574
3575=== modified file 'snappy/parts.go'
3576--- snappy/parts.go 2015-07-23 11:05:37 +0000
3577+++ snappy/parts.go 2015-09-03 08:14:56 +0000
3578@@ -134,14 +134,10 @@
3579 m := new(MetaRepository)
3580 m.all = []Repository{}
3581
3582- if repo := NewSystemImageRepository(); repo != nil {
3583- m.all = append(m.all, repo)
3584- }
3585- if repo := NewLocalSnapRepository(snapAppsDir); repo != nil {
3586- m.all = append(m.all, repo)
3587- }
3588- if repo := NewLocalSnapRepository(snapOemDir); repo != nil {
3589- m.all = append(m.all, repo)
3590+ for _, d := range []string{snapAppsDir, snapOemDir, snapOsDir, snapKernelDir} {
3591+ if repo := NewLocalSnapRepository(d); repo != nil {
3592+ m.all = append(m.all, repo)
3593+ }
3594 }
3595
3596 return m
3597
3598=== modified file 'snappy/purge.go'
3599--- snappy/purge.go 2015-05-29 12:08:46 +0000
3600+++ snappy/purge.go 2015-09-03 08:14:56 +0000
3601@@ -47,7 +47,7 @@
3602
3603 purgeActive := flags&DoPurgeActive != 0
3604
3605- var active []*SnapPart
3606+ var active []Snap
3607
3608 for _, datadir := range datadirs {
3609 yamlPath := filepath.Join(snapAppsDir, datadir.QualifiedName(), datadir.Version, "meta", "package.yaml")
3610
3611=== modified file 'snappy/purge_test.go'
3612--- snappy/purge_test.go 2015-06-09 19:11:54 +0000
3613+++ snappy/purge_test.go 2015-09-03 08:14:56 +0000
3614@@ -57,7 +57,7 @@
3615 c.Check(inter.notified, HasLen, 0)
3616 }
3617
3618-func (s *purgeSuite) mkpkg(c *C, args ...string) (dataDir string, part *SnapPart) {
3619+func (s *purgeSuite) mkpkg(c *C, args ...string) (dataDir string, part Snap) {
3620 version := "1.10"
3621 extra := ""
3622 switch len(args) {
3623
3624=== modified file 'snappy/snapp.go'
3625--- snappy/snapp.go 2015-08-19 15:21:16 +0000
3626+++ snappy/snapp.go 2015-09-03 08:14:56 +0000
3627@@ -38,6 +38,7 @@
3628
3629 "gopkg.in/yaml.v2"
3630
3631+ "launchpad.net/snappy/bootloader"
3632 "launchpad.net/snappy/clickdeb"
3633 "launchpad.net/snappy/helpers"
3634 "launchpad.net/snappy/logger"
3635@@ -198,6 +199,16 @@
3636 return nil
3637 }
3638
3639+type Snap interface {
3640+ Part
3641+ activate(inhibitHooks bool, inter interacter) error
3642+ deactivate(inhibitHooks bool, inter interacter) error
3643+ remove(inter interacter) error
3644+ dir() string
3645+ ServiceYamls() []ServiceYaml
3646+ debClose() error
3647+}
3648+
3649 // TODO split into payloads per package type composing the common
3650 // elements for all snaps.
3651 type packageYaml struct {
3652@@ -228,6 +239,11 @@
3653
3654 ExplicitLicenseAgreement bool `yaml:"explicit-license-agreement,omitempty"`
3655 LicenseVersion string `yaml:"license-version,omitempty"`
3656+
3657+ // FIXME: move into a special kernel struct
3658+ Kernel string `yaml:"kernel,omitempty"`
3659+ Initrd string `yaml:"initrd,omitempty"`
3660+ Dtbs string `yaml:"dtbs,omitempty"`
3661 }
3662
3663 type remoteSnap struct {
3664@@ -362,6 +378,12 @@
3665 return m.Name + "." + origin
3666 }
3667
3668+// noramlizeAssetName transforms like "vmlinuz-4.1.0" -> "vmlinuz"
3669+func normalizeKernelInitrdName(name string) string {
3670+ name = filepath.Base(name)
3671+ return strings.SplitN(name, "-", 2)[0]
3672+}
3673+
3674 func (m *packageYaml) checkForNameClashes() error {
3675 d := make(map[string]struct{})
3676 for _, bin := range m.Binaries {
3677@@ -528,7 +550,7 @@
3678 }
3679
3680 // NewInstalledSnapPart returns a new SnapPart from the given yamlPath
3681-func NewInstalledSnapPart(yamlPath, origin string) (*SnapPart, error) {
3682+func NewInstalledSnapPart(yamlPath, origin string) (Snap, error) {
3683 m, err := parsePackageYamlFile(yamlPath)
3684 if err != nil {
3685 return nil, err
3686@@ -546,7 +568,7 @@
3687 // NewSnapPartFromSnapFile loads a snap from the given (clickdeb) snap file.
3688 // Caller should call Close on the clickdeb.
3689 // TODO: expose that Close.
3690-func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (*SnapPart, error) {
3691+func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (Snap, error) {
3692 if err := clickdeb.Verify(snapFile, unauthOk); err != nil {
3693 return nil, err
3694 }
3695@@ -566,10 +588,15 @@
3696 return nil, err
3697 }
3698
3699+ // FIMXE: move all into {App,Kernel,Os}Snap.dir()
3700 targetDir := snapAppsDir
3701- // the "oem" parts are special
3702- if m.Type == pkg.TypeOem {
3703+ switch m.Type {
3704+ case pkg.TypeOem:
3705 targetDir = snapOemDir
3706+ case pkg.TypeOS:
3707+ targetDir = snapOsDir
3708+ case pkg.TypeKernel:
3709+ targetDir = snapKernelDir
3710 }
3711
3712 if origin == sideloadedOrigin {
3713@@ -579,12 +606,22 @@
3714 fullName := m.qualifiedName(origin)
3715 instDir := filepath.Join(targetDir, fullName, m.Version)
3716
3717- return &SnapPart{
3718+ baseSnap := &SnapPart{
3719 basedir: instDir,
3720 origin: origin,
3721 m: m,
3722 deb: d,
3723- }, nil
3724+ }
3725+ switch m.Type {
3726+ case pkg.TypeOem:
3727+ return &OemSnap{SnapPart: *baseSnap}, nil
3728+ case pkg.TypeOS:
3729+ return &OsSnap{SnapPart: *baseSnap}, nil
3730+ case pkg.TypeKernel:
3731+ return &KernelSnap{SnapPart: *baseSnap}, nil
3732+ }
3733+
3734+ return baseSnap, nil
3735 }
3736
3737 // NewSnapPartFromYaml returns a new SnapPart from the given *packageYaml at yamlPath
3738@@ -650,6 +687,10 @@
3739 return part, nil
3740 }
3741
3742+func (s *SnapPart) dir() string {
3743+ return s.basedir
3744+}
3745+
3746 // Type returns the type of the SnapPart (app, oem, ...)
3747 func (s *SnapPart) Type() pkg.Type {
3748 if s.m.Type != "" {
3749@@ -665,6 +706,11 @@
3750 return s.m.Name
3751 }
3752
3753+// FIXME: super ugly
3754+func (s *SnapPart) debClose() error {
3755+ return s.deb.Close()
3756+}
3757+
3758 // Version returns the version
3759 func (s *SnapPart) Version() string {
3760 if s.basedir != "" {
3761@@ -802,7 +848,7 @@
3762 fullName := QualifiedName(s)
3763 dataDir := filepath.Join(snapDataDir, fullName, s.Version())
3764
3765- var oldPart *SnapPart
3766+ var oldPart Snap
3767 if currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(s.basedir, "..", "current")); currentActiveDir != "" {
3768 oldPart, err = NewInstalledSnapPart(filepath.Join(currentActiveDir, "meta", "package.yaml"), s.origin)
3769 if err != nil {
3770@@ -824,10 +870,21 @@
3771 }
3772 }()
3773
3774- // we need to call the external helper so that we can reliable drop
3775- // privs
3776- if err := s.deb.UnpackWithDropPrivs(s.basedir, globalRootDir); err != nil {
3777- return "", err
3778+ // FIXME: so ugly, what we need is
3779+ // "dynamic dispatch of overridden methods" which go makes hard
3780+ switch s.Type() {
3781+ case pkg.TypeOS:
3782+ if err := unpackOS(s); err != nil {
3783+ return "", err
3784+ }
3785+ case pkg.TypeKernel:
3786+ if err := unpackKernel(s); err != nil {
3787+ return "", err
3788+ }
3789+ default:
3790+ if err := s.deb.UnpackWithDropPrivs(s.basedir, globalRootDir); err != nil {
3791+ return "", err
3792+ }
3793 }
3794
3795 // legacy, the hooks (e.g. apparmor) need this. Once we converted
3796@@ -1128,6 +1185,14 @@
3797
3798 // NeedsReboot returns true if the snap becomes active on the next reboot
3799 func (s *SnapPart) NeedsReboot() bool {
3800+ // FIXME: needs to become SnapKernel
3801+ if s.m.Type == pkg.TypeKernel {
3802+ snappyKernel, _ := bootloader.GetBootVar("snappy_kernel")
3803+ if !s.IsActive() && snappyKernel == s.Version() {
3804+ return true
3805+ }
3806+ }
3807+
3808 return false
3809 }
3810
3811@@ -1266,10 +1331,10 @@
3812 }
3813
3814 // RefreshDependentsSecurity refreshes the security policies of dependent snaps
3815-func (s *SnapPart) RefreshDependentsSecurity(oldPart *SnapPart, inter interacter) (err error) {
3816+func (s *SnapPart) RefreshDependentsSecurity(oldPart Snap, inter interacter) (err error) {
3817 oldBaseDir := ""
3818 if oldPart != nil {
3819- oldBaseDir = oldPart.basedir
3820+ oldBaseDir = oldPart.(*SnapPart).basedir
3821 }
3822 upPol, upTpl := policy.AppArmorDelta(oldBaseDir, s.basedir, s.Name()+"_")
3823
3824@@ -1878,7 +1943,7 @@
3825 // The returned environment contains additional SNAP_* variables that
3826 // are required when calling a meta/hook/ script and that will override
3827 // any already existing SNAP_* variables in os.Environment()
3828-func makeSnapHookEnv(part *SnapPart) (env []string) {
3829+func makeSnapHookEnv(part Snap) (env []string) {
3830 desc := struct {
3831 AppName string
3832 AppArch string
3833@@ -1889,7 +1954,7 @@
3834 }{
3835 part.Name(),
3836 helpers.UbuntuArchitecture(),
3837- part.basedir,
3838+ part.dir(),
3839 part.Version(),
3840 QualifiedName(part),
3841 part.Origin(),
3842
3843=== modified file 'snappy/snapp_test.go'
3844--- snappy/snapp_test.go 2015-07-23 11:05:37 +0000
3845+++ snappy/snapp_test.go 2015-09-03 08:14:56 +0000
3846@@ -29,10 +29,10 @@
3847 "os"
3848 "path/filepath"
3849 "strings"
3850+ "testing"
3851
3852 "launchpad.net/snappy/clickdeb"
3853 "launchpad.net/snappy/helpers"
3854- "launchpad.net/snappy/partition"
3855 "launchpad.net/snappy/pkg"
3856 "launchpad.net/snappy/policy"
3857 "launchpad.net/snappy/release"
3858@@ -41,6 +41,9 @@
3859 . "gopkg.in/check.v1"
3860 )
3861
3862+// Hook up check.v1 into the "go test" runner
3863+func Test(t *testing.T) { TestingT(t) }
3864+
3865 type SnapTestSuite struct {
3866 tempdir string
3867 clickhook string
3868@@ -54,9 +57,6 @@
3869 aaClickHookCmd = "/bin/true"
3870 s.secbase = policy.SecBase
3871 s.tempdir = c.MkDir()
3872- newPartition = func() (p partition.Interface) {
3873- return new(MockPartition)
3874- }
3875
3876 SetRootDir(s.tempdir)
3877 policy.SecBase = filepath.Join(s.tempdir, "security")
3878@@ -96,9 +96,6 @@
3879 c.Assert(err, IsNil)
3880
3881 runScFilterGen = mockRunScFilterGen
3882-
3883- // ensure we do not look at the system
3884- systemImageRoot = s.tempdir
3885 }
3886
3887 func (s *SnapTestSuite) TearDownTest(c *C) {
3888@@ -153,11 +150,11 @@
3889 c.Assert(services[0].Name, Equals, "svc1")
3890
3891 // ensure we get valid Date()
3892- st, err := os.Stat(snap.basedir)
3893+ st, err := os.Stat(snap.dir())
3894 c.Assert(err, IsNil)
3895 c.Assert(snap.Date(), Equals, st.ModTime())
3896
3897- c.Assert(snap.basedir, Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
3898+ c.Assert(snap.dir(), Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
3899 c.Assert(snap.InstalledSize(), Not(Equals), -1)
3900 }
3901
3902@@ -827,11 +824,11 @@
3903 c.Assert(services[1].Description, Equals, "Service #2")
3904
3905 // ensure we get valid Date()
3906- st, err := os.Stat(snap.basedir)
3907+ st, err := os.Stat(snap.dir())
3908 c.Assert(err, IsNil)
3909 c.Assert(snap.Date(), Equals, st.ModTime())
3910
3911- c.Assert(snap.basedir, Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
3912+ c.Assert(snap.dir(), Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
3913 c.Assert(snap.InstalledSize(), Not(Equals), -1)
3914 }
3915
3916
3917=== removed file 'snappy/systemimage.go'
3918--- snappy/systemimage.go 2015-07-24 12:03:30 +0000
3919+++ snappy/systemimage.go 1970-01-01 00:00:00 +0000
3920@@ -1,518 +0,0 @@
3921-// -*- Mode: Go; indent-tabs-mode: t -*-
3922-
3923-/*
3924- * Copyright (C) 2014-2015 Canonical Ltd
3925- *
3926- * This program is free software: you can redistribute it and/or modify
3927- * it under the terms of the GNU General Public License version 3 as
3928- * published by the Free Software Foundation.
3929- *
3930- * This program is distributed in the hope that it will be useful,
3931- * but WITHOUT ANY WARRANTY; without even the implied warranty of
3932- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3933- * GNU General Public License for more details.
3934- *
3935- * You should have received a copy of the GNU General Public License
3936- * along with this program. If not, see <http://www.gnu.org/licenses/>.
3937- *
3938- */
3939-
3940-package snappy
3941-
3942-import (
3943- "crypto/sha512"
3944- "encoding/hex"
3945- "fmt"
3946- "os"
3947- "path/filepath"
3948- "strings"
3949- "time"
3950-
3951- "github.com/mvo5/goconfigparser"
3952-
3953- "launchpad.net/snappy/coreconfig"
3954- "launchpad.net/snappy/helpers"
3955- "launchpad.net/snappy/logger"
3956- "launchpad.net/snappy/partition"
3957- "launchpad.net/snappy/pkg"
3958- "launchpad.net/snappy/progress"
3959- "launchpad.net/snappy/provisioning"
3960-)
3961-
3962-const (
3963- systemImagePartName = "ubuntu-core"
3964- systemImagePartOrigin = "ubuntu"
3965- systemImagePartVendor = "Canonical Ltd."
3966-
3967- // location of the channel config on the filesystem.
3968- //
3969- // This file specifies the s-i version installed on the rootfs
3970- // and hence s-i updates this file on every update applied to
3971- // the rootfs (by unpacking file "version-$version.tar.xz").
3972- systemImageChannelConfig = "/etc/system-image/config.d/01_channel.ini"
3973-
3974- // location of the client config.
3975- //
3976- // The full path to this file needs to be passed to
3977- // systemImageCli when querying a different rootfs.
3978- systemImageClientConfig = "/etc/system-image/config.d/00_default.ini"
3979-)
3980-
3981-var (
3982- // the system-image-cli binary
3983- systemImageCli = "system-image-cli"
3984-)
3985-
3986-// This is the root directory of the filesystem. Its only useful to
3987-// change when writing tests
3988-var systemImageRoot = "/"
3989-
3990-// will replace newPartition() to return a mockPartition
3991-var newPartition = newPartitionImpl
3992-
3993-func newPartitionImpl() (p partition.Interface) {
3994- return partition.New()
3995-}
3996-
3997-// SystemImagePart represents a "core" snap that is managed via the SystemImage
3998-// client
3999-type SystemImagePart struct {
4000- version string
4001- versionDetails string
4002- channelName string
4003- lastUpdate time.Time
4004-
4005- isInstalled bool
4006- isActive bool
4007-
4008- updateSize int64
4009-
4010- partition partition.Interface
4011-}
4012-
4013-// Type returns pkg.TypeCore for this snap
4014-func (s *SystemImagePart) Type() pkg.Type {
4015- return pkg.TypeCore
4016-}
4017-
4018-// Name returns the name
4019-func (s *SystemImagePart) Name() string {
4020- return systemImagePartName
4021-}
4022-
4023-// Origin returns the origin ("ubuntu")
4024-func (s *SystemImagePart) Origin() string {
4025- return systemImagePartOrigin
4026-}
4027-
4028-// Vendor returns the vendor ("Canonical Ltd.")
4029-func (s *SystemImagePart) Vendor() string {
4030- return systemImagePartVendor
4031-}
4032-
4033-// Version returns the version
4034-func (s *SystemImagePart) Version() string {
4035- return s.version
4036-}
4037-
4038-// Description returns the description
4039-func (s *SystemImagePart) Description() string {
4040- return "ubuntu-core description"
4041-}
4042-
4043-// Hash returns the hash
4044-func (s *SystemImagePart) Hash() string {
4045- hasher := sha512.New()
4046- hasher.Write([]byte(s.versionDetails))
4047- hexdigest := hex.EncodeToString(hasher.Sum(nil))
4048-
4049- return hexdigest
4050-}
4051-
4052-// IsActive returns true if the snap is active
4053-func (s *SystemImagePart) IsActive() bool {
4054- return s.isActive
4055-}
4056-
4057-// IsInstalled returns true if the snap is installed
4058-func (s *SystemImagePart) IsInstalled() bool {
4059- return s.isInstalled
4060-}
4061-
4062-// InstalledSize returns the size of the installed snap
4063-func (s *SystemImagePart) InstalledSize() int64 {
4064- return -1
4065-}
4066-
4067-// DownloadSize returns the dowload size
4068-func (s *SystemImagePart) DownloadSize() int64 {
4069- return s.updateSize
4070-}
4071-
4072-// Date returns the last update date
4073-func (s *SystemImagePart) Date() time.Time {
4074- return s.lastUpdate
4075-}
4076-
4077-// SetActive sets the snap active
4078-func (s *SystemImagePart) SetActive(pb progress.Meter) (err error) {
4079- isNextBootOther := s.partition.IsNextBootOther()
4080- // active and no switch scheduled -> nothing to do
4081- if s.IsActive() && !isNextBootOther {
4082- return nil
4083- }
4084- // not currently active but switch scheduled already -> nothing to do
4085- if !s.IsActive() && isNextBootOther {
4086- return nil
4087- }
4088-
4089- return s.partition.ToggleNextBoot()
4090-}
4091-
4092-// override in tests
4093-var bootloaderDir = bootloaderDirImpl
4094-
4095-func bootloaderDirImpl() string {
4096- return partition.BootloaderDir()
4097-}
4098-
4099-// Install installs the snap
4100-func (s *SystemImagePart) Install(pb progress.Meter, flags InstallFlags) (name string, err error) {
4101- if provisioning.IsSideLoaded(bootloaderDir()) {
4102- return "", ErrSideLoaded
4103- }
4104-
4105- if pb != nil {
4106- // ensure the progress finishes when we are done
4107- defer func() {
4108- pb.Finished()
4109- }()
4110- }
4111-
4112- // Ensure there is always a kernel + initrd to boot with, even
4113- // if the update does not provide new versions.
4114- if s.needsBootAssetSync() {
4115- if pb != nil {
4116- pb.Notify("Syncing boot files")
4117- }
4118- err = s.partition.SyncBootloaderFiles(bootAssetFilePaths())
4119- if err != nil {
4120- return "", err
4121- }
4122- }
4123-
4124- // find out what config file to use, the other partition may be
4125- // empty so we need to fallback to the current one if it is
4126- configFile := systemImageClientConfig
4127- err = s.partition.RunWithOther(partition.RO, func(otherRoot string) (err error) {
4128- // XXX: Note that systemImageDownloadUpdate() requires
4129- // the s-i _client_ config file whereas otherIsEmpty()
4130- // checks the s-i _channel_ config file.
4131- otherConfigFile := filepath.Join(systemImageRoot, otherRoot, systemImageClientConfig)
4132- if !otherIsEmpty(otherRoot) && helpers.FileExists(otherConfigFile) {
4133- configFile = otherConfigFile
4134- }
4135-
4136- // NOTE: we need to pass the config dir here
4137- configDir := filepath.Dir(configFile)
4138- return systemImageDownloadUpdate(configDir, pb)
4139- })
4140- if err != nil {
4141- return "", err
4142- }
4143-
4144- // Check that the final system state is as expected.
4145- if err = s.verifyUpgradeWasApplied(); err != nil {
4146- return "", err
4147- }
4148-
4149- // XXX: ToggleNextBoot() calls handleAssets() (but not SyncBootloader
4150- // files :/) - handleAssets() may copy kernel/initramfs to the
4151- // sync mounted /boot/uboot, so its very slow, tell the user
4152- // at least that something is going on
4153- if pb != nil {
4154- pb.Notify("Updating boot files")
4155- }
4156- if err = s.partition.ToggleNextBoot(); err != nil {
4157- return "", err
4158- }
4159- return systemImagePartName, nil
4160-}
4161-
4162-// Ensure the expected version update was applied to the expected partition.
4163-func (s *SystemImagePart) verifyUpgradeWasApplied() error {
4164- // The upgrade has now been applied, so check that the expected
4165- // update was applied by comparing "self" (which is the newest
4166- // system-image revision with that installed on the other
4167- // partition.
4168-
4169- // Determine the latest installed part.
4170- latestPart := makeOtherPart(s.partition)
4171- if latestPart == nil {
4172- // If there is no other part, this system must be a
4173- // single rootfs one, so re-query current to find the
4174- // latest installed part.
4175- latestPart = makeCurrentPart(s.partition)
4176- }
4177-
4178- if latestPart == nil {
4179- return &ErrUpgradeVerificationFailed{
4180- msg: "could not find latest installed partition",
4181- }
4182- }
4183-
4184- if s.version != latestPart.Version() {
4185- return &ErrUpgradeVerificationFailed{
4186- msg: fmt.Sprintf("found %q but expected %q", latestPart.Version(), s.version),
4187- }
4188- }
4189-
4190- return nil
4191-}
4192-
4193-// Uninstall can not be used for "core" snaps
4194-func (s *SystemImagePart) Uninstall(progress.Meter) error {
4195- return ErrPackageNotRemovable
4196-}
4197-
4198-// Config is used to to configure the snap
4199-func (s *SystemImagePart) Config(configuration []byte) (newConfig string, err error) {
4200- if cfg := string(configuration); cfg != "" {
4201- return coreconfig.Set(cfg)
4202- }
4203-
4204- return coreconfig.Get()
4205-}
4206-
4207-// NeedsReboot returns true if the snap becomes active on the next reboot
4208-func (s *SystemImagePart) NeedsReboot() bool {
4209-
4210- if !s.IsActive() && s.partition.IsNextBootOther() {
4211- return true
4212- }
4213-
4214- return false
4215-}
4216-
4217-// MarkBootSuccessful marks the *currently* booted rootfs as "good"
4218-// (it booted :)
4219-// Note: Not part of the Part interface.
4220-func (s *SystemImagePart) MarkBootSuccessful() (err error) {
4221-
4222- return s.partition.MarkBootSuccessful()
4223-}
4224-
4225-// Channel returns the system-image-server channel used
4226-func (s *SystemImagePart) Channel() string {
4227- return s.channelName
4228-}
4229-
4230-// Icon returns the icon path
4231-func (s *SystemImagePart) Icon() string {
4232- return ""
4233-}
4234-
4235-// Frameworks returns the list of frameworks needed by the snap
4236-func (s *SystemImagePart) Frameworks() ([]string, error) {
4237- // system image parts can't depend on frameworks.
4238- return nil, nil
4239-}
4240-
4241-// SystemImageRepository is the type used for the system-image-server
4242-type SystemImageRepository struct {
4243- partition partition.Interface
4244-}
4245-
4246-// NewSystemImageRepository returns a new SystemImageRepository
4247-func NewSystemImageRepository() *SystemImageRepository {
4248- return &SystemImageRepository{partition: newPartition()}
4249-}
4250-
4251-func makePartFromSystemImageConfigFile(p partition.Interface, channelIniPath string, isActive bool) (part Part, err error) {
4252- cfg := goconfigparser.New()
4253- f, err := os.Open(channelIniPath)
4254- if err != nil {
4255- return nil, err
4256- }
4257- defer f.Close()
4258- err = cfg.Read(f)
4259- if err != nil {
4260- logger.Noticef("Can not parse config %q: %v", channelIniPath, err)
4261- return nil, err
4262- }
4263- st, err := os.Stat(channelIniPath)
4264- if err != nil {
4265- logger.Noticef("Can not stat %q: %v", channelIniPath, err)
4266- return nil, err
4267- }
4268-
4269- currentBuildNumber, err := cfg.Get("service", "build_number")
4270- versionDetails, err := cfg.Get("service", "version_detail")
4271- channelName, err := cfg.Get("service", "channel")
4272- return &SystemImagePart{
4273- isActive: isActive,
4274- isInstalled: true,
4275- version: currentBuildNumber,
4276- versionDetails: versionDetails,
4277- channelName: channelName,
4278- lastUpdate: st.ModTime(),
4279- partition: p}, err
4280-}
4281-
4282-// Returns the part associated with the current rootfs
4283-func makeCurrentPart(p partition.Interface) Part {
4284- configFile := filepath.Join(systemImageRoot, systemImageChannelConfig)
4285- part, err := makePartFromSystemImageConfigFile(p, configFile, true)
4286- if err != nil {
4287- return nil
4288- }
4289- return part
4290-}
4291-
4292-// otherIsEmpty returns true if the rootfs path specified should be
4293-// considered empty.
4294-//
4295-// Note that the rootfs _may_ not actually be strictly empty, but it
4296-// must be considered empty if it is incomplete.
4297-//
4298-// This function encapsulates the heuristics to determine if the rootfs
4299-// is complete.
4300-func otherIsEmpty(root string) bool {
4301- configFile := filepath.Join(systemImageRoot, root, systemImageChannelConfig)
4302-
4303- st, err := os.Stat(configFile)
4304-
4305- if err == nil && st.Size() > 0 {
4306- // the channel config file exists and has a "reasonable"
4307- // size. The upgrade therefore completed successfully,
4308- // so consider the rootfs complete.
4309- return false
4310- }
4311-
4312- // The upgrader pre-creates the s-i channel config file as a
4313- // zero-sized file when "other" is first provisioned.
4314- //
4315- // So if this file either does not exist, or has a size of zero
4316- // (indicating the upgrader failed to complete the s-i unpack on
4317- // a previous boot [which would have made configFile >0 bytes]),
4318- // the other partition is considered empty.
4319-
4320- return true
4321-}
4322-
4323-// Returns the part associated with the other rootfs (if any)
4324-func makeOtherPart(p partition.Interface) Part {
4325- var part Part
4326- err := p.RunWithOther(partition.RO, func(otherRoot string) (err error) {
4327- if otherIsEmpty(otherRoot) {
4328- return nil
4329- }
4330-
4331- configFile := filepath.Join(systemImageRoot, otherRoot, systemImageChannelConfig)
4332- part, err = makePartFromSystemImageConfigFile(p, configFile, false)
4333- if err != nil {
4334- logger.Noticef("Can not make system-image part for %q: %v", configFile, err)
4335- }
4336- return err
4337- })
4338- if err == partition.ErrNoDualPartition {
4339- return nil
4340- }
4341- return part
4342-}
4343-
4344-// Description describes the repository
4345-func (s *SystemImageRepository) Description() string {
4346- return "SystemImageRepository"
4347-}
4348-
4349-// Search searches the SystemImageRepository for the given terms
4350-func (s *SystemImageRepository) Search(terms string) (versions []Part, err error) {
4351- if strings.Contains(terms, systemImagePartName) {
4352- part := makeCurrentPart(s.partition)
4353- versions = append(versions, part)
4354- }
4355- return versions, err
4356-}
4357-
4358-// Details returns details for the given snap
4359-func (s *SystemImageRepository) Details(snapName string) (versions []Part, err error) {
4360- if snapName == systemImagePartName {
4361- part := makeCurrentPart(s.partition)
4362- versions = append(versions, part)
4363- }
4364- return versions, err
4365-}
4366-
4367-// Updates returns the available updates
4368-func (s *SystemImageRepository) Updates() (parts []Part, err error) {
4369- configFile := filepath.Join(systemImageRoot, systemImageChannelConfig)
4370- updateStatus, err := systemImageClientCheckForUpdates(configFile)
4371-
4372- current := makeCurrentPart(s.partition)
4373- // no VersionCompare here because the channel provides a "order" and
4374- // that may go backwards when switching channels(?)
4375- if current.Version() != updateStatus.targetVersion {
4376- parts = append(parts, &SystemImagePart{
4377- version: updateStatus.targetVersion,
4378- versionDetails: updateStatus.targetVersionDetails,
4379- lastUpdate: updateStatus.lastUpdate,
4380- updateSize: updateStatus.updateSize,
4381- channelName: current.(*SystemImagePart).channelName,
4382- partition: s.partition})
4383- }
4384-
4385- return parts, err
4386-}
4387-
4388-// Installed returns the installed snaps from this repository
4389-func (s *SystemImageRepository) Installed() (parts []Part, err error) {
4390- // current partition
4391- curr := makeCurrentPart(s.partition)
4392- if curr != nil {
4393- parts = append(parts, curr)
4394- }
4395-
4396- // other partition
4397- other := makeOtherPart(s.partition)
4398- if other != nil {
4399- parts = append(parts, other)
4400- }
4401-
4402- return parts, err
4403-}
4404-
4405-// needsSync determines if syncing boot assets is required
4406-func (s *SystemImagePart) needsBootAssetSync() bool {
4407- // current partition
4408- curr := makeCurrentPart(s.partition)
4409- if curr == nil {
4410- // this should never ever happen
4411- panic("current part does not exist")
4412- }
4413-
4414- // other partition
4415- other := makeOtherPart(s.partition)
4416- if other == nil {
4417- return true
4418- }
4419-
4420- // the idea here is that a channel change on the other
4421- // partition always triggers a full image download so
4422- // there is no need for syncing the assets (because the
4423- // kernel is included in the full image already)
4424- //
4425- // FIXME: its not entirely clear if this is true, there
4426- // is no mechanism to switch channels right now and all
4427- // the tests always switch both partitions
4428- if curr.Channel() != other.Channel() {
4429- return false
4430- }
4431-
4432- // if the other version is already a higher version number
4433- // than the current one it means all the kernel updates
4434- // has happend already and we do not need to sync the
4435- // bootloader files, see:
4436- // https://bugs.launchpad.net/snappy/+bug/1474125
4437- return VersionCompare(curr.Version(), other.Version()) > 0
4438-}
4439
4440=== removed file 'snappy/systemimage_native.go'
4441--- snappy/systemimage_native.go 2015-07-02 20:57:18 +0000
4442+++ snappy/systemimage_native.go 1970-01-01 00:00:00 +0000
4443@@ -1,211 +0,0 @@
4444-// -*- Mode: Go; indent-tabs-mode: t -*-
4445-
4446-/*
4447- * Copyright (C) 2014-2015 Canonical Ltd
4448- *
4449- * This program is free software: you can redistribute it and/or modify
4450- * it under the terms of the GNU General Public License version 3 as
4451- * published by the Free Software Foundation.
4452- *
4453- * This program is distributed in the hope that it will be useful,
4454- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4455- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4456- * GNU General Public License for more details.
4457- *
4458- * You should have received a copy of the GNU General Public License
4459- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4460- *
4461- */
4462-
4463-package snappy
4464-
4465-import (
4466- "bufio"
4467- "encoding/json"
4468- "fmt"
4469- "io"
4470- "io/ioutil"
4471- "net/http"
4472- "os"
4473- "os/exec"
4474- "path"
4475- "strings"
4476- "time"
4477-
4478- "github.com/mvo5/goconfigparser"
4479-
4480- "launchpad.net/snappy/helpers"
4481- "launchpad.net/snappy/progress"
4482-)
4483-
4484-var systemImageServer = "https://system-image.ubuntu.com/"
4485-
4486-type updateStatus struct {
4487- targetVersion string
4488- targetVersionDetails string
4489- updateSize int64
4490- lastUpdate time.Time
4491-}
4492-
4493-type channelImage struct {
4494- Descripton string `json:"description,omitempty"`
4495- Type string `json:"type, omitempty"`
4496- Version int `json:"version, omitempty"`
4497- VersionDetails string `json:"version_detail, omitempty"`
4498- Files []channelImageFiles `json:"files"`
4499-}
4500-
4501-type channelImageFiles struct {
4502- Size int64 `json:"size"`
4503-}
4504-
4505-type channelImageGlobal struct {
4506- GeneratedAt string `json:"generated_at"`
4507-}
4508-
4509-type channelJSON struct {
4510- Global channelImageGlobal `json:"global"`
4511- Images []channelImage `json:"images"`
4512-}
4513-
4514-func systemImageClientCheckForUpdates(configFile string) (us updateStatus, err error) {
4515- cfg := goconfigparser.New()
4516- if err := cfg.ReadFile(configFile); err != nil {
4517- return us, err
4518- }
4519- channel, _ := cfg.Get("service", "channel")
4520- device, _ := cfg.Get("service", "device")
4521-
4522- indexURL := systemImageServer + "/" + path.Join(channel, device, "index.json")
4523-
4524- resp, err := http.Get(indexURL)
4525- if err != nil {
4526- return us, err
4527- }
4528- defer resp.Body.Close()
4529-
4530- if resp.StatusCode != 200 {
4531- return us, fmt.Errorf("systemImageDbusProxy: unexpected http statusCode %v for %s", resp.StatusCode, indexURL)
4532- }
4533-
4534- // and decode json
4535- var channelData channelJSON
4536- dec := json.NewDecoder(resp.Body)
4537- if err := dec.Decode(&channelData); err != nil {
4538- return us, err
4539- }
4540-
4541- // global property
4542- us.lastUpdate, _ = time.Parse("Mon Jan 2 15:04:05 MST 2006", channelData.Global.GeneratedAt)
4543-
4544- // FIXME: find latest image of type "full" here
4545- latestImage := channelData.Images[len(channelData.Images)-1]
4546- us.targetVersion = fmt.Sprintf("%d", latestImage.Version)
4547- us.targetVersionDetails = latestImage.VersionDetails
4548-
4549- // FIXME: this is not accurate right now as it does not take
4550- // the deltas into account
4551- for _, f := range latestImage.Files {
4552- us.updateSize += f.Size
4553- }
4554-
4555- return us, nil
4556-}
4557-
4558-type genericJSON struct {
4559- Type string `json:"type, omitempty"`
4560- Message string `json:"msg, omitempty"`
4561- Now float64 `json:"now, omitempty"`
4562- Total float64 `json:"total, omitempty"`
4563-}
4564-
4565-func parseSIProgress(pb progress.Meter, stdout io.Reader) error {
4566- if pb == nil {
4567- pb = &progress.NullProgress{}
4568- }
4569-
4570- scanner := bufio.NewScanner(stdout)
4571- // s-i is funny, total changes during the runs
4572- total := 0.0
4573- pb.Start("ubuntu-core", 100)
4574-
4575- for scanner.Scan() {
4576- if os.Getenv("SNAPPY_DEBUG") != "" {
4577- fmt.Println(scanner.Text())
4578- }
4579-
4580- jsonStream := strings.NewReader(scanner.Text())
4581- dec := json.NewDecoder(jsonStream)
4582- var genericData genericJSON
4583- if err := dec.Decode(&genericData); err != nil {
4584- // we ignore invalid json here and continue
4585- // the parsing if s-i-cli or ubuntu-core-upgrader
4586- // output something unexpected (like stray debug
4587- // output or whatnot)
4588- continue
4589- }
4590-
4591- switch {
4592- case genericData.Type == "spinner":
4593- pb.Spin(genericData.Message)
4594- case genericData.Type == "error":
4595- return fmt.Errorf("error from %s: %s", systemImageCli, genericData.Message)
4596- case genericData.Type == "progress":
4597- if total != genericData.Total {
4598- total = genericData.Total
4599- pb.SetTotal(total)
4600- }
4601- pb.Set(genericData.Now)
4602- }
4603- }
4604- // ugly: avoid Spin() artifacts
4605- pb.Notify("\nApply done")
4606-
4607- if err := scanner.Err(); err != nil {
4608- return err
4609- }
4610-
4611- return nil
4612-}
4613-
4614-func systemImageDownloadUpdate(configDir string, pb progress.Meter) (err error) {
4615- cmd := exec.Command(systemImageCli, "--progress", "json", "-C", configDir)
4616-
4617- // collect progress over stdout pipe if we want progress
4618- var stdout io.Reader
4619- stdout, err = cmd.StdoutPipe()
4620- if err != nil {
4621- return err
4622- }
4623-
4624- // collect error message (traceback etc in a separate goroutine)
4625- stderr, err := cmd.StderrPipe()
4626- if err != nil {
4627- return err
4628- }
4629- stderrCh := make(chan []byte)
4630- go func() {
4631- stderrContent, _ := ioutil.ReadAll(stderr)
4632- stderrCh <- stderrContent
4633- }()
4634-
4635- // run it
4636- if err := cmd.Start(); err != nil {
4637- return err
4638- }
4639-
4640- // and parse progress synchronously
4641- if err := parseSIProgress(pb, stdout); err != nil {
4642- return err
4643- }
4644-
4645- // we need to read all of stderr *before* calling cmd.Wait() to avoid
4646- // a race, see docs for "os/exec:func (*Cmd) StdoutPipe"
4647- stderrContent := <-stderrCh
4648- if err := cmd.Wait(); err != nil {
4649- retCode, _ := helpers.ExitCode(err)
4650- return fmt.Errorf("%s failed with return code %v: %s", systemImageCli, retCode, string(stderrContent))
4651- }
4652-
4653- return err
4654-}
4655
4656=== removed file 'snappy/systemimage_native_test.go'
4657--- snappy/systemimage_native_test.go 2015-06-02 20:46:07 +0000
4658+++ snappy/systemimage_native_test.go 1970-01-01 00:00:00 +0000
4659@@ -1,123 +0,0 @@
4660-// -*- Mode: Go; indent-tabs-mode: t -*-
4661-
4662-/*
4663- * Copyright (C) 2014-2015 Canonical Ltd
4664- *
4665- * This program is free software: you can redistribute it and/or modify
4666- * it under the terms of the GNU General Public License version 3 as
4667- * published by the Free Software Foundation.
4668- *
4669- * This program is distributed in the hope that it will be useful,
4670- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4671- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4672- * GNU General Public License for more details.
4673- *
4674- * You should have received a copy of the GNU General Public License
4675- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4676- *
4677- */
4678-
4679-package snappy
4680-
4681-import (
4682- "fmt"
4683- "io"
4684- "net/http"
4685- "net/http/httptest"
4686- "path/filepath"
4687- "time"
4688-
4689- . "gopkg.in/check.v1"
4690-)
4691-
4692-/* acquired via:
4693- curl https://system-image.ubuntu.com/ubuntu-core/devel/generic_armhf/index.json
4694-*/
4695-const mockSystemImageIndexJSONTemplate = `{
4696- "global": {
4697- "generated_at": "Thu Feb 19 18:26:23 UTC 2015"
4698- },
4699- "images": [
4700- {
4701- "description": ",version=1",
4702- "files": [
4703- {
4704- "checksum": "3869be55a95db880862fe3dc3f5d643101736f674e9970a2099a883bd2bd2367",
4705- "order": 0,
4706- "path": "/pool/ubuntu-3f8ef532557aaec57264565b9948734bfe01f2a39886c816b0f69ae19e25f903.tar.xz",
4707- "signature": "/pool/ubuntu-3f8ef532557aaec57264565b9948734bfe01f2a39886c816b0f69ae19e25f903.tar.xz.asc",
4708- "size": 71081252
4709- },
4710- {
4711- "checksum": "f1a5ae7b4264e045ad58710a427168c02732e29130e5619dd2a82bbc8781b7c0",
4712- "order": 1,
4713- "path": "/pool/device-bf0a49e75a3deb99855f186906f17d7668e2dcc3a0b38f5feade3e6f7c75e9b8.tar.xz",
4714- "signature": "/pool/device-bf0a49e75a3deb99855f186906f17d7668e2dcc3a0b38f5feade3e6f7c75e9b8.tar.xz.asc",
4715- "size": 52589508
4716- },
4717- {
4718- "checksum": "a84fb49b505a797e1ddd6811d8cf79b5fe2a021198cd221e022318026dfb5417",
4719- "order": 2,
4720- "path": "/ubuntu-core/devel/generic_armhf/version-1.tar.xz",
4721- "signature": "/ubuntu-core/devel/generic_armhf/version-1.tar.xz.asc",
4722- "size": 328
4723- }
4724- ],
4725- "type": "full",
4726- "version": 1,
4727- "version_detail": ",version=1"
4728- },
4729- {
4730- "description": ",version=2",
4731- "files": [
4732- {
4733- "checksum": "8be1d2c82a6d785089de91febf70aa7aa790c23fae4f2e02c5dc4dfc343d005a",
4734- "order": 0,
4735- "path": "/pool/ubuntu-0e6f7a24f941a2fbe27c922b5158da9ab177afaa851c28c002a22f4166f3ec01.tar.xz",
4736- "signature": "/pool/ubuntu-0e6f7a24f941a2fbe27c922b5158da9ab177afaa851c28c002a22f4166f3ec01.tar.xz.asc",
4737- "size": 70576648
4738- },
4739- {
4740- "checksum": "f1a5ae7b4264e045ad58710a427168c02732e29130e5619dd2a82bbc8781b7c0",
4741- "order": 1,
4742- "path": "/pool/device-bf0a49e75a3deb99855f186906f17d7668e2dcc3a0b38f5feade3e6f7c75e9b8.tar.xz",
4743- "signature": "/pool/device-bf0a49e75a3deb99855f186906f17d7668e2dcc3a0b38f5feade3e6f7c75e9b8.tar.xz.asc",
4744- "size": 52589508
4745- },
4746- {
4747- "checksum": "242f0198b4bd0b63c943e7ff7eb46889753c725cb5b127a7bb76600bcecee544",
4748- "order": 2,
4749- "path": "/ubuntu-core/devel/generic_armhf/version-2.tar.xz",
4750- "signature": "/ubuntu-core/devel/generic_armhf/version-2.tar.xz.asc",
4751- "size": 332
4752- }
4753- ],
4754- "type": "full",
4755- "version": %s,
4756- "version_detail": ",version=2"
4757- }
4758- ]
4759-}`
4760-
4761-var mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "2")
4762-
4763-func runMockSystemImageWebServer() *httptest.Server {
4764- mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4765- io.WriteString(w, mockSystemImageIndexJSON)
4766- }))
4767- if mockServer == nil {
4768- return nil
4769- }
4770- systemImageServer = mockServer.URL
4771- return mockServer
4772-}
4773-
4774-func (s *SITestSuite) TestCheckForUpdates(c *C) {
4775- mockConfigFile := filepath.Join(c.MkDir(), "channel.init")
4776- makeFakeSystemImageChannelConfig(c, mockConfigFile, "1")
4777- updateStatus, err := systemImageClientCheckForUpdates(mockConfigFile)
4778- c.Assert(err, IsNil)
4779- c.Assert(updateStatus.targetVersion, Equals, "2")
4780- c.Assert(updateStatus.targetVersionDetails, Equals, ",version=2")
4781- c.Assert(updateStatus.lastUpdate, Equals, time.Date(2015, 02, 19, 18, 26, 23, 0, time.UTC))
4782-}
4783
4784=== removed file 'snappy/systemimage_test.go'
4785--- snappy/systemimage_test.go 2015-07-15 07:53:43 +0000
4786+++ snappy/systemimage_test.go 1970-01-01 00:00:00 +0000
4787@@ -1,486 +0,0 @@
4788-// -*- Mode: Go; indent-tabs-mode: t -*-
4789-
4790-/*
4791- * Copyright (C) 2014-2015 Canonical Ltd
4792- *
4793- * This program is free software: you can redistribute it and/or modify
4794- * it under the terms of the GNU General Public License version 3 as
4795- * published by the Free Software Foundation.
4796- *
4797- * This program is distributed in the hope that it will be useful,
4798- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4799- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4800- * GNU General Public License for more details.
4801- *
4802- * You should have received a copy of the GNU General Public License
4803- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4804- *
4805- */
4806-
4807-package snappy
4808-
4809-import (
4810- "fmt"
4811- "io/ioutil"
4812- "net/http/httptest"
4813- "os"
4814- "path/filepath"
4815- "strings"
4816- "testing"
4817-
4818- "launchpad.net/snappy/partition"
4819- "launchpad.net/snappy/provisioning"
4820-
4821- . "gopkg.in/check.v1"
4822-)
4823-
4824-// Hook up check.v1 into the "go test" runner
4825-func Test(t *testing.T) { TestingT(t) }
4826-
4827-type SITestSuite struct {
4828- systemImage *SystemImageRepository
4829- mockSystemImageWebServer *httptest.Server
4830-}
4831-
4832-var _ = Suite(&SITestSuite{})
4833-
4834-func (s *SITestSuite) SetUpTest(c *C) {
4835- newPartition = func() (p partition.Interface) {
4836- return new(MockPartition)
4837- }
4838-
4839- s.systemImage = NewSystemImageRepository()
4840- c.Assert(s, NotNil)
4841- // setup alternative root for system image
4842- tempdir := c.MkDir()
4843- systemImageRoot = tempdir
4844-
4845- makeFakeSystemImageChannelConfig(c, filepath.Join(tempdir, systemImageChannelConfig), "1")
4846- // setup fake /other partition
4847- makeFakeSystemImageChannelConfig(c, filepath.Join(tempdir, "other", systemImageChannelConfig), "0")
4848-
4849- // run test webserver instead of talking to the real one
4850- //
4851- // The mock webserver versions "1" and "2"
4852- s.mockSystemImageWebServer = runMockSystemImageWebServer()
4853- c.Assert(s.mockSystemImageWebServer, NotNil)
4854-
4855- // create mock system-image-cli
4856- systemImageCli = makeMockSystemImageCli(c, tempdir)
4857-}
4858-
4859-func (s *SITestSuite) TearDownTest(c *C) {
4860- s.mockSystemImageWebServer.Close()
4861- systemImageRoot = "/"
4862- bootloaderDir = bootloaderDirImpl
4863-}
4864-
4865-func makeMockSystemImageCli(c *C, tempdir string) string {
4866- s := `#!/bin/sh
4867-
4868-printf '{"type": "progress", "now": 20, "total":100}\n'
4869-printf '{"type": "progress", "now": 40, "total":100}\n'
4870-printf '{"type": "progress", "now": 60, "total":100}\n'
4871-printf '{"type": "progress", "now": 80, "total":100}\n'
4872-printf '{"type": "progress", "now": 100, "total":100}\n'
4873-printf '{"type": "spinner", "msg": "Applying"}\n'
4874-`
4875- mockScript := filepath.Join(tempdir, "system-image-cli")
4876- err := ioutil.WriteFile(mockScript, []byte(s), 0755)
4877- c.Assert(err, IsNil)
4878-
4879- return mockScript
4880-}
4881-
4882-func makeFakeSystemImageChannelConfig(c *C, cfgPath, buildNumber string) {
4883- os.MkdirAll(filepath.Dir(cfgPath), 0775)
4884- f, err := os.OpenFile(cfgPath, os.O_CREATE|os.O_RDWR, 0664)
4885- c.Assert(err, IsNil)
4886- defer f.Close()
4887- f.Write([]byte(fmt.Sprintf(`
4888-[service]
4889-base: system-image.ubuntu.com
4890-http_port: 80
4891-https_port: 443
4892-channel: ubuntu-core/devel-proposed
4893-device: generic_amd64
4894-build_number: %s
4895-version_detail: ubuntu=20141206,raw-device=20141206,version=77
4896-`, buildNumber)))
4897-}
4898-
4899-func (s *SITestSuite) TestTestInstalled(c *C) {
4900- // whats installed
4901- parts, err := s.systemImage.Installed()
4902- c.Assert(err, IsNil)
4903- // we have one active and one inactive
4904- c.Assert(parts, HasLen, 2)
4905- c.Assert(parts[0].Name(), Equals, systemImagePartName)
4906- c.Assert(parts[0].Origin(), Equals, systemImagePartOrigin)
4907- c.Assert(parts[0].Vendor(), Equals, systemImagePartVendor)
4908- c.Assert(parts[0].Version(), Equals, "1")
4909- c.Assert(parts[0].Hash(), Equals, "e09c13f68fccef3b2fe0f5c8ff5c61acf2173b170b1f2a3646487147690b0970ef6f2c555d7bcb072035f29ee4ea66a6df7f6bb320d358d3a7d78a0c37a8a549")
4910- c.Assert(parts[0].IsActive(), Equals, true)
4911- c.Assert(parts[0].Channel(), Equals, "ubuntu-core/devel-proposed")
4912-
4913- // second partition is not active and has a different version
4914- c.Assert(parts[1].IsActive(), Equals, false)
4915- c.Assert(parts[1].Version(), Equals, "0")
4916-}
4917-
4918-func (s *SITestSuite) TestUpdateNoUpdate(c *C) {
4919- mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "1")
4920- parts, err := s.systemImage.Updates()
4921- c.Assert(err, IsNil)
4922- c.Assert(parts, HasLen, 0)
4923-}
4924-
4925-func (s *SITestSuite) TestUpdateHasUpdate(c *C) {
4926- // add a update
4927- mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "2")
4928- parts, err := s.systemImage.Updates()
4929- c.Assert(err, IsNil)
4930- c.Assert(parts, HasLen, 1)
4931- c.Assert(parts[0].Name(), Equals, "ubuntu-core")
4932- c.Assert(parts[0].Version(), Equals, "2")
4933- c.Assert(parts[0].DownloadSize(), Equals, int64(123166488))
4934-}
4935-
4936-type MockPartition struct {
4937- toggleNextBootCalled bool
4938- markBootSuccessfulCalled bool
4939- syncBootloaderFilesCalled bool
4940-}
4941-
4942-func (p *MockPartition) ToggleNextBoot() error {
4943- p.toggleNextBootCalled = true
4944- return nil
4945-}
4946-
4947-func (p *MockPartition) MarkBootSuccessful() error {
4948- p.markBootSuccessfulCalled = true
4949- return nil
4950-}
4951-func (p *MockPartition) SyncBootloaderFiles(map[string]string) error {
4952- p.syncBootloaderFilesCalled = true
4953- return nil
4954-}
4955-func (p *MockPartition) IsNextBootOther() bool {
4956- return false
4957-}
4958-
4959-func (p *MockPartition) RunWithOther(option partition.MountOption, f func(otherRoot string) (err error)) (err error) {
4960- return f("/other")
4961-}
4962-
4963-// used by GetBootLoaderDir(), used to test sideload logic.
4964-var tempBootDir string
4965-
4966-func (p *MockPartition) BootloaderDir() string {
4967- return tempBootDir
4968-}
4969-
4970-func (s *SITestSuite) TestSystemImagePartInstallUpdatesPartition(c *C) {
4971- // FIXME: ideally we would change the version to "2" as a side-effect
4972- // of calling sp.Install() we need to update it because the
4973- // sp.Install() will verify that it got applied
4974- makeFakeSystemImageChannelConfig(c, filepath.Join(systemImageRoot, "other", systemImageChannelConfig), "2")
4975-
4976- // add a update
4977- mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "2")
4978- parts, err := s.systemImage.Updates()
4979- c.Assert(err, IsNil)
4980-
4981- sp := parts[0].(*SystemImagePart)
4982- mockPartition := MockPartition{}
4983- sp.partition = &mockPartition
4984-
4985- pb := &MockProgressMeter{}
4986- // do the install
4987- _, err = sp.Install(pb, 0)
4988- c.Assert(err, IsNil)
4989- c.Assert(mockPartition.toggleNextBootCalled, Equals, true)
4990- c.Assert(pb.total, Equals, 100.0)
4991- c.Assert(pb.spin, Equals, true)
4992- c.Assert(pb.spinMsg, Equals, "Applying")
4993- c.Assert(pb.finished, Equals, true)
4994- c.Assert(pb.progress, DeepEquals, []float64{20.0, 40.0, 60.0, 80.0, 100.0})
4995-}
4996-
4997-func (s *SITestSuite) TestSystemImagePartInstallUpdatesBroken(c *C) {
4998- // fake a broken upgrade
4999- scriptContent := `#!/bin/sh
5000-printf '{"type": "error", "msg": "some error msg"}\n'
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches