Merge lp:~mvo/snappy/snappy-all-snap2 into lp:~snappy-dev/snappy/snappy-moved-to-github
- snappy-all-snap2
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Snappy Developers | Pending | ||
Review via email: mp+269922@code.launchpad.net |
Commit message
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 PartitionTestSu
ite->Bootloader TestSuite - 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.