Skip to content

Commit

Permalink
linux-client: Support single file updates along with rollback
Browse files Browse the repository at this point in the history
Signed-off-by: Mateusz Leonowicz <[email protected]>
  • Loading branch information
mleonowicz authored and ksychla committed Oct 7, 2024
1 parent 6157b29 commit ee3ca47
Show file tree
Hide file tree
Showing 3 changed files with 329 additions and 26 deletions.
24 changes: 3 additions & 21 deletions devices/linux-client/app/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
conf "github.com/antmicro/rdfm/conf"
"github.com/antmicro/rdfm/delta"
"github.com/antmicro/rdfm/helpers"
"github.com/mendersoftware/mender/app"
"github.com/mendersoftware/mender/client"
mconf "github.com/mendersoftware/mender/conf"
"github.com/mendersoftware/mender/datastore"
Expand Down Expand Up @@ -172,35 +171,18 @@ func createDeviceManager(config *mconf.MenderConfig, store *store.DBStore) (*dev
// This can be either an artifact on the local filesystem, or an HTTP URL
func (ctx *RDFM) InstallArtifact(path string) error {
clientConfig := client.Config{}
menderConfig, err := OverlayMenderConfig(ctx.RdfmConfig, ctx.menderConfig)
if err != nil {
return err
}
stateExec := device.NewStateScriptExecutor(menderConfig)

return DoInstall(ctx.deviceManager, path, clientConfig, stateExec, false)
return DoInstall(ctx.deviceManager, path, clientConfig, false)
}

// Attempt to commit the currently installed update
func (ctx *RDFM) CommitCurrentArtifact() error {
menderConfig, err := OverlayMenderConfig(ctx.RdfmConfig, ctx.menderConfig)
if err != nil {
return err
}
stateExec := device.NewStateScriptExecutor(menderConfig)

return app.DoStandaloneCommit(ctx.deviceManager, stateExec)
return DoCommit(ctx.deviceManager)
}

// Attempt to rollback the currently installed update
func (ctx *RDFM) RollbackCurrentArtifact() error {
menderConfig, err := OverlayMenderConfig(ctx.RdfmConfig, ctx.menderConfig)
if err != nil {
return err
}
stateExec := device.NewStateScriptExecutor(menderConfig)

return app.DoStandaloneRollback(ctx.deviceManager, stateExec)
return DoRollback(ctx.deviceManager)
}

func (ctx *RDFM) GetCurrentArtifactName() (string, error) {
Expand Down
140 changes: 135 additions & 5 deletions devices/linux-client/app/install.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
package app

import (
"encoding/json"
"io"
"io/ioutil"
"strings"

"github.com/pkg/errors"

"github.com/antmicro/rdfm/download"
"github.com/antmicro/rdfm/handlers"
"github.com/antmicro/rdfm/parser"

"github.com/mendersoftware/mender/app"
"github.com/mendersoftware/mender/client"
"github.com/mendersoftware/mender/datastore"
dev "github.com/mendersoftware/mender/device"
"github.com/mendersoftware/mender/installer"
"github.com/mendersoftware/mender/statescript"
"github.com/mendersoftware/mender/store"
"github.com/mendersoftware/mender/utils"

log "github.com/sirupsen/logrus"
)

const (
errMsgDependencyNotSatisfiedF = "Artifact dependency %q not satisfied by currently installed artifact (%v != %v)."
errMsgInvalidDependsTypeF = "invalid type %T for dependency with name %s"
)

func DoInstall(device *dev.DeviceManager, updateURI string,
clientConfig client.Config,
stateExec statescript.Executor, rebootExitCode bool) error {
clientConfig client.Config, rebootExitCode bool) error {

var image io.ReadCloser
var imageSize int64
Expand All @@ -40,11 +50,131 @@ func DoInstall(device *dev.DeviceManager, updateURI string,
p := utils.NewProgressWriter(imageSize)
tr := io.TeeReader(image, p)

err = app.DoStandaloneInstallStates(ioutil.NopCloser(tr), device, stateExec, rebootExitCode)
err = DoInstallStates(ioutil.NopCloser(tr), device, rebootExitCode)

if err == nil {
download.CleanCache()
}

return err
}

func DoInstallStates(art io.ReadCloser,
device *dev.DeviceManager, rebootExitCode bool) error {

installHandler, err := parser.InitializeArtifactHandlers(art, &device.InstallerFactories)

if err != nil {
return err
}

currentProvides, err := datastore.LoadProvides(device.Store)
err = installHandler.VerifyDependencies(currentProvides)

if err != nil {
return err
}

err = installHandler.StorePayloads()
if err != nil {
log.Errorf("Download failed: %s", err.Error())
handleFailure(installHandler)
return err
}

err = installHandler.InstallUpdate()
if err != nil {
handleFailure(installHandler)
return err
}

installHandler.SaveUpdateToStore(device.Store)

if installHandler.IsRollbackSupported() {
log.Infof("Use 'commit' to update, or 'rollback' to roll back the update.")
} else {
log.Infof("Artifact doesn't support rollback. Committing immediately.")
installHandler.Commit()
}

if installHandler.IsRebootNeeded() {
log.Infof("At least one payload requested a reboot of the device it updated.")
}

return nil
}

func DoCommit(device *dev.DeviceManager) error {
log.Infof("Committing Artifact...")
stateData, installers, err := restoreHandlerData(device)
if err != nil {
return err
}
for _, inst := range installers {
inst.CommitUpdate()
}
err = storeUpdateInDB(stateData, device)

return err
}

func DoRollback(device *dev.DeviceManager) error {
log.Infof("Rolling back Artifact...")
stateData, installers, err := restoreHandlerData(device)
if err != nil {
return err
}
for _, inst := range installers {
if support, _ := inst.SupportsRollback(); support {
inst.Rollback()
}
}
stateData.ArtifactName = ""
err = storeUpdateInDB(stateData, device)

return err
}

func storeUpdateInDB(stateData *datastore.StandaloneStateData, device *dev.DeviceManager) error {
err := device.Store.WriteTransaction(func(txn store.Transaction) error {
err := txn.Remove(datastore.StandaloneStateKey)
if err != nil {
return err
}
if stateData.ArtifactName != "" {
return datastore.CommitArtifactData(txn, stateData.ArtifactName,
stateData.ArtifactGroup, stateData.ArtifactTypeInfoProvides,
stateData.ArtifactClearsProvides)
}
return nil
})
return err
}

func restoreHandlerData(device *dev.DeviceManager) (*datastore.StandaloneStateData, []installer.PayloadUpdatePerformer, error) {

data, err := device.Store.ReadAll(datastore.StandaloneStateKey)
if err != nil {
return nil, nil, err
}
var stateData datastore.StandaloneStateData
err = json.Unmarshal(data, &stateData)
if err != nil {
return nil, nil, err
}

if stateData.Version != datastore.StandaloneStateDataVersion {
return &stateData, nil, errors.New("Incompatible version stored in database.")
}

installHandlers, err := handlers.RestoreHandlersFromStore(&device.InstallerFactories, device.Store, stateData.PayloadTypes)

return &stateData, installHandlers, err
}

func handleFailure(installHandler *handlers.Handler) {
if installHandler.IsRollbackSupported() {
installHandler.Rollback()
}
installHandler.Cleanup()
}
Loading

0 comments on commit ee3ca47

Please sign in to comment.