func getManifestData(dataType, manifestFile string) string { // This is where Yocto stores buid information manifest, err := os.Open(manifestFile) if err != nil { log.Error("Can not read manifest data.") return "" } scanner := bufio.NewScanner(manifest) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) log.Debug("Read data from device manifest file: ", line) if strings.HasPrefix(line, dataType) { log.Debug("Found needed line: ", line) lineID := strings.Split(line, "=") if len(lineID) != 2 { log.Errorf("Broken device manifest file: (%v)", lineID) return "" } log.Debug("Current manifest data: ", strings.TrimSpace(lineID[1])) return strings.TrimSpace(lineID[1]) } } if err := scanner.Err(); err != nil { log.Error(err) } return "" }
func processUpdateResponse(response *http.Response) (interface{}, error) { log.Debug("Received response:", response.Status) respBody, err := ioutil.ReadAll(response.Body) if err != nil { return nil, err } switch response.StatusCode { case http.StatusOK: log.Debug("Have update available") var data UpdateResponse if err := json.Unmarshal(respBody, &data); err != nil { return nil, errors.Wrapf(err, "failed to parse response") } if err := validateGetUpdate(data); err != nil { return nil, err } return data, nil case http.StatusNoContent: log.Debug("No update available") return nil, nil case http.StatusUnauthorized: log.Warn("Client not authorized to get update schedule.") return nil, ErrNotAuthorized default: return nil, errors.New("Invalid response received from server") } }
func (p *partitions) GetActive() (string, error) { if p.active != "" { log.Debug("Active partition: ", p.active) return p.active, nil } return p.getAndCacheActivePartition(isMountedRoot, getAllMountedDevices) }
func (p *partitions) GetInactive() (string, error) { if p.inactive != "" { log.Debug("Inactive partition: ", p.inactive) return p.inactive, nil } return p.getAndCacheInactivePartition() }
func getOrSetEnvironmentVariable(cmd *exec.Cmd) (BootVars, error) { cmdReader, err := cmd.StdoutPipe() if err != nil { log.Errorln("Error creating StdoutPipe: ", err) return nil, err } scanner := bufio.NewScanner(cmdReader) err = cmd.Start() if err != nil { return nil, err } var env_variables = make(BootVars) for scanner.Scan() { log.Debug("Have U-Boot variable: ", scanner.Text()) splited_line := strings.Split(scanner.Text(), "=") //we are having empty line (usually at the end of output) if scanner.Text() == "" { continue } //we have some malformed data or Warning/Error if len(splited_line) != 2 { log.Error("U-Boot variable malformed or error occured") return nil, errors.New("Invalid U-Boot variable or error: " + scanner.Text()) } env_variables[splited_line[0]] = splited_line[1] } err = cmd.Wait() if err != nil { return nil, err } if len(env_variables) > 0 { log.Debug("List of U-Boot variables:", env_variables) } return env_variables, err }
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 } }
// 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 (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 (p *partitions) getAndCacheActivePartition(rootChecker func(StatCommander, string, *syscall.Stat_t) bool, getMountedDevices func(string) ([]string, error)) (string, error) { mountData, err := p.Command("mount").Output() if err != nil { return "", err } mountCandidate := getRootCandidateFromMount(mountData) rootDevice := getRootDevice(p) if rootDevice == nil { return "", errors.New("Can not find root device") } // First check if mountCandidate matches rootDevice if mountCandidate != "" { if rootChecker(p, mountCandidate, rootDevice) { p.active = mountCandidate log.Debugf("Setting active partition from mount candidate: %s", p.active) return p.active, nil } // If not see if we are lucky somewhere else } const devDir string = "/dev" mountedDevices, err := getMountedDevices(devDir) if err != nil { return "", err } activePartition, err := getRootFromMountedDevices(p, rootChecker, mountedDevices, rootDevice) if err != nil { return "", err } bootEnvBootPart, err := getBootEnvActivePartition(p.BootEnvReadWriter) if err != nil { return "", err } if checkBootEnvAndRootPartitionMatch(bootEnvBootPart, activePartition) { p.active = activePartition log.Debug("Setting active partition: ", activePartition) return p.active, nil } log.Error("Mounted root '" + activePartition + "' does not match boot environment mender_boot_part: " + bootEnvBootPart) return "", ErrorNoMatchBootPartRootPart }
func readConfigFile(config interface{}, fileName string) error { log.Debug("Reading Mender configuration from file " + fileName) conf, err := ioutil.ReadFile(fileName) if err != nil { return err } if err := json.Unmarshal(conf, &config); err != nil { switch err.(type) { case *json.SyntaxError: return errors.New("Error parsing mender configuration file: " + err.Error()) } return errors.New("Error parsing config file: " + err.Error()) } return nil }
func (usr *UpdateStatusReportState) Handle(ctx *StateContext, c Controller) (State, bool) { // start deployment logging; no error checking // we can do nothing here; either we will have the logs or not... DeploymentLogger.Enable(usr.update.ID) if err := StoreStateData(ctx.store, StateData{ Id: usr.Id(), UpdateInfo: usr.update, UpdateStatus: usr.status, }); err != nil { log.Errorf("failed to store state data in update status report state: %v", err) return NewReportErrorState(usr.update, usr.status), false } err, wasInterupted := usr.trySend(sendStatus, c) if wasInterupted { return usr, false } if err != nil { log.Errorf("failed to send status to server: %v", err) return NewReportErrorState(usr.update, usr.status), false } if usr.status == client.StatusFailure { log.Debugf("attempting to upload deployment logs for failed update") err, wasInterupted = usr.trySend(sendDeploymentLogs, c) if wasInterupted { return usr, false } if err != nil { log.Errorf("failed to send deployment logs to server: %v", err) return NewReportErrorState(usr.update, usr.status), false } } log.Debug("reporting complete") // stop deployment logging as the update is completed at this point DeploymentLogger.Disable() // status reported, logs uploaded if needed, remove state data RemoveStateData(ctx.store) return initState, false }
func (d *device) EnableUpdatedPartition() error { inactivePartition, err := d.getInactivePartition() if err != nil { return err } log.Info("Enabling partition with new image installed to be a boot candidate: ", string(inactivePartition)) // For now we are only setting boot variables err = d.WriteEnv(BootVars{"upgrade_available": "1", "mender_boot_part": inactivePartition, "bootcount": "0"}) if err != nil { return err } log.Debug("Marking inactive partition as a boot candidate successful.") return nil }
func (u *UpdateClient) getUpdateInfo(api ApiRequester, process RequestProcessingFunc, server string) (interface{}, error) { req, err := makeUpdateCheckRequest(server) if err != nil { return nil, errors.Wrapf(err, "failed to create update check request") } r, err := api.Do(req) if err != nil { log.Debug("Sending request error: ", err) return nil, errors.Wrapf(err, "update check request failed") } defer r.Body.Close() data, err := process(r) return data, err }
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 TestDeploymentLoggingHook(t *testing.T) { tempDir, _ := ioutil.TempDir("", "logs") defer os.RemoveAll(tempDir) deploymentLogger := NewDeploymentLogManager(tempDir) log.AddHook(NewDeploymentLogHook(deploymentLogger)) log.Info("test1") deploymentLogger.Enable("1111-2222") logFile := fmt.Sprintf(logFileNameScheme, 1, "1111-2222") fileLocation := path.Join(tempDir, logFile) log.Debug("test2") deploymentLogger.Disable() log.Info("test3") // test correct format of log messages if !logFileContains(fileLocation, `{"level":"debug","message":"test2","timestamp":"`) { t.FailNow() } }
// Check if new update is available. In case of errors, returns nil and error // that occurred. If no update is available *UpdateResponse is nil, otherwise it // contains update information. func (m *mender) CheckUpdate() (*client.UpdateResponse, menderError) { currentImageID := m.GetCurrentImageID() //TODO: if currentImageID == "" { // return errors.New("") // } haveUpdate, err := m.updater.GetScheduledUpdate(m.api.Request(m.authToken), m.config.ServerURL) if err != nil { // remove authentication token if device is not authorized if err == client.ErrNotAuthorized { if remErr := m.authMgr.RemoveAuthToken(); remErr != nil { log.Warn("can not remove rejected authentication token") } } log.Error("Error receiving scheduled update data: ", err) return nil, NewTransientError(err) } if haveUpdate == nil { log.Debug("no updates available") return nil, nil } update, ok := haveUpdate.(client.UpdateResponse) if !ok { return nil, NewTransientError(errors.Errorf("not an update response?")) } log.Debugf("received update response: %v", update) if update.Image.YoctoID == currentImageID { log.Info("Attempting to upgrade to currently installed image ID, not performing upgrade.") return &update, NewTransientError(os.ErrExist) } return &update, nil }