Skip to content

Commit

Permalink
overlord: make config defaults from gadget work also at first boot (m…
Browse files Browse the repository at this point in the history
…erge #3322)

This achieves that by:

* delaying retrieving the config defaults from gadget until we need to run the hook so that not knowing the values when creating the seeding change is not an issue
* postponing in the first boot logic the configure hook step for core, kernel and gadget after gadget is installed

LP: #16738152
  • Loading branch information
pedronis committed May 19, 2017
2 parents ef6d184 + 29daf2f commit 4b152b2
Show file tree
Hide file tree
Showing 13 changed files with 899 additions and 222 deletions.
4 changes: 3 additions & 1 deletion overlord/configstate/configstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ func Configure(s *state.State, snapName string, patch map[string]interface{}, fl
Timeout: configureHookTimeout(),
}
var contextData map[string]interface{}
if len(patch) > 0 {
if flags&snapstate.UseConfigDefaults != 0 {
contextData = map[string]interface{}{"use-defaults": true}
} else if len(patch) > 0 {
contextData = map[string]interface{}{"patch": patch}
}
var summary string
Expand Down
12 changes: 12 additions & 0 deletions overlord/configstate/configstate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ var configureTests = []struct {
patch map[string]interface{}
optional bool
ignoreError bool
useDefaults bool
}{{
patch: nil,
optional: true,
Expand All @@ -61,6 +62,11 @@ var configureTests = []struct {
patch: nil,
optional: true,
ignoreError: true,
}, {
patch: nil,
optional: true,
ignoreError: true,
useDefaults: true,
}}

func (s *tasksetsSuite) TestConfigure(c *C) {
Expand All @@ -69,6 +75,9 @@ func (s *tasksetsSuite) TestConfigure(c *C) {
if test.ignoreError {
flags |= snapstate.IgnoreHookError
}
if test.useDefaults {
flags |= snapstate.UseConfigDefaults
}

s.state.Lock()
taskset := configstate.Configure(s.state, "test-snap", test.patch, flags)
Expand Down Expand Up @@ -105,7 +114,9 @@ func (s *tasksetsSuite) TestConfigure(c *C) {
c.Check(context.HookName(), Equals, "configure")

var patch map[string]interface{}
var useDefaults bool
context.Lock()
context.Get("use-defaults", &useDefaults)
err = context.Get("patch", &patch)
context.Unlock()
if len(test.patch) > 0 {
Expand All @@ -115,5 +126,6 @@ func (s *tasksetsSuite) TestConfigure(c *C) {
c.Check(err, Equals, state.ErrNoState)
c.Check(patch, IsNil)
}
c.Check(useDefaults, Equals, test.useDefaults)
}
}
140 changes: 136 additions & 4 deletions overlord/configstate/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,39 @@
package configstate_test

import (
"io/ioutil"
"path/filepath"
"testing"

. "gopkg.in/check.v1"

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/overlord/configstate"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/hookstate/hooktest"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
)

func TestConfigState(t *testing.T) { TestingT(t) }

type configureHandlerSuite struct {
state *state.State
context *hookstate.Context
handler hookstate.Handler
}

var _ = Suite(&configureHandlerSuite{})

func (s *configureHandlerSuite) SetUpTest(c *C) {
state := state.New(nil)
state.Lock()
defer state.Unlock()
s.state = state.New(nil)
s.state.Lock()
defer s.state.Unlock()

task := state.NewTask("test-task", "my test task")
task := s.state.NewTask("test-task", "my test task")
setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"}

var err error
Expand Down Expand Up @@ -73,3 +80,128 @@ func (s *configureHandlerSuite) TestBeforeInitializesTransaction(c *C) {
c.Check(tr.Get("test-snap", "foo", &value), IsNil)
c.Check(value, Equals, "bar")
}

func (s *configureHandlerSuite) TestBeforeInitializesTransactionUseDefaults(c *C) {
r := release.MockOnClassic(false)
defer r()
dirs.SetRootDir(c.MkDir())
defer dirs.SetRootDir("/")

const mockGadgetSnapYaml = `
name: canonical-pc
type: gadget
`
var mockGadgetYaml = []byte(`
defaults:
test-snap-id:
bar: baz
num: 1.305
volumes:
volume-id:
bootloader: grub
`)

info := snaptest.MockSnap(c, mockGadgetSnapYaml, "SNAP", &snap.SideInfo{Revision: snap.R(1)})
err := ioutil.WriteFile(filepath.Join(info.MountDir(), "meta", "gadget.yaml"), mockGadgetYaml, 0644)
c.Assert(err, IsNil)

s.state.Lock()
snapstate.Set(s.state, "canonical-pc", &snapstate.SnapState{
Active: true,
Sequence: []*snap.SideInfo{
{RealName: "canonical-pc", Revision: snap.R(1)},
},
Current: snap.R(1),
SnapType: "gadget",
})

const mockTestSnapYaml = `
name: test-snap
hooks:
configure:
`

snaptest.MockSnap(c, mockTestSnapYaml, "SNAP", &snap.SideInfo{Revision: snap.R(11)})
snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
Active: true,
Sequence: []*snap.SideInfo{
{RealName: "test-snap", Revision: snap.R(11), SnapID: "test-snap-id"},
},
Current: snap.R(11),
SnapType: "app",
})
s.state.Unlock()

// Initialize context
s.context.Lock()
s.context.Set("use-defaults", true)
s.context.Unlock()

c.Check(s.handler.Before(), IsNil)

s.context.Lock()
tr := configstate.ContextTransaction(s.context)
s.context.Unlock()

var value string
c.Check(tr.Get("test-snap", "bar", &value), IsNil)
c.Check(value, Equals, "baz")
var fl float64
c.Check(tr.Get("test-snap", "num", &fl), IsNil)
c.Check(fl, Equals, 1.305)
}

func (s *configureHandlerSuite) TestBeforeUseDefaultsMissingHook(c *C) {
r := release.MockOnClassic(false)
defer r()
dirs.SetRootDir(c.MkDir())
defer dirs.SetRootDir("/")

const mockGadgetSnapYaml = `
name: canonical-pc
type: gadget
`
var mockGadgetYaml = []byte(`
defaults:
test-snap-id:
bar: baz
num: 1.305
volumes:
volume-id:
bootloader: grub
`)

info := snaptest.MockSnap(c, mockGadgetSnapYaml, "SNAP", &snap.SideInfo{Revision: snap.R(1)})
err := ioutil.WriteFile(filepath.Join(info.MountDir(), "meta", "gadget.yaml"), mockGadgetYaml, 0644)
c.Assert(err, IsNil)

s.state.Lock()
snapstate.Set(s.state, "canonical-pc", &snapstate.SnapState{
Active: true,
Sequence: []*snap.SideInfo{
{RealName: "canonical-pc", Revision: snap.R(1)},
},
Current: snap.R(1),
SnapType: "gadget",
})

snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
Active: true,
Sequence: []*snap.SideInfo{
{RealName: "test-snap", Revision: snap.R(11), SnapID: "test-snap-id"},
},
Current: snap.R(11),
SnapType: "app",
})
s.state.Unlock()

// Initialize context
s.context.Lock()
s.context.Set("use-defaults", true)
s.context.Unlock()

err = s.handler.Before()
c.Check(err, ErrorMatches, `cannot apply gadget config defaults for snap "test-snap", no configure hook`)
}
40 changes: 36 additions & 4 deletions overlord/configstate/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
package configstate

import (
"fmt"

"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
)

// configureHandler is the handler for the configure hook.
Expand Down Expand Up @@ -66,13 +70,41 @@ func (h *configureHandler) Before() error {
tr := ContextTransaction(h.context)

// Initialize the transaction if there's a patch provided in the
// context.
// context or useDefaults is set in which case gadget defaults are used.

var patch map[string]interface{}
if err := h.context.Get("patch", &patch); err == nil {
for key, value := range patch {
if err := tr.Set(h.context.SnapName(), key, value); err != nil {
var useDefaults bool
if err := h.context.Get("use-defaults", &useDefaults); err != nil && err != state.ErrNoState {
return err
}

snapName := h.context.SnapName()
st := h.context.State()
if useDefaults {
var err error
patch, err = snapstate.ConfigDefaults(st, snapName)
if err != nil && err != state.ErrNoState {
return err
}
if len(patch) != 0 {
// TODO: helper on context?
info, err := snapstate.CurrentInfo(st, snapName)
if err != nil {
return err
}
if info.Hooks["configure"] == nil {
return fmt.Errorf("cannot apply gadget config defaults for snap %q, no configure hook", snapName)
}
}
} else {
if err := h.context.Get("patch", &patch); err != nil && err != state.ErrNoState {
return err
}
}

for key, value := range patch {
if err := tr.Set(snapName, key, value); err != nil {
return err
}
}

Expand Down
Loading

0 comments on commit 4b152b2

Please sign in to comment.