func snapsOp(c *Command, r *http.Request, user *auth.UserState) Response { route := c.d.router.Get(stateChangeCmd.Path) if route == nil { return InternalError("cannot find route for change") } decoder := json.NewDecoder(r.Body) var inst snapInstruction if err := decoder.Decode(&inst); err != nil { return BadRequest("cannot decode request body into snap instruction: %v", err) } if inst.Channel != "" || !inst.Revision.Unset() || inst.DevMode || inst.JailMode { return BadRequest("unsupported option provided for multi-snap operation") } st := c.d.overlord.State() st.Lock() defer st.Unlock() if user != nil { inst.userID = user.ID } var msg string var affected []string var tsets []*state.TaskSet var err error switch inst.Action { case "refresh": msg, affected, tsets, err = snapUpdateMany(&inst, st) case "install": msg, affected, tsets, err = snapInstallMany(&inst, st) case "remove": msg, affected, tsets, err = snapRemoveMany(&inst, st) default: return BadRequest("unsupported multi-snap operation %q", inst.Action) } if err != nil { return InternalError("cannot %s %q: %v", inst.Action, inst.Snaps, err) } var chg *state.Change if len(tsets) == 0 { chg = st.NewChange(inst.Action+"-snap", msg) chg.SetStatus(state.DoneStatus) } else { chg = newChange(st, inst.Action+"-snap", msg, tsets, affected) ensureStateSoon(st) } chg.Set("api-data", map[string]interface{}{"snap-names": affected}) return AsyncResponse(nil, &Meta{Change: chg.ID()}) }
func ensureChange(c *C, r *state.TaskRunner, sb *stateBackend, chg *state.Change) { for i := 0; i < 10; i++ { sb.ensureBefore = time.Hour r.Ensure() r.Wait() chg.State().Lock() s := chg.Status() chg.State().Unlock() if s.Ready() { return } if sb.ensureBefore > 0 { break } } var statuses []string chg.State().Lock() for _, t := range chg.Tasks() { statuses = append(statuses, t.Summary()+":"+t.Status().String()) } chg.State().Unlock() c.Fatalf("Change didn't reach final state without blocking: %s", strings.Join(statuses, " ")) }
func (s *deviceMgrSuite) TestFullDeviceRegistrationHappyPrepareDeviceHook(c *C) { r1 := devicestate.MockKeyLength(752) defer r1() mockServer := s.mockServer(c, "REQID-1") defer mockServer.Close() r2 := hookstate.MockRunHook(func(ctx *hookstate.Context, _ *tomb.Tomb) ([]byte, error) { c.Assert(ctx.HookName(), Equals, "prepare-device") // snapctl set the registration params _, _, err := ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("device-service.url=%q", mockServer.URL+"/identity/api/v1/")}) c.Assert(err, IsNil) h, err := json.Marshal(map[string]string{ "x-extra-header": "extra", }) c.Assert(err, IsNil) _, _, err = ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("device-service.headers=%s", string(h))}) c.Assert(err, IsNil) _, _, err = ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("registration.proposed-serial=%q", "Y9999")}) c.Assert(err, IsNil) d, err := yaml.Marshal(map[string]string{ "mac": "00:00:00:00:ff:00", }) c.Assert(err, IsNil) _, _, err = ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("registration.body=%q", d)}) c.Assert(err, IsNil) return nil, nil }) defer r2() // setup state as will be done by first-boot // & have a gadget with a prepare-device hook s.state.Lock() defer s.state.Unlock() s.setupGadget(c, ` name: gadget type: gadget version: gadget hooks: prepare-device: `, "") auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", Model: "pc", }) // runs the whole device registration process s.state.Unlock() s.settle() s.state.Lock() var becomeOperational *state.Change for _, chg := range s.state.Changes() { if chg.Kind() == "become-operational" { becomeOperational = chg break } } c.Assert(becomeOperational, NotNil) c.Check(becomeOperational.Status().Ready(), Equals, true) c.Check(becomeOperational.Err(), IsNil) device, err := auth.Device(s.state) c.Assert(err, IsNil) c.Check(device.Brand, Equals, "canonical") c.Check(device.Model, Equals, "pc") c.Check(device.Serial, Equals, "Y9999") a, err := s.db.Find(asserts.SerialType, map[string]string{ "brand-id": "canonical", "model": "pc", "serial": "Y9999", }) c.Assert(err, IsNil) serial := a.(*asserts.Serial) var details map[string]interface{} err = yaml.Unmarshal(serial.Body(), &details) c.Assert(err, IsNil) c.Check(details, DeepEquals, map[string]interface{}{ "mac": "00:00:00:00:ff:00", }) privKey, err := s.mgr.KeypairManager().Get(serial.DeviceKey().ID()) c.Assert(err, IsNil) c.Check(privKey, NotNil) c.Check(device.KeyID, Equals, privKey.PublicKey().ID()) }
func (s *deviceMgrSuite) TestFullDeviceRegistrationPollHappy(c *C) { r1 := devicestate.MockKeyLength(752) defer r1() mockServer := s.mockServer(c, "REQID-POLL") defer mockServer.Close() mockRequestIDURL := mockServer.URL + "/identity/api/v1/request-id" r2 := devicestate.MockRequestIDURL(mockRequestIDURL) defer r2() mockSerialRequestURL := mockServer.URL + "/identity/api/v1/devices" r3 := devicestate.MockSerialRequestURL(mockSerialRequestURL) defer r3() // immediately r4 := devicestate.MockRetryInterval(0) defer r4() // setup state as will be done by first-boot s.state.Lock() defer s.state.Unlock() s.setupGadget(c, ` name: gadget type: gadget version: gadget `, "") auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", Model: "pc", }) // runs the whole device registration process with polling s.state.Unlock() s.settle() s.state.Lock() var becomeOperational *state.Change for _, chg := range s.state.Changes() { if chg.Kind() == "become-operational" { becomeOperational = chg break } } c.Assert(becomeOperational, NotNil) c.Check(becomeOperational.Status().Ready(), Equals, true) c.Check(becomeOperational.Err(), IsNil) device, err := auth.Device(s.state) c.Assert(err, IsNil) c.Check(device.Brand, Equals, "canonical") c.Check(device.Model, Equals, "pc") c.Check(device.Serial, Equals, "10002") a, err := s.db.Find(asserts.SerialType, map[string]string{ "brand-id": "canonical", "model": "pc", "serial": "10002", }) c.Assert(err, IsNil) serial := a.(*asserts.Serial) privKey, err := s.mgr.KeypairManager().Get(serial.DeviceKey().ID()) c.Assert(err, IsNil) c.Check(privKey, NotNil) c.Check(device.KeyID, Equals, privKey.PublicKey().ID()) }
func change2changeInfo(chg *state.Change) *changeInfo { status := chg.Status() chgInfo := &changeInfo{ ID: chg.ID(), Kind: chg.Kind(), Summary: chg.Summary(), Status: status.String(), Ready: status.Ready(), SpawnTime: chg.SpawnTime(), } readyTime := chg.ReadyTime() if !readyTime.IsZero() { chgInfo.ReadyTime = &readyTime } if err := chg.Err(); err != nil { chgInfo.Err = err.Error() } tasks := chg.Tasks() taskInfos := make([]*taskInfo, len(tasks)) for j, t := range tasks { label, done, total := t.Progress() taskInfo := &taskInfo{ ID: t.ID(), Kind: t.Kind(), Summary: t.Summary(), Status: t.Status().String(), Log: t.Log(), Progress: taskInfoProgress{ Label: label, Done: done, Total: total, }, SpawnTime: t.SpawnTime(), } readyTime := t.ReadyTime() if !readyTime.IsZero() { taskInfo.ReadyTime = &readyTime } taskInfos[j] = taskInfo } chgInfo.Tasks = taskInfos var data map[string]*json.RawMessage if chg.Get("api-data", &data) == nil { chgInfo.Data = data } return chgInfo }