func (s *setSuite) TestCommand(c *C) { stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"set", "foo=bar", "baz=qux"}) c.Check(err, IsNil) c.Check(string(stdout), Equals, "") c.Check(string(stderr), Equals, "") // Verify that the previous set doesn't modify the global state s.mockContext.State().Lock() transaction, err := configstate.NewTransaction(s.mockContext.State()) s.mockContext.State().Unlock() c.Check(err, IsNil) var value string c.Check(transaction.Get("test-snap", "foo", &value), ErrorMatches, ".*snap.*has no.*configuration.*") c.Check(transaction.Get("test-snap", "baz", &value), ErrorMatches, ".*snap.*has no.*configuration.*") // Notify the context that we're done. This should save the config. s.mockContext.Lock() defer s.mockContext.Unlock() c.Check(s.mockContext.Done(), IsNil) // Verify that the global config has been updated. transaction, err = configstate.NewTransaction(s.mockContext.State()) c.Check(err, IsNil) c.Check(transaction.Get("test-snap", "foo", &value), IsNil) c.Check(value, Equals, "bar") c.Check(transaction.Get("test-snap", "baz", &value), IsNil) c.Check(value, Equals, "qux") }
func (s *setSuite) TestCommandSavesDeltasOnly(c *C) { // Setup an initial configuration s.mockContext.State().Lock() transaction, err := configstate.NewTransaction(s.mockContext.State()) c.Check(err, IsNil) transaction.Set("test-snap", "test-key1", "test-value1") transaction.Set("test-snap", "test-key2", "test-value2") transaction.Commit() s.mockContext.State().Unlock() stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"set", "test-key2=test-value3"}) c.Check(err, IsNil) c.Check(string(stdout), Equals, "") c.Check(string(stderr), Equals, "") // Notify the context that we're done. This should save the config. s.mockContext.Lock() defer s.mockContext.Unlock() c.Check(s.mockContext.Done(), IsNil) // Verify that the global config has been updated, but only test-key2 transaction, err = configstate.NewTransaction(s.mockContext.State()) c.Check(err, IsNil) var value string c.Check(transaction.Get("test-snap", "test-key1", &value), IsNil) c.Check(value, Equals, "test-value1") c.Check(transaction.Get("test-snap", "test-key2", &value), IsNil) c.Check(value, Equals, "test-value3") }
func (s *transactionSuite) TestCommitOnlyCommitsChanges(c *C) { // Set the initial config s.state.Lock() defer s.state.Unlock() c.Check(s.transaction.Set("test-snap", "foo", "bar"), IsNil) s.transaction.Commit() // Create two new transactions transaction1, err := configstate.NewTransaction(s.state) c.Check(err, IsNil) transaction2, err := configstate.NewTransaction(s.state) c.Check(err, IsNil) // transaction1 will change the configuration item that is already present. c.Check(transaction1.Set("test-snap", "foo", "baz"), IsNil) transaction1.Commit() // transaction2 will add a new configuration item. c.Check(transaction2.Set("test-snap", "qux", "quux"), IsNil) transaction2.Commit() // Now verify that the change made by both transactions actually took place // (i.e. transaction1's change was not overridden by the old data in // transaction2). transaction, err := configstate.NewTransaction(s.state) c.Check(err, IsNil) var value string c.Check(transaction.Get("test-snap", "foo", &value), IsNil) c.Check(value, Equals, "baz", Commentf("Expected 'test-snap' value for 'foo' to be set by transaction1")) c.Check(transaction.Get("test-snap", "qux", &value), IsNil) c.Check(value, Equals, "quux", Commentf("Expected 'test-snap' value for 'qux' to be set by transaction2")) }
func (s *getSuite) TestGetTests(c *C) { for _, test := range getTests { c.Logf("Test: %s", test.args) mockHandler := hooktest.NewMockHandler() state := state.New(nil) state.Lock() task := state.NewTask("test-task", "my test task") setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} var err error mockContext, err := hookstate.NewContext(task, setup, mockHandler) c.Check(err, IsNil) // Initialize configuration t := configstate.NewTransaction(state) t.Set("test-snap", "test-key1", "test-value1") t.Set("test-snap", "test-key2", 2) t.Commit() state.Unlock() stdout, stderr, err := ctlcmd.Run(mockContext, strings.Fields(test.args)) if test.error != "" { c.Check(err, ErrorMatches, test.error) } else { c.Check(err, IsNil) c.Check(string(stderr), Equals, "") c.Check(string(stdout), Equals, test.stdout) } } }
func getSnapConf(c *Command, r *http.Request, user *auth.UserState) Response { vars := muxVars(r) snapName := vars["name"] keys := strings.Split(r.URL.Query().Get("keys"), ",") if len(keys) == 0 { return BadRequest("cannot obtain configuration: no keys supplied") } s := c.d.overlord.State() s.Lock() transaction, err := configstate.NewTransaction(s) s.Unlock() if err != nil { return BadRequest("cannot create transaction: %s", err) } currentConfValues := make(map[string]interface{}) for _, key := range keys { var value interface{} if err := transaction.Get(snapName, key, &value); err != nil { return BadRequest("%s", err) } currentConfValues[key] = value } return SyncResponse(currentConfValues, nil) }
func (s *transactionSuite) SetUpTest(c *C) { s.state = state.New(nil) var err error s.state.Lock() defer s.state.Unlock() s.transaction, err = configstate.NewTransaction(s.state) c.Check(err, IsNil) }
func (s *transactionSuite) TestSetDoesNotTouchState(c *C) { c.Check(s.transaction.Set("test-snap", "foo", "bar"), IsNil) // Create a new transaction to grab a new snapshot of the state s.state.Lock() defer s.state.Unlock() transaction, err := configstate.NewTransaction(s.state) c.Check(err, IsNil) var value string err = transaction.Get("test-snap", "foo", &value) c.Check(err, NotNil, Commentf("Expected config set by first transaction to not be saved")) }
func (s *transactionSuite) TestIsolationFromOtherTransactions(c *C) { // Set the initial config s.state.Lock() defer s.state.Unlock() c.Check(s.transaction.Set("test-snap", "foo", "initial"), IsNil) s.transaction.Commit() // Create two new transactions transaction1, err := configstate.NewTransaction(s.state) c.Check(err, IsNil) transaction2, err := configstate.NewTransaction(s.state) c.Check(err, IsNil) // Change the config in one c.Check(transaction1.Set("test-snap", "foo", "updated"), IsNil) transaction1.Commit() // Verify that the other transaction doesn't see the changes var value string c.Check(transaction2.Get("test-snap", "foo", &value), IsNil) c.Check(value, Equals, "initial", Commentf("Expected transaction2 to be isolated from transaction1")) }
func (s *transactionSuite) TestCommit(c *C) { s.state.Lock() defer s.state.Unlock() c.Check(s.transaction.Set("test-snap", "foo", "bar"), IsNil) s.transaction.Commit() // Create a new transaction to grab a new snapshot of the state transaction, err := configstate.NewTransaction(s.state) c.Check(err, IsNil) var value string err = transaction.Get("test-snap", "foo", &value) c.Check(err, IsNil, Commentf("Expected config set by first transaction to be saved")) c.Check(value, Equals, "bar") }
func (s *transactionSuite) TestGetOriginalEvenWithCachedWrites(c *C) { // Set the initial config s.state.Lock() defer s.state.Unlock() c.Check(s.transaction.Set("test-snap", "foo", "bar"), IsNil) s.transaction.Commit() transaction, err := configstate.NewTransaction(s.state) c.Check(err, IsNil) c.Check(transaction.Set("test-snap", "baz", "qux"), IsNil) // Now get both the cached write as well as the initial config var value string c.Check(transaction.Get("test-snap", "foo", &value), IsNil) c.Check(value, Equals, "bar") c.Check(transaction.Get("test-snap", "baz", &value), IsNil) c.Check(value, Equals, "qux") }
func (s *getSuite) SetUpTest(c *C) { s.mockHandler = hooktest.NewMockHandler() state := state.New(nil) state.Lock() defer state.Unlock() task := state.NewTask("test-task", "my test task") setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} var err error s.mockContext, err = hookstate.NewContext(task, setup, s.mockHandler) c.Assert(err, IsNil) // Initialize configuration transaction := configstate.NewTransaction(state) transaction.Set("test-snap", "initial-key", "initial-value") transaction.Commit() }
func (s *transactionSuite) TestGetUnmarshalError(c *C) { s.state.Lock() defer s.state.Unlock() c.Check(s.transaction.Set("test-snap", "foo", "good"), IsNil) s.transaction.Commit() transaction := configstate.NewTransaction(s.state) c.Check(transaction.Set("test-snap", "foo", "break"), IsNil) // Pristine state is good, value in the transaction breaks. broken := brokenType{`"break"`} err := transaction.Get("test-snap", "foo", &broken) c.Assert(err, ErrorMatches, ".*BAM!.*") // Pristine state breaks, nothing in the transaction. transaction.Commit() err = transaction.Get("test-snap", "foo", &broken) c.Assert(err, ErrorMatches, ".*BAM!.*") }
func getTransaction(context *hookstate.Context) (*configstate.Transaction, error) { context.Lock() defer context.Unlock() // Extract the transaction from the context. If none, make one and cache it // in the context. transaction, ok := context.Cached(cachedTransaction{}).(*configstate.Transaction) if !ok { var err error transaction, err = configstate.NewTransaction(context.State()) if err != nil { return nil, fmt.Errorf("cannot create transaction: %s", err) } context.OnDone(func() error { transaction.Commit() return nil }) context.Cache(cachedTransaction{}, transaction) } return transaction, nil }
func getSerialRequestConfig(t *state.Task) (*serialRequestConfig, error) { gadgetInfo, err := snapstate.GadgetInfo(t.State()) if err != nil { return nil, fmt.Errorf("cannot find gadget snap and its name: %v", err) } gadgetName := gadgetInfo.Name() tr := configstate.NewTransaction(t.State()) var svcURL string err = tr.GetMaybe(gadgetName, "device-service.url", &svcURL) if err != nil { return nil, err } if svcURL != "" { baseURL, err := url.Parse(svcURL) if err != nil { return nil, fmt.Errorf("cannot parse device registration base URL %q: %v", svcURL, err) } var headers map[string]string err = tr.GetMaybe(gadgetName, "device-service.headers", &headers) if err != nil { return nil, err } cfg := serialRequestConfig{ headers: headers, } reqIDURL, err := baseURL.Parse("request-id") if err != nil { return nil, fmt.Errorf("cannot build /request-id URL from %v: %v", baseURL, err) } cfg.requestIDURL = reqIDURL.String() var bodyStr string err = tr.GetMaybe(gadgetName, "registration.body", &bodyStr) if err != nil { return nil, err } cfg.body = []byte(bodyStr) serialURL, err := baseURL.Parse("serial") if err != nil { return nil, fmt.Errorf("cannot build /serial URL from %v: %v", baseURL, err) } cfg.serialRequestURL = serialURL.String() var proposedSerial string err = tr.GetMaybe(gadgetName, "registration.proposed-serial", &proposedSerial) if err != nil { return nil, err } cfg.proposedSerial = proposedSerial return &cfg, nil } return &serialRequestConfig{ requestIDURL: requestIDURL, serialRequestURL: serialRequestURL, }, nil }
func (s *transactionSuite) SetUpTest(c *C) { s.state = state.New(nil) s.state.Lock() defer s.state.Unlock() s.transaction = configstate.NewTransaction(s.state) }
func (s *transactionSuite) TestSetGet(c *C) { s.state.Lock() defer s.state.Unlock() for _, test := range setGetTests { c.Logf("-----") s.state.Set("config", map[string]interface{}{}) t := configstate.NewTransaction(s.state) snap := "core" for _, op := range test { c.Logf("%s", op) switch op.kind() { case "set": for k, v := range op.args() { err := t.Set(snap, k, v) if op.fails() { c.Assert(err, ErrorMatches, op.error()) } else { c.Assert(err, IsNil) } } case "get": for k, expected := range op.args() { var obtained interface{} err := t.Get(snap, k, &obtained) if op.fails() { c.Assert(err, ErrorMatches, op.error()) var nothing interface{} c.Assert(t.GetMaybe(snap, k, ¬hing), ErrorMatches, op.error()) c.Assert(nothing, IsNil) continue } if expected == "-" { if !configstate.IsNoOption(err) { c.Fatalf("Expected %q key to not exist, but it has value %v", k, obtained) } c.Assert(err, ErrorMatches, fmt.Sprintf("snap %q has no %q configuration option", snap, k)) var nothing interface{} c.Assert(t.GetMaybe(snap, k, ¬hing), IsNil) c.Assert(nothing, IsNil) continue } c.Assert(err, IsNil) c.Assert(obtained, DeepEquals, expected) obtained = nil c.Assert(t.GetMaybe(snap, k, &obtained), IsNil) c.Assert(obtained, DeepEquals, expected) } case "commit": t.Commit() case "setunder": var config map[string]map[string]interface{} s.state.Get("config", &config) if config == nil { config = make(map[string]map[string]interface{}) } if config[snap] == nil { config[snap] = make(map[string]interface{}) } for k, v := range op.args() { if v == "-" { delete(config[snap], k) if len(config[snap]) == 0 { delete(config[snap], snap) } } else { config[snap][k] = v } } s.state.Set("config", config) case "getunder": var config map[string]map[string]interface{} s.state.Get("config", &config) for k, expected := range op.args() { obtained, ok := config[snap][k] if expected == "-" { if ok { c.Fatalf("Expected %q key to not exist, but it has value %v", k, obtained) } continue } c.Assert(obtained, DeepEquals, expected) } default: panic("unknown test op kind: " + op.kind()) } } } }