Skip to content

Commit

Permalink
pillar: add immediate snapshot feature
Browse files Browse the repository at this point in the history
this feature allows to make an "immediate" snapshot,
i.e. the request triggers a shutdown of the edge app,
then does the snapshot and brings the edge app back
to running state

Signed-off-by: Christoph Ostarek <[email protected]>
  • Loading branch information
christoph-zededa committed Feb 28, 2025
1 parent 7596e99 commit 73e888c
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 17 deletions.
2 changes: 1 addition & 1 deletion pkg/pillar/cmd/zedagent/reportinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ func PublishDeviceInfoToZedCloud(ctx *zedagentContext, dest destinationBitset) {
// device returns a runtime error. Similarly, we only support enforced application network
// interface order for the KVM hypervisor. If enabled for application deployed under Xen
// or Kubevirt hypervisor, EVE returns error and the application will not be started.
ReportDeviceInfo.ApiCapability = info.APICapability_API_CAPABILITY_NTPS_FQDN
ReportDeviceInfo.ApiCapability = info.APICapability_API_CAPABILITY_VOLUME_SNAPSHOTS_IMMEDIATE

// Report if there is a local override of profile
if ctx.getconfigCtx.sideController.currentProfile !=
Expand Down
2 changes: 1 addition & 1 deletion pkg/pillar/cmd/zedmanager/updatestatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func removeAIStatus(ctx *zedmanagerContext, status *types.AppInstanceStatus) {
// The VM has been just shutdown in a result of the purge&update command coming from the controller.
if !uninstall && domainStatus != nil && !domainStatus.Activated {
// We should do it before the doRemove is called, so that all the volumes are still available.
if status.SnapStatus.SnapshotOnUpgrade && len(status.SnapStatus.PreparedVolumesSnapshotConfigs) > 0 {
if status.SnapStatus.SnapshotTakenType != types.NoSnapshotTake && len(status.SnapStatus.PreparedVolumesSnapshotConfigs) > 0 {
// Check whether there are snapshots to be deleted first (not to exceed the maximum number of snapshots).
if len(status.SnapStatus.SnapshotsToBeDeleted) > 0 {
triggerSnapshotDeletion(status.SnapStatus.SnapshotsToBeDeleted, ctx, status)
Expand Down
45 changes: 33 additions & 12 deletions pkg/pillar/cmd/zedmanager/zedmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -829,18 +829,26 @@ func lookupLocalAppInstanceConfig(ctx *zedmanagerContext, key string) *types.App
return &config
}

func isSnapshotRequestedOnUpdate(status *types.AppInstanceStatus, config types.AppInstanceConfig) bool {
if config.Snapshot.Snapshots == nil {
return false
}
func snapshotRequestedType(status *types.AppInstanceStatus, config types.AppInstanceConfig) types.SnapshotTakenTime {
ret := types.NoSnapshotTake

for _, snap := range config.Snapshot.Snapshots {
// VM should be marked to be snapshotted on update only if there is any snapshot request of the type "on update"
// that is not handled yet.
if snap.SnapshotType == types.SnapshotTypeAppUpdate && lookupAvailableSnapshot(status, snap.SnapshotID) == nil {
return true
if lookupAvailableSnapshot(status, snap.SnapshotID) != nil {
continue
}

switch snap.SnapshotType {
case types.SnapshotTypeUnspecified:
ret = types.NoSnapshotTake
case types.SnapshotTypeAppUpdate:
ret = types.SnapshotOnUpgrade
case types.SnapshotTypeImmediate:
// Immediate snapshots have precedence over snapshots on upgrade
return types.SnapshotImmediate
}
}
return false

return ret
}

// removeNUnpublishedSnapshotRequests removes up to n snapshot requests that have not been triggered yet
Expand Down Expand Up @@ -985,7 +993,7 @@ func isNewSnapshotRequest(id string, status *types.AppInstanceStatus) bool {

// Update the snapshot related fields in the AppInstanceStatus
func updateSnapshotsInAIStatus(status *types.AppInstanceStatus, config types.AppInstanceConfig) {
status.SnapStatus.SnapshotOnUpgrade = isSnapshotRequestedOnUpdate(status, config)
status.SnapStatus.SnapshotTakenType = snapshotRequestedType(status, config)
//markReportedSnapshots(status, config)
status.SnapStatus.MaxSnapshots = config.Snapshot.MaxSnapshots
snapshotsToBeDeleted := getSnapshotsToBeDeleted(config, status)
Expand Down Expand Up @@ -1021,7 +1029,7 @@ func updateSnapshotsInAIStatus(status *types.AppInstanceStatus, config types.App
func prepareVolumesSnapshotConfigs(ctx *zedmanagerContext, config types.AppInstanceConfig, status *types.AppInstanceStatus) []types.VolumesSnapshotConfig {
var volumesSnapshotConfigList []types.VolumesSnapshotConfig
for _, snapshot := range status.SnapStatus.RequestedSnapshots {
if snapshot.Snapshot.SnapshotType == types.SnapshotTypeAppUpdate {
if snapshot.Snapshot.SnapshotType == types.SnapshotTypeAppUpdate || snapshot.Snapshot.SnapshotType == types.SnapshotTypeImmediate {
log.Noticef("Creating volumesSnapshotConfig for snapshot %s", snapshot.Snapshot.SnapshotID)
volumesSnapshotConfig := types.VolumesSnapshotConfig{
SnapshotID: snapshot.Snapshot.SnapshotID,
Expand Down Expand Up @@ -1264,9 +1272,11 @@ func handleModify(ctxArg interface{}, key string,
if needPurge {
needRestart = false
}

// A snapshot is deemed necessary whenever the application requires a restart, as this typically
// indicates a significant change in the application, such as an upgrade.
if status.SnapStatus.SnapshotOnUpgrade && (needRestart || needPurge) {
if status.SnapStatus.SnapshotTakenType == types.SnapshotImmediate ||
(status.SnapStatus.SnapshotTakenType == types.SnapshotOnUpgrade && (needRestart || needPurge)) {
// Save the list of the volumes that need to be backed up. We will use this list to create the snapshot when
// it's triggered. We cannot trigger the snapshot creation here immediately, as the VM
// should be stopped first. But we still need to save the list of volumes that are known only at this point.
Expand All @@ -1279,6 +1289,17 @@ func handleModify(ctxArg interface{}, key string,
}
}

if status.SnapStatus.SnapshotTakenType == types.SnapshotImmediate {
// this is okay as a clash with updated volumes is prevented later by
// "Need purge due to %s but not a purgeCmd", so if the user
// updates the volumes but does not set the purgeCmd but does
// an immediate snapshot, the immediate snapshot will not trigger
// an update of the volumes as the purgeCmd is missing
status.PurgeInprogress = types.DownloadAndVerify
status.PurgeStartedAt = time.Now()
restartReason = "Restart to create immediate snapshot"
}

if config.RestartCmd.Counter != oldConfig.RestartCmd.Counter ||
config.LocalRestartCmd.Counter != oldConfig.LocalRestartCmd.Counter {

Expand Down
24 changes: 21 additions & 3 deletions pkg/pillar/types/zedmanagertypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ type UUIDandVersion struct {
type SnapshotType int32

const (
// SnapshotTypeUnspecified is the default value, and should not be used in practice
// SnapshotTypeUnspecified is the default value, and means no snapshot should be taken
SnapshotTypeUnspecified SnapshotType = 0
// SnapshotTypeAppUpdate is used when the snapshot is created as a result of an app update
SnapshotTypeAppUpdate SnapshotType = 1
// SnapshotTypeImmediate is used when the snapshot is created immediately
SnapshotTypeImmediate SnapshotType = 2
)

func (s SnapshotType) String() string {
Expand All @@ -39,6 +41,8 @@ func (s SnapshotType) String() string {
return "SnapshotTypeUnspecified"
case SnapshotTypeAppUpdate:
return "SnapshotTypeAppUpdate"
case SnapshotTypeImmediate:
return "SnapshotTypeImmediate"
default:
return fmt.Sprintf("Unknown SnapshotType %d", s)
}
Expand All @@ -49,6 +53,8 @@ func (s SnapshotType) ConvertToInfoSnapshotType() info.SnapshotType {
switch s {
case SnapshotTypeAppUpdate:
return info.SnapshotType_SNAPSHOT_TYPE_APP_UPDATE
case SnapshotTypeImmediate:
return info.SnapshotType_SNAPSHOT_TYPE_IMMEDIATE
default:
return info.SnapshotType_SNAPSHOT_TYPE_UNSPECIFIED
}
Expand Down Expand Up @@ -217,6 +223,18 @@ func (config AppInstanceConfig) Key() string {
return config.UUIDandVersion.UUID.String()
}

// SnapshotTakenTime describes when a snapshot should be taken, see info below
type SnapshotTakenTime uint8

const (
// NoSnapshotTake indicated no snapshot should be taken, f.e. because it already exists.
NoSnapshotTake SnapshotTakenTime = iota
// SnapshotImmediate indicates whether a snapshot should be taken immediately.
SnapshotImmediate
// SnapshotOnUpgrade indicates whether a snapshot should be taken during the app instance update.
SnapshotOnUpgrade
)

// SnapshottingStatus contains the snapshot information for the app instance.
type SnapshottingStatus struct {
// MaxSnapshots indicates the maximum number of snapshots to be kept for the app instance.
Expand All @@ -229,8 +247,8 @@ type SnapshottingStatus struct {
SnapshotsToBeDeleted []SnapshotDesc
// PreparedVolumesSnapshotConfigs contains the list of snapshots to be triggered for the app instance.
PreparedVolumesSnapshotConfigs []VolumesSnapshotConfig
// SnapshotOnUpgrade indicates whether a snapshot should be taken during the app instance update.
SnapshotOnUpgrade bool
// SnapshotTakenType indicates if and when a snapshot should be taken
SnapshotTakenType SnapshotTakenTime
// HasRollbackRequest indicates whether there are any rollback requests for the app instance.
// Set to true when a rollback is requested by controller, set to false when the rollback is triggered.
HasRollbackRequest bool
Expand Down

0 comments on commit 73e888c

Please sign in to comment.