Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

partition,snap: add support for android boot #3324

Merged
merged 11 commits into from
May 19, 2017

Conversation

alfonsosanchezbeato
Copy link
Member

@alfonsosanchezbeato alfonsosanchezbeato commented May 16, 2017

Support for android style bootloading

@zyga zyga changed the title Androidboot partition,snap: add support for android boot May 16, 2017
morphis and others added 4 commits May 16, 2017 09:15
Rename fastboot bootloader to android-boot, as this is a more accurate
description (fastboot is a protocol). android-boot is chosen because
what we support is booting from devices that are configured in the
usual way for Android devices, with recovery and boot partitions and
boot images with format as defined in
https://android.googlesource.com/platform/system/core/+/master/mkbootimg/bootimg.h
Copy link
Contributor

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments / early feedback.

// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2014-2017 Canonical Ltd
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: was this written in 2014?

}

func (a *androidboot) GetBootVars(names ...string) (map[string]string, error) {
out := make(map[string]string)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: move this closer to the loop that populates it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

furthermore: you know how big it needs to be (len(names)), so make it with that size already

. "gopkg.in/check.v1"
)

func mockAndroidbootFile(c *C, newPath string, mode os.FileMode) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we not have a SetUpTest function that calls dirs.SetRootDir(c.MkDir())? This should be paired with TearDownTest that calls dirs.SetRootDir("")

func (s *PartitionTestSuite) TestNewAndroidbootNoAndroidbootReturnsNil(c *C) {
s.makeFakeAndroidbootConfig(c)

dirs.GlobalRootDir = "/something/not/there"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't want this, see my comment above

c.Assert(err, IsNil)
}

func (s *PartitionTestSuite) makeFakeAndroidbootConfig(c *C) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this happens to be done in each test feel free to do it in SetUpTest

return err
}

rawEnv := bytes.Split(buf, []byte("\n"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be nicer with a bufio.Scanner

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this whole method needs restructuring around that. Please.

l := bytes.SplitN(env, []byte("="), 2)
// be liberal in what you accept
if len(l) < 2 {
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: please log weird stuff with logger.Noticef

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or logger.Debugf if it's weird but harmless)

return err
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the place to use our atomic write helpers?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, one nice thing about bytes.Buffer is that you can just declare it and start using it; NewBuffer is only needed when you want to preload the buffer with something. Otherwise, just

var w bytes.Buffer

and you're done (although some things will need you to pass a pointer to this, e.g. things expecting an io.Writer).

Copy link
Contributor

@chipaca chipaca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice, thank you!

A bunch of small things to fix, mostly idiomatic and not conceptual.

}

func (a *androidboot) GetBootVars(names ...string) (map[string]string, error) {
out := make(map[string]string)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

furthermore: you know how big it needs to be (len(names)), so make it with that size already

l := bytes.SplitN(env, []byte("="), 2)
// be liberal in what you accept
if len(l) < 2 {
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or logger.Debugf if it's weird but harmless)

return err
}

rawEnv := bytes.Split(buf, []byte("\n"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this whole method needs restructuring around that. Please.

return err
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep :-)

*
*/

package partition
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you make this package partition_test, please? (maybe use export_test.go to get to the private bits you need for testing)


a := newAndroidboot()
bootVars := map[string]string{}
bootVars["snap_mode"] = "try"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could directly

bootVars := map[string]string{"snap_mode": "try"}

return err
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, one nice thing about bytes.Buffer is that you can just declare it and start using it; NewBuffer is only needed when you want to preload the buffer with something. Otherwise, just

var w bytes.Buffer

and you're done (although some things will need you to pass a pointer to this, e.g. things expecting an io.Writer).


func (a *androidbootenvTestSuite) TestSaveAndLoad(c *C) {
env := androidbootenv.NewEnv(a.envPath)
c.Check(env, NotNil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here this should probably be an Assert (otherwise all the other tests will panic)


func (a *androidbootenvTestSuite) TestSet(c *C) {
env := androidbootenv.NewEnv(a.envPath)
c.Check(env, NotNil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... Assert :-)

snap/gadget.go Outdated
foundBootloader = true
default:
return nil, fmt.Errorf(errorFormat, "bootloader must be either grub or u-boot")
return nil, fmt.Errorf(errorFormat, "bootloader must be either grub, u-boot or android-boot")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/either/one of/

Copy link
Contributor

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let us know if there's anything we can do to help.

@alfonsosanchezbeato
Copy link
Member Author

PR resubmitted after addressing the requested changes. @zyga and @chipaca thanks for your thorough review and for being patient with a golang newbie like me :)

Copy link
Contributor

@mvo5 mvo5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very nice, thanks for doing this work. I have some comments inside that might be worth considering. But nothing major, it looks pretty nice.

)

// creates a new Androidboot bootloader object
func MockNewAndroidboot() Bootloader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nitpick) this can probably just be NewAndroidboot() nothing is mocked/faked here its just exported for the tests.

func MockAndroidbootFile(c *C, mode os.FileMode) {
f := &androidboot{}
newpath := filepath.Join(dirs.GlobalRootDir, "/boot/androidboot")
os.MkdirAll(newpath, os.ModePerm)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nitpick) I think it makes sense to get the err from os.MkdirAll() and c.Assert(err, IsNil).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, os.ModePerm is 0777, even in its a test I would use 0755 here instead (just in case umask is funny etc).


func MockAndroidbootFile(c *C, mode os.FileMode) {
f := &androidboot{}
newpath := filepath.Join(dirs.GlobalRootDir, "/boot/androidboot")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nitpick) might be easier to do: newpath := filepath.Dir(f.ConfigFile()) (or f.Dir()), then you don't need to encode the knowledge of the exact path into this test.

file, err := os.Open(a.path)
if err != nil {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defer file.Close() is missing here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(fixed, below)

Copy link
Contributor

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much nicer, thank you! A few more suggestions.


out := make(map[string]string, len(names))
for _, name := range names {
out[name] = env.Get(name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can there be any duplicates in names?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, it would be a bug either in snapd or in the bootloader if that happens.

if err := env.Load(); err != nil && !os.IsNotExist(err) {
return err
}
for k, v := range values {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to worry about overwriting values here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, values should not be duplicated

func (g *androidbootTestSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())

// the file needs to exist
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more useful to explain why the file has to exist.

}

func (s *androidbootTestSuite) TestNewAndroidbootNoAndroidbootReturnsNil(c *C) {
dirs.SetRootDir("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except when ran on android. I would suggest you to actually keep using the temporary random directory and populate it (or not populate it perhaps) so that it has the right data for the test. This will just run it on the real system which is not what unit tests are about.

}

func (s *androidbootTestSuite) TestSetGetBootVar(c *C) {
a := partition.MockNewAndroidboot()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move this to the SetUpTest function as it seems to happen in all the tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestNewAndroidbootNoAndroidbootReturnsNil is unfortunately an exception.

"github.com/snapcore/snapd/osutil"
)

type Env struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some documentation (tip: run golint in the directory) would help here.

l := strings.SplitN(scanner.Text(), "=", 2)
// be liberal in what you accept
if len(l) < 2 {
logger.Noticef("WARNING: bad value while parsing %v", a.path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be invaluable to actually log scanner.Text() with %q here.

}

func (a *androidbootenvTestSuite) TestSet(c *C) {
env := androidbootenv.NewEnv(a.envPath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env := ... can be moved to SetUpTest

)

// creates a new Androidboot bootloader object
func MockNewAndroidboot() Bootloader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a mock, it's doing the real thing.

Instead do this:

var NewAndroidBoot = newAndroidBoot

(Note that I also changed the capitalization of androidboot to AndroidBoot)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed but to NewAndroidBoot instead

return newAndroidboot()
}

func MockAndroidbootFile(c *C, mode os.FileMode) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: spell it as androidBoot everywhere

Copy link
Contributor

@chipaca chipaca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nearly there! marking approved by me as my concerns are raised by zyga and mvo already :-)

@alfonsosanchezbeato
Copy link
Member Author

Thanks again for the reviews, PR refreshed after addressing comments.

Copy link
Contributor

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last set of nitpicks. LGTM then

}
a.env[l[0]] = l[1]
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to use the scanner.Err here. See https://golang.org/src/bufio/example_test.go for examples.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, done

func (a *Env) Save() error {
var w bytes.Buffer

for k, v := range a.env {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we care about deterministic output here? If so please sort the keys, and write in order.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, neither the booloader or snapd cares about the order in which the variables are stored.

a.env.Set("key3", "value3")

err := a.env.Save()
c.Assert(err, IsNil)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a trivial test that ensures the output is as you expect, as in the actual string is what we expect (deterministic)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous comment

@alfonsosanchezbeato
Copy link
Member Author

PR repushed after addressing comments

@codecov-io
Copy link

Codecov Report

Merging #3324 into master will decrease coverage by 0.01%.
The diff coverage is 73.77%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3324      +/-   ##
==========================================
- Coverage    77.6%   77.59%   -0.02%     
==========================================
  Files         364      366       +2     
  Lines       24956    25014      +58     
==========================================
+ Hits        19368    19409      +41     
- Misses       3865     3876      +11     
- Partials     1723     1729       +6
Impacted Files Coverage Δ
snap/gadget.go 82.35% <100%> (ø) ⬆️
partition/bootloader.go 80.55% <33.33%> (-4.74%) ⬇️
partition/androidbootenv/androidbootenv.go 66.66% <66.66%> (ø)
partition/androidboot.go 84.61% <84.61%> (ø)
interfaces/sorting.go 93.33% <0%> (-3.34%) ⬇️
overlord/ifacestate/helpers.go 62.86% <0%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update eee977f...7824302. Read the comment docs.

Copy link
Contributor

@zyga zyga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, there are some tweaks we could do but I won't mind doing it in a follow-up.

@zyga zyga merged commit 004f307 into canonical:master May 19, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants