func (cts *ClientTestServer) logReq(w http.ResponseWriter, r *http.Request, id string) { log.Infof("got log request deployment ID: %v, %v", id, r) cts.Log.Called = true if !isMethod(http.MethodPut, w, r) { return } if !isContentType("application/json", w, r) { return } if !cts.verifyAuth(w, r) { return } logs, err := ioutil.ReadAll(r.Body) if err != nil { log.Errorf("error when receiving logs: %v", err) w.WriteHeader(http.StatusBadRequest) return } log.Infof("got logs: %v", logs) cts.Log.Logs = logs w.WriteHeader(http.StatusNoContent) }
// Write writes data `p` to underlying block device. Will automatically open // the device in a write mode. Otherwise, behaves like io.Writer. func (bd *BlockDevice) Write(p []byte) (int, error) { if bd.out == nil { log.Infof("opening device %s for writing", bd.Path) out, err := os.OpenFile(bd.Path, os.O_WRONLY, 0) if err != nil { return 0, err } size, err := BlockDeviceGetSizeOf(out) if err != nil { log.Errorf("failed to read block device size: %v", err) out.Close() return 0, err } log.Infof("partition %s size: %v", bd.Path, size) bd.out = out bd.w = &utils.LimitedWriter{ W: out, N: size, } } w, err := bd.w.Write(p) if err != nil { log.Errorf("written %v out of %v bytes to partition %s: %v", w, len(p), bd.Path, err) } return w, err }
func (cts *ClientTestServer) inventoryReq(w http.ResponseWriter, r *http.Request) { log.Infof("got inventory request %v", r) cts.Inventory.Called = true if !isMethod(http.MethodPatch, w, r) { return } if !isContentType("application/json", w, r) { return } if !cts.verifyAuth(w, r) { return } var attrs []client.InventoryAttribute if err := fromJSON(r.Body, &attrs); err != nil { log.Errorf("failed to parse attrs data: %v", err) w.WriteHeader(http.StatusBadRequest) return } log.Infof("got attrs: %v", attrs) cts.Inventory.Attrs = attrs w.WriteHeader(http.StatusOK) }
func (a *AuthorizedState) Handle(ctx *StateContext, c Controller) (State, bool) { // restore previous state information sd, err := LoadStateData(ctx.store) // tricky part - try to figure out if there's an update in progress, if so // proceed to UpdateCommitState; in case of errors that occur either now or // when the update was being feched/installed previously, try to handle them // gracefully // handle easy case first, no update info present, means no update in progress if err != nil && os.IsNotExist(err) { log.Debug("no update in progress, proceed") return inventoryUpdateState, false } if err != nil { log.Errorf("failed to restore update information: %v", err) me := NewFatalError(errors.Wrapf(err, "failed to restore update information")) // report update error with unknown deployment ID // TODO: fill current image ID? return NewUpdateErrorState(me, client.UpdateResponse{ ID: "unknown", }), false } log.Infof("handling state: %v", sd.Id) // chack last known status switch sd.Id { // update process was finished; check what is the status of update case MenderStateReboot: return NewUpdateVerifyState(sd.UpdateInfo), false // update prosess was initialized but stopped in the middle case MenderStateUpdateFetch, MenderStateUpdateInstall: // TODO: for now we just continue sending error report to the server // in future we might want to have some recovery option here me := NewFatalError(errors.New("update process was interrupted")) return NewUpdateErrorState(me, sd.UpdateInfo), false // there was some error while reporting update status case MenderStateUpdateStatusReport: log.Infof("restoring update status report state") if sd.UpdateStatus != client.StatusFailure && sd.UpdateStatus != client.StatusSuccess { return NewUpdateStatusReportState(sd.UpdateInfo, client.StatusError), false } // check what is exact state of update before reporting anything return NewUpdateVerifyState(sd.UpdateInfo), false // this should not happen default: log.Errorf("got invalid update state: %v", sd.Id) me := NewFatalError(errors.New("got invalid update state")) return NewUpdateErrorState(me, sd.UpdateInfo), false } }
func (usr *UpdateStatusReportState) trySend(send SendData, c Controller) (error, bool) { poll := c.GetUpdatePollInterval() if poll == 0 { poll = 5 * time.Second } maxAttempts := int(maxReportSendingTime / poll) for usr.triesSendingReport < maxAttempts { log.Infof("attempting to report data of deployment [%v] to the backend;"+ " deployment status [%v], try %d", usr.update.ID, usr.status, usr.triesSendingReport) if err := send(usr.update, usr.status, c); err != nil { log.Errorf("failed to report data %v: %v", usr.status, err.Cause()) // error reporting status or sending logs; // wait for some time before trying again if wc := usr.Wait(c.GetUpdatePollInterval()); wc == false { // if the waiting was interrupted don't increase triesSendingReport return nil, true } usr.triesSendingReport++ continue } // reset counter usr.triesSendingReport = 0 return nil, false } return NewFatalError(errors.New("error sending data to server")), false }
func (m *mender) InventoryRefresh() error { ic := client.NewInventory() idg := NewInventoryDataRunner(path.Join(getDataDirPath(), "inventory")) idata, err := idg.Get() if err != nil { // at least report device type log.Errorf("failed to obtain inventory data: %s", err.Error()) } reqAttr := []client.InventoryAttribute{ {"device_type", m.GetDeviceType()}, {"image_id", m.GetCurrentImageID()}, {"client_version", VersionString()}, } if idata == nil { idata = make(client.InventoryData, 0, len(reqAttr)) } idata.ReplaceAttributes(reqAttr) if idata == nil { log.Infof("no inventory data to submit") return nil } err = ic.Submit(m.api.Request(m.authToken), m.config.ServerURL, idata) if err != nil { return errors.Wrapf(err, "failed to submit inventory data") } return nil }
func (cts *ClientTestServer) statusReq(w http.ResponseWriter, r *http.Request, id string) { log.Infof("got status request deployment ID: %v, %v", id, r) cts.Status.Called = true if !isMethod(http.MethodPut, w, r) { return } if !isContentType("application/json", w, r) { return } if !cts.verifyAuth(w, r) { return } var report client.StatusReport if err := fromJSON(r.Body, &report); err != nil { log.Errorf("failed to parse status data: %v", err) w.WriteHeader(http.StatusBadRequest) return } cts.Status.Status = report.Status w.WriteHeader(http.StatusNoContent) }
func (e *ErrorState) Handle(ctx *StateContext, c Controller) (State, bool) { log.Infof("handling error state, current error: %v", e.cause.Error()) // decide if error is transient, exit for now if e.cause.IsFatal() { return doneState, false } return initState, false }
func (cts *ClientTestServer) updateDownloadReq(w http.ResponseWriter, r *http.Request) { log.Infof("got update download request %v", r) cts.UpdateDownload.Called = true if !isMethod(http.MethodGet, w, r) { return } }
func LoadConfig(configFile string) (*menderConfig, error) { var confFromFile menderConfig if err := readConfigFile(&confFromFile, configFile); err != nil { // Some error occured while loading config file. // Use default configuration. log.Infof("Error loading configuration from file: %s (%s)", configFile, err.Error()) return nil, err } if confFromFile.DeviceKey == "" { log.Infof("device key path not configured, fallback to default %s", defaultKeyFile) confFromFile.DeviceKey = defaultKeyFile } return &confFromFile, nil }
// This will be run manually from command line ONLY func doRootfs(device installer.UInstaller, args runOptionsType, dt string) error { var image io.ReadCloser var imageSize int64 var err error var upclient client.Updater if args == (runOptionsType{}) { return errors.New("rootfs called without needed parameters") } log.Debug("Starting device update.") updateLocation := *args.imageFile if strings.HasPrefix(updateLocation, "http:") || strings.HasPrefix(updateLocation, "https:") { log.Infof("Performing remote update from: [%s].", updateLocation) var ac *client.ApiClient // we are having remote update ac, err = client.New(args.Config) if err != nil { return errors.New("Can not initialize client for performing network update.") } upclient = client.NewUpdate() log.Debug("Client initialized. Start downloading image.") image, imageSize, err = upclient.FetchUpdate(ac, updateLocation) log.Debugf("Image downloaded: %d [%v] [%v]", imageSize, image, err) } else { // perform update from local file log.Infof("Start updating from local image file: [%s]", updateLocation) image, imageSize, err = FetchUpdateFromFile(updateLocation) log.Debugf("Feting update from file results: [%v], %d, %v", image, imageSize, err) } if image == nil || err != nil { return errors.Wrapf(err, "rootfs: error while updating image from command line") } defer image.Close() return installer.Install(image, dt, device) }
func (m *mender) doBootstrap() menderError { if !m.authMgr.HasKey() || m.forceBootstrap { log.Infof("device keys not present or bootstrap forced, generating") if err := m.authMgr.GenerateKey(); err != nil { return NewFatalError(err) } } m.forceBootstrap = false return nil }
// wait and return true if wait was completed (false if canceled) func (cs *CancellableState) Wait(wait time.Duration) bool { ticker := time.NewTicker(wait) defer ticker.Stop() select { case <-ticker.C: log.Debugf("wait complete") return true case <-cs.cancel: log.Infof("wait canceled") } return false }
func (d *device) Rollback() error { // first get inactive partition inactivePartition, err := d.getInactivePartition() if err != nil { return err } log.Infof("setting partition for rollback: %s", inactivePartition) err = d.WriteEnv(BootVars{"mender_boot_part": inactivePartition, "upgrade_available": "0"}) if err != nil { return err } log.Debug("Marking inactive partition as a boot candidate successful.") return nil }
func InstallRootfs(device UInstaller, dt string) parser.DataHandlerFunc { return func(r io.Reader, dev string, uf parser.UpdateFile) error { if dev != dt { return errors.Errorf("unexpected device type [%v], expected to see [%v]", dev, dt) } log.Infof("installing update %v of size %v", uf.Name, uf.Size) err := device.InstallUpdate(ioutil.NopCloser(r), uf.Size) if err != nil { log.Errorf("update image installation failed: %v", err) return err } return device.EnableUpdatedPartition() } }
func (cts *ClientTestServer) deploymentsReq(w http.ResponseWriter, r *http.Request) { log.Infof("got deployments log/status request %v", r) p := r.URL.Path s := strings.TrimPrefix(p, "/api/devices/0.1/deployments/device/deployments/") if s == p { // unchanged, was no prefix? w.WriteHeader(http.StatusBadRequest) return } log.Infof("request for %v", s) idwhat := strings.SplitN(s, "/", 2) id := idwhat[0] what := idwhat[1] switch { case what == "log": cts.logReq(w, r, id) case what == "status": cts.statusReq(w, r, id) default: w.WriteHeader(http.StatusBadRequest) } }
func (d *device) InstallUpdate(image io.ReadCloser, size int64) error { log.Debugf("Trying to install update of size: %d", size) if image == nil || size < 0 { return errors.New("Have invalid update. Aborting.") } inactivePartition, err := d.GetInactive() if err != nil { return err } b := &BlockDevice{Path: inactivePartition} if bsz, err := b.Size(); err != nil { log.Errorf("failed to read size of block device %s: %v", inactivePartition, err) return err } else if bsz < uint64(size) { log.Errorf("update (%v bytes) is larger than the size of device %s (%v bytes)", size, inactivePartition, bsz) return syscall.ENOSPC } w, err := io.Copy(b, image) if err != nil { log.Errorf("failed to write image data to device %v: %v", inactivePartition, err) } log.Infof("wrote %v/%v bytes of update to device %v", w, size, inactivePartition) if cerr := b.Close(); cerr != nil { log.Errorf("closing device %v failed: %v", inactivePartition, cerr) if err != nil { return cerr } } return err }
func NewClientTestServer() *ClientTestServer { cts := &ClientTestServer{} mux := http.NewServeMux() mux.HandleFunc("/api/devices/0.1/authentication/auth_requests", cts.authReq) mux.HandleFunc("/api/devices/0.1/inventory/device/attributes", cts.inventoryReq) mux.HandleFunc("/api/devices/0.1/deployments/device/update", cts.updateReq) // mux.HandleFunc("/api/devices/0.1/deployments/device/deployments/%s/log", cts.logReq) // mux.HandleFunc("/api/devices/0.1/deployments/device/deployments/%s/status", cts.statusReq) mux.HandleFunc("/api/devices/0.1/deployments/device/deployments/", cts.deploymentsReq) mux.HandleFunc("/api/devices/0.1/download", cts.updateDownloadReq) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { log.Infof("fallback request handler, request %v", r) w.WriteHeader(http.StatusBadRequest) }) srv := httptest.NewServer(mux) cts.Server = srv return cts }
func (uv *UpdateVerifyState) Handle(ctx *StateContext, c Controller) (State, bool) { // start deployment logging if err := DeploymentLogger.Enable(uv.update.ID); err != nil { return NewUpdateErrorState(NewTransientError(err), uv.update), false } log.Debug("handle update verify state") // look at the update flag has, haserr := c.HasUpgrade() if haserr != nil { log.Errorf("has upgrade check failed: %v", haserr) me := NewFatalError(errors.Wrapf(haserr, "failed to perform 'has upgrade' check")) return NewUpdateErrorState(me, uv.update), false } if has { if uv.update.Image.YoctoID == c.GetCurrentImageID() { log.Infof("successfully running with new image %v", c.GetCurrentImageID()) // update info and has upgrade flag are there, we're running the new // update, everything looks good, proceed with committing return NewUpdateCommitState(uv.update), false } else { // seems like we're running in a different image than expected from update // information, best report an error log.Errorf("running with image %v, expected updated image %v", c.GetCurrentImageID(), uv.update.Image.YoctoID) return NewUpdateStatusReportState(uv.update, client.StatusFailure), false } } // HasUpgrade() returned false // most probably booting new image failed and u-boot rolledback to // previous image log.Errorf("update info for deployment %v present, but update flag is not set;"+ " running rollback image (previous active partition)", uv.update.ID) return NewUpdateStatusReportState(uv.update, client.StatusFailure), false }
func (cts *ClientTestServer) updateReq(w http.ResponseWriter, r *http.Request) { log.Infof("got update request %v", r) cts.Update.Called = true if !isMethod(http.MethodGet, w, r) { return } if !cts.verifyAuth(w, r) { return } switch { case cts.Update.Unauthorized == true: w.WriteHeader(http.StatusUnauthorized) case cts.Update.Has == false: w.WriteHeader(http.StatusNoContent) case cts.Update.Has == true: w.WriteHeader(http.StatusOK) if cts.Update.Data.ID == "" { cts.Update.Data.ID = "foo" } if cts.Update.Data.Image.ID == "" { cts.Update.Data.Image.ID = "foo" } if cts.Update.Data.Image.YoctoID == "" { cts.Update.Data.Image.YoctoID = "yocto-foo" } if cts.Update.Data.Image.URI == "" { cts.Update.Data.Image.URI = cts.URL + "/download" } if cts.Update.Data.Image.Checksum == "" { cts.Update.Data.Image.Checksum = "123" } w.Header().Set("Content-Type", "application/json") writeJSON(w, &cts.Update.Data) } }
func (cts *ClientTestServer) authReq(w http.ResponseWriter, r *http.Request) { log.Infof("got auth request %v", r) cts.Auth.Called = true if !isMethod(http.MethodPost, w, r) { return } if !isContentType("application/json", w, r) { return } if cts.Auth.Authorize { w.WriteHeader(http.StatusOK) if cts.Auth.Token != nil { w.Header().Set("Content-Type", "text/plain") w.Write(cts.Auth.Token) } } else { w.WriteHeader(http.StatusUnauthorized) } }
func (m *mender) SetState(s State) { log.Infof("Mender state: %v -> %v", m.state.Id(), s.Id()) m.state = s }