mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-09 14:43:24 +00:00
Add StartTimestamp and CompletionTimestamp in Restore Status (#2748)
Signed-off-by: thejas <thejasb99@gmail.com>
This commit is contained in:
2
changelogs/unreleased/2748-thejasbabu
Normal file
2
changelogs/unreleased/2748-thejasbabu
Normal file
@@ -0,0 +1,2 @@
|
||||
Adds Start and CompletionTimestamp to RestoreStatus
|
||||
Displays the Timestamps when issued a print or describe
|
||||
@@ -143,6 +143,13 @@ spec:
|
||||
status:
|
||||
description: RestoreStatus captures the current status of a Velero restore
|
||||
properties:
|
||||
completionTimestamp:
|
||||
description: CompletionTimestamp records the time the restore operation
|
||||
was completed. Completion time is recorded even on failed restore.
|
||||
The server's time is used for StartTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
errors:
|
||||
description: Errors is a count of all error messages that were generated
|
||||
during execution of the restore. The actual errors are stored in object
|
||||
@@ -162,6 +169,12 @@ spec:
|
||||
- PartiallyFailed
|
||||
- Failed
|
||||
type: string
|
||||
startTimestamp:
|
||||
description: StartTimestamp records the time the restore operation was
|
||||
started. The server's time is used for StartTimestamps
|
||||
format: date-time
|
||||
nullable: true
|
||||
type: string
|
||||
validationErrors:
|
||||
description: ValidationErrors is a slice of all validation errors (if
|
||||
applicable)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -137,6 +137,19 @@ type RestoreStatus struct {
|
||||
// FailureReason is an error that caused the entire restore to fail.
|
||||
// +optional
|
||||
FailureReason string `json:"failureReason,omitempty"`
|
||||
|
||||
// StartTimestamp records the time the restore operation was started.
|
||||
// The server's time is used for StartTimestamps
|
||||
// +optional
|
||||
// +nullable
|
||||
StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"`
|
||||
|
||||
// CompletionTimestamp records the time the restore operation was completed.
|
||||
// Completion time is recorded even on failed restore.
|
||||
// The server's time is used for StartTimestamps
|
||||
// +optional
|
||||
// +nullable
|
||||
CompletionTimestamp *metav1.Time `json:"completionTimestamp,omitempty"`
|
||||
}
|
||||
|
||||
// +genclient
|
||||
|
||||
@@ -1146,6 +1146,14 @@ func (in *RestoreStatus) DeepCopyInto(out *RestoreStatus) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.StartTimestamp != nil {
|
||||
in, out := &in.StartTimestamp, &out.StartTimestamp
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.CompletionTimestamp != nil {
|
||||
in, out := &in.CompletionTimestamp, &out.CompletionTimestamp
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
package builder
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
@@ -133,3 +135,15 @@ func (b *RestoreBuilder) RestorePVs(val bool) *RestoreBuilder {
|
||||
b.object.Spec.RestorePVs = &val
|
||||
return b
|
||||
}
|
||||
|
||||
// StartTimestamp sets the Restore's start timestamp.
|
||||
func (b *RestoreBuilder) StartTimestamp(val time.Time) *RestoreBuilder {
|
||||
b.object.Status.StartTimestamp = &metav1.Time{Time: val}
|
||||
return b
|
||||
}
|
||||
|
||||
// CompletionTimestamp sets the Restore's completion timestamp.
|
||||
func (b *RestoreBuilder) CompletionTimestamp(val time.Time) *RestoreBuilder {
|
||||
b.object.Status.CompletionTimestamp = &metav1.Time{Time: val}
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -48,6 +48,20 @@ func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestor
|
||||
|
||||
d.Printf("Phase:\t%s%s\n", restore.Status.Phase, resultsNote)
|
||||
|
||||
d.Println()
|
||||
// "<n/a>" output should only be applicable for restore that failed validation
|
||||
if restore.Status.StartTimestamp == nil || restore.Status.StartTimestamp.IsZero() {
|
||||
d.Printf("Started:\t%s\n", "<n/a>")
|
||||
} else {
|
||||
d.Printf("Started:\t%s\n", restore.Status.StartTimestamp)
|
||||
}
|
||||
|
||||
if restore.Status.CompletionTimestamp == nil || restore.Status.CompletionTimestamp.IsZero() {
|
||||
d.Printf("Completed:\t%s\n", "<n/a>")
|
||||
} else {
|
||||
d.Printf("Completed:\t%s\n", restore.Status.CompletionTimestamp)
|
||||
}
|
||||
|
||||
if len(restore.Status.ValidationErrors) > 0 {
|
||||
d.Println()
|
||||
d.Printf("Validation errors:")
|
||||
|
||||
@@ -30,6 +30,8 @@ var (
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Backup"},
|
||||
{Name: "Status"},
|
||||
{Name: "Started"},
|
||||
{Name: "Completed"},
|
||||
{Name: "Errors"},
|
||||
{Name: "Warnings"},
|
||||
{Name: "Created"},
|
||||
@@ -60,6 +62,8 @@ func printRestore(restore *v1.Restore) []metav1.TableRow {
|
||||
restore.Name,
|
||||
restore.Spec.BackupName,
|
||||
status,
|
||||
restore.Status.StartTimestamp,
|
||||
restore.Status.CompletionTimestamp,
|
||||
restore.Status.Errors,
|
||||
restore.Status.Warnings,
|
||||
restore.CreationTimestamp.Time,
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
@@ -90,6 +91,7 @@ type restoreController struct {
|
||||
defaultBackupLocation string
|
||||
metrics *metrics.ServerMetrics
|
||||
logFormat logging.Format
|
||||
clock clock.Clock
|
||||
|
||||
newPluginManager func(logger logrus.FieldLogger) clientmgmt.Manager
|
||||
newBackupStore func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error)
|
||||
@@ -125,6 +127,7 @@ func NewRestoreController(
|
||||
defaultBackupLocation: defaultBackupLocation,
|
||||
metrics: metrics,
|
||||
logFormat: logFormat,
|
||||
clock: &clock.RealClock{},
|
||||
|
||||
// use variables to refer to these functions so they can be
|
||||
// replaced with fakes for testing.
|
||||
@@ -235,6 +238,7 @@ func (c *restoreController) processRestore(restore *api.Restore) error {
|
||||
restore.Status.Phase = api.RestorePhaseFailedValidation
|
||||
c.metrics.RegisterRestoreValidationFailed(backupScheduleName)
|
||||
} else {
|
||||
restore.Status.StartTimestamp = &metav1.Time{Time: c.clock.Now()}
|
||||
restore.Status.Phase = api.RestorePhaseInProgress
|
||||
}
|
||||
|
||||
@@ -268,6 +272,7 @@ func (c *restoreController) processRestore(restore *api.Restore) error {
|
||||
c.metrics.RegisterRestoreSuccess(backupScheduleName)
|
||||
}
|
||||
|
||||
restore.Status.CompletionTimestamp = &metav1.Time{Time: c.clock.Now()}
|
||||
c.logger.Debug("Updating restore's final status")
|
||||
if _, err = patchRestore(original, restore, c.restoreClient); err != nil {
|
||||
c.logger.WithError(errors.WithStack(err)).Info("Error updating restore's final status")
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
core "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
@@ -232,6 +233,12 @@ func TestProcessQueueItem(t *testing.T) {
|
||||
|
||||
defaultStorageLocation := builder.ForBackupStorageLocation("velero", "default").Provider("myCloud").Bucket("bucket").Result()
|
||||
|
||||
now, err := time.Parse(time.RFC1123Z, time.RFC1123Z)
|
||||
require.NoError(t, err)
|
||||
now = now.Local()
|
||||
timestamp := metav1.NewTime(now)
|
||||
assert.NotNil(t, timestamp)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
restoreKey string
|
||||
@@ -241,6 +248,8 @@ func TestProcessQueueItem(t *testing.T) {
|
||||
restorerError error
|
||||
expectedErr bool
|
||||
expectedPhase string
|
||||
expectedStartTime *metav1.Time
|
||||
expectedCompletedTime *metav1.Time
|
||||
expectedValidationErrors []string
|
||||
expectedRestoreErrors int
|
||||
expectedRestorerCall *velerov1api.Restore
|
||||
@@ -282,13 +291,15 @@ func TestProcessQueueItem(t *testing.T) {
|
||||
expectedValidationErrors: []string{"Either a backup or schedule must be specified as a source for the restore, but not both"},
|
||||
},
|
||||
{
|
||||
name: "valid restore with schedule name gets executed",
|
||||
location: defaultStorageLocation,
|
||||
restore: NewRestore("foo", "bar", "", "ns-1", "", velerov1api.RestorePhaseNew).Schedule("sched-1").Result(),
|
||||
backup: defaultBackup().StorageLocation("default").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "sched-1")).Phase(velerov1api.BackupPhaseCompleted).Result(),
|
||||
expectedErr: false,
|
||||
expectedPhase: string(velerov1api.RestorePhaseInProgress),
|
||||
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Schedule("sched-1").Result(),
|
||||
name: "valid restore with schedule name gets executed",
|
||||
location: defaultStorageLocation,
|
||||
restore: NewRestore("foo", "bar", "", "ns-1", "", velerov1api.RestorePhaseNew).Schedule("sched-1").Result(),
|
||||
backup: defaultBackup().StorageLocation("default").ObjectMeta(builder.WithLabels(velerov1api.ScheduleNameLabel, "sched-1")).Phase(velerov1api.BackupPhaseCompleted).Result(),
|
||||
expectedErr: false,
|
||||
expectedPhase: string(velerov1api.RestorePhaseInProgress),
|
||||
expectedStartTime: ×tamp,
|
||||
expectedCompletedTime: ×tamp,
|
||||
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Schedule("sched-1").Result(),
|
||||
},
|
||||
{
|
||||
name: "restore with non-existent backup name fails",
|
||||
@@ -307,17 +318,21 @@ func TestProcessQueueItem(t *testing.T) {
|
||||
expectedErr: false,
|
||||
expectedPhase: string(velerov1api.RestorePhaseInProgress),
|
||||
expectedFinalPhase: string(velerov1api.RestorePhasePartiallyFailed),
|
||||
expectedStartTime: ×tamp,
|
||||
expectedCompletedTime: ×tamp,
|
||||
expectedRestoreErrors: 1,
|
||||
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Result(),
|
||||
},
|
||||
{
|
||||
name: "valid restore gets executed",
|
||||
location: defaultStorageLocation,
|
||||
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(),
|
||||
backup: defaultBackup().StorageLocation("default").Result(),
|
||||
expectedErr: false,
|
||||
expectedPhase: string(velerov1api.RestorePhaseInProgress),
|
||||
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Result(),
|
||||
name: "valid restore gets executed",
|
||||
location: defaultStorageLocation,
|
||||
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(),
|
||||
backup: defaultBackup().StorageLocation("default").Result(),
|
||||
expectedErr: false,
|
||||
expectedPhase: string(velerov1api.RestorePhaseInProgress),
|
||||
expectedStartTime: ×tamp,
|
||||
expectedCompletedTime: ×tamp,
|
||||
expectedRestorerCall: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseInProgress).Result(),
|
||||
},
|
||||
{
|
||||
name: "restoration of nodes is not supported",
|
||||
@@ -385,6 +400,8 @@ func TestProcessQueueItem(t *testing.T) {
|
||||
restore: NewRestore(velerov1api.DefaultNamespace, "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).Result(),
|
||||
expectedPhase: string(velerov1api.RestorePhaseInProgress),
|
||||
expectedFinalPhase: string(velerov1api.RestorePhaseFailed),
|
||||
expectedStartTime: ×tamp,
|
||||
expectedCompletedTime: ×tamp,
|
||||
backupStoreGetBackupContentsErr: errors.New("Couldn't download backup"),
|
||||
backup: defaultBackup().StorageLocation("default").Result(),
|
||||
},
|
||||
@@ -431,7 +448,7 @@ func TestProcessQueueItem(t *testing.T) {
|
||||
c.newBackupStore = func(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
|
||||
return backupStore, nil
|
||||
}
|
||||
|
||||
c.clock = clock.NewFakeClock(now)
|
||||
if test.location != nil {
|
||||
require.NoError(t, fakeClient.Create(context.Background(), test.location))
|
||||
}
|
||||
@@ -555,9 +572,11 @@ func TestProcessQueueItem(t *testing.T) {
|
||||
}
|
||||
|
||||
type StatusPatch struct {
|
||||
Phase velerov1api.RestorePhase `json:"phase"`
|
||||
ValidationErrors []string `json:"validationErrors"`
|
||||
Errors int `json:"errors"`
|
||||
Phase velerov1api.RestorePhase `json:"phase"`
|
||||
ValidationErrors []string `json:"validationErrors"`
|
||||
Errors int `json:"errors"`
|
||||
StartTimestamp *metav1.Time `json:"startTimestamp"`
|
||||
CompletionTimestamp *metav1.Time `json:"completionTimestamp"`
|
||||
}
|
||||
|
||||
type Patch struct {
|
||||
@@ -588,6 +607,10 @@ func TestProcessQueueItem(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if test.expectedStartTime != nil {
|
||||
expected.Status.StartTimestamp = test.expectedStartTime
|
||||
}
|
||||
|
||||
velerotest.ValidatePatch(t, actions[0], expected, decode)
|
||||
|
||||
// if we don't expect a restore, validate it wasn't called and exit the test
|
||||
@@ -602,16 +625,18 @@ func TestProcessQueueItem(t *testing.T) {
|
||||
|
||||
expected = Patch{
|
||||
Status: StatusPatch{
|
||||
Phase: velerov1api.RestorePhaseCompleted,
|
||||
Errors: test.expectedRestoreErrors,
|
||||
Phase: velerov1api.RestorePhaseCompleted,
|
||||
Errors: test.expectedRestoreErrors,
|
||||
CompletionTimestamp: test.expectedCompletedTime,
|
||||
},
|
||||
}
|
||||
// Override our default expectations if the case requires it
|
||||
if test.expectedFinalPhase != "" {
|
||||
expected = Patch{
|
||||
Status: StatusPatch{
|
||||
Phase: velerov1api.RestorePhase(test.expectedFinalPhase),
|
||||
Errors: test.expectedRestoreErrors,
|
||||
Phase: velerov1api.RestorePhase(test.expectedFinalPhase),
|
||||
Errors: test.expectedRestoreErrors,
|
||||
CompletionTimestamp: test.expectedCompletedTime,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user