add NoData operation status (#1042)
## Description Adds a NoData status to operations in the event that a backup (or, possibly in the future, restore) finishes processing without having any items to store or restore. ## Type of change - [x] 🌻 Feature ## Issue(s) * #1000 ## Test Plan - [x] 💪 Manual - [x] ⚡ Unit test
This commit is contained in:
parent
3a62f7f90c
commit
630d74bee7
@ -171,6 +171,10 @@ func (op *BackupOperation) persistResults(
|
||||
opStats.writeErr)
|
||||
}
|
||||
|
||||
if opStats.readErr == nil && opStats.writeErr == nil && opStats.gc.Successful == 0 {
|
||||
op.Status = NoData
|
||||
}
|
||||
|
||||
op.Results.ReadErrors = opStats.readErr
|
||||
op.Results.WriteErrors = opStats.writeErr
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -35,52 +34,79 @@ func TestBackupOpSuite(t *testing.T) {
|
||||
}
|
||||
|
||||
func (suite *BackupOpSuite) TestBackupOperation_PersistResults() {
|
||||
t := suite.T()
|
||||
ctx := context.Background()
|
||||
|
||||
var (
|
||||
kw = &kopia.Wrapper{}
|
||||
sw = &store.Wrapper{}
|
||||
acct = account.Account{}
|
||||
now = time.Now()
|
||||
stats = backupStats{
|
||||
started: true,
|
||||
readErr: multierror.Append(nil, assert.AnError),
|
||||
writeErr: assert.AnError,
|
||||
resourceCount: 1,
|
||||
k: &kopia.BackupStats{
|
||||
TotalFileCount: 1,
|
||||
TotalHashedBytes: 1,
|
||||
TotalUploadedBytes: 1,
|
||||
},
|
||||
gc: &support.ConnectorOperationStatus{
|
||||
Successful: 1,
|
||||
},
|
||||
}
|
||||
ctx = context.Background()
|
||||
kw = &kopia.Wrapper{}
|
||||
sw = &store.Wrapper{}
|
||||
acct = account.Account{}
|
||||
now = time.Now()
|
||||
)
|
||||
|
||||
op, err := NewBackupOperation(
|
||||
ctx,
|
||||
control.Options{},
|
||||
kw,
|
||||
sw,
|
||||
acct,
|
||||
selectors.Selector{},
|
||||
evmock.NewBus())
|
||||
require.NoError(t, err)
|
||||
table := []struct {
|
||||
expectStatus opStatus
|
||||
expectErr assert.ErrorAssertionFunc
|
||||
stats backupStats
|
||||
}{
|
||||
{
|
||||
expectStatus: Completed,
|
||||
expectErr: assert.NoError,
|
||||
stats: backupStats{
|
||||
started: true,
|
||||
resourceCount: 1,
|
||||
k: &kopia.BackupStats{
|
||||
TotalFileCount: 1,
|
||||
TotalHashedBytes: 1,
|
||||
TotalUploadedBytes: 1,
|
||||
},
|
||||
gc: &support.ConnectorOperationStatus{
|
||||
Successful: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expectStatus: Failed,
|
||||
expectErr: assert.Error,
|
||||
stats: backupStats{
|
||||
started: false,
|
||||
k: &kopia.BackupStats{},
|
||||
gc: &support.ConnectorOperationStatus{},
|
||||
},
|
||||
},
|
||||
{
|
||||
expectStatus: NoData,
|
||||
expectErr: assert.NoError,
|
||||
stats: backupStats{
|
||||
started: true,
|
||||
k: &kopia.BackupStats{},
|
||||
gc: &support.ConnectorOperationStatus{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.expectStatus.String(), func(t *testing.T) {
|
||||
op, err := NewBackupOperation(
|
||||
ctx,
|
||||
control.Options{},
|
||||
kw,
|
||||
sw,
|
||||
acct,
|
||||
selectors.Selector{},
|
||||
evmock.NewBus())
|
||||
require.NoError(t, err)
|
||||
test.expectErr(t, op.persistResults(now, &test.stats))
|
||||
|
||||
require.NoError(t, op.persistResults(now, &stats))
|
||||
|
||||
assert.Equal(t, op.Status.String(), Completed.String(), "status")
|
||||
assert.Equal(t, op.Results.ItemsRead, stats.gc.Successful, "items read")
|
||||
assert.Equal(t, op.Results.ReadErrors, stats.readErr, "read errors")
|
||||
assert.Equal(t, op.Results.ItemsWritten, stats.k.TotalFileCount, "items written")
|
||||
assert.Equal(t, stats.k.TotalHashedBytes, op.Results.BytesRead, "bytes read")
|
||||
assert.Equal(t, stats.k.TotalUploadedBytes, op.Results.BytesUploaded, "bytes written")
|
||||
assert.Equal(t, op.Results.ResourceOwners, stats.resourceCount, "resource owners")
|
||||
assert.Equal(t, op.Results.WriteErrors, stats.writeErr, "write errors")
|
||||
assert.Equal(t, op.Results.StartedAt, now, "started at")
|
||||
assert.Less(t, now, op.Results.CompletedAt, "completed at")
|
||||
assert.Equal(t, test.expectStatus.String(), op.Status.String(), "status")
|
||||
assert.Equal(t, test.stats.gc.Successful, op.Results.ItemsRead, "items read")
|
||||
assert.Equal(t, test.stats.readErr, op.Results.ReadErrors, "read errors")
|
||||
assert.Equal(t, test.stats.k.TotalFileCount, op.Results.ItemsWritten, "items written")
|
||||
assert.Equal(t, test.stats.k.TotalHashedBytes, op.Results.BytesRead, "bytes read")
|
||||
assert.Equal(t, test.stats.k.TotalUploadedBytes, op.Results.BytesUploaded, "bytes written")
|
||||
assert.Equal(t, test.stats.resourceCount, op.Results.ResourceOwners, "resource owners")
|
||||
assert.Equal(t, test.stats.writeErr, op.Results.WriteErrors, "write errors")
|
||||
assert.Equal(t, now, op.Results.StartedAt, "started at")
|
||||
assert.Less(t, now, op.Results.CompletedAt, "completed at")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -13,13 +13,17 @@ import (
|
||||
|
||||
// opStatus describes the current status of an operation.
|
||||
// InProgress - the standard value for any process that has not
|
||||
// arrived at an end state. The two end states are Failed and
|
||||
// Completed.
|
||||
// arrived at an end state. The end states are Failed, Completed,
|
||||
// or NoData.
|
||||
// Failed - the operation was unable to begin processing data at all.
|
||||
// No items have been written by the consumer.
|
||||
// Completed - the operation was able to process one or more of the
|
||||
// items in the request. Both partial success (0 < N < len(items)
|
||||
// errored) and total success (0 errors) are set as Completed.
|
||||
// NoData - only occurs when no data was involved in an operation.
|
||||
// For example, if a backup is requested for a specific user's
|
||||
// mail, but that account contains zero mail messages, the backup
|
||||
// contains No Data.
|
||||
type opStatus int
|
||||
|
||||
//go:generate stringer -type=opStatus -linecomment
|
||||
@ -28,6 +32,7 @@ const (
|
||||
InProgress // In Progress
|
||||
Completed // Completed
|
||||
Failed // Failed
|
||||
NoData // No Data
|
||||
)
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
@ -12,11 +12,12 @@ func _() {
|
||||
_ = x[InProgress-1]
|
||||
_ = x[Completed-2]
|
||||
_ = x[Failed-3]
|
||||
_ = x[NoData-4]
|
||||
}
|
||||
|
||||
const _opStatus_name = "Status UnknownIn ProgressCompletedFailed"
|
||||
const _opStatus_name = "Status UnknownIn ProgressCompletedFailedNo Data"
|
||||
|
||||
var _opStatus_index = [...]uint8{0, 14, 25, 34, 40}
|
||||
var _opStatus_index = [...]uint8{0, 14, 25, 34, 40, 47}
|
||||
|
||||
func (i opStatus) String() string {
|
||||
if i < 0 || i >= opStatus(len(_opStatus_index)-1) {
|
||||
|
||||
@ -195,6 +195,10 @@ func (op *RestoreOperation) persistResults(
|
||||
opStats.writeErr)
|
||||
}
|
||||
|
||||
if opStats.readErr == nil && opStats.writeErr == nil && opStats.gc.Successful == 0 {
|
||||
op.Status = NoData
|
||||
}
|
||||
|
||||
op.Results.ReadErrors = opStats.readErr
|
||||
op.Results.WriteErrors = opStats.writeErr
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@ -38,56 +37,83 @@ func TestRestoreOpSuite(t *testing.T) {
|
||||
suite.Run(t, new(RestoreOpSuite))
|
||||
}
|
||||
|
||||
// TODO: after modelStore integration is added, mock the store and/or
|
||||
// move this to an integration test.
|
||||
func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
|
||||
var (
|
||||
t = suite.T()
|
||||
ctx = context.Background()
|
||||
|
||||
ctx = context.Background()
|
||||
kw = &kopia.Wrapper{}
|
||||
sw = &store.Wrapper{}
|
||||
acct = account.Account{}
|
||||
now = time.Now()
|
||||
rs = restoreStats{
|
||||
started: true,
|
||||
readErr: multierror.Append(nil, assert.AnError),
|
||||
writeErr: assert.AnError,
|
||||
resourceCount: 1,
|
||||
bytesRead: &stats.ByteCounter{
|
||||
NumBytes: 42,
|
||||
},
|
||||
cs: []data.Collection{&exchange.Collection{}},
|
||||
gc: &support.ConnectorOperationStatus{
|
||||
ObjectCount: 1,
|
||||
},
|
||||
}
|
||||
dest = control.DefaultRestoreDestination(common.SimpleDateTimeFormat)
|
||||
)
|
||||
|
||||
op, err := NewRestoreOperation(
|
||||
ctx,
|
||||
control.Options{},
|
||||
kw,
|
||||
sw,
|
||||
acct,
|
||||
"foo",
|
||||
selectors.Selector{},
|
||||
dest,
|
||||
evmock.NewBus())
|
||||
require.NoError(t, err)
|
||||
table := []struct {
|
||||
expectStatus opStatus
|
||||
expectErr assert.ErrorAssertionFunc
|
||||
stats restoreStats
|
||||
}{
|
||||
{
|
||||
expectStatus: Completed,
|
||||
expectErr: assert.NoError,
|
||||
stats: restoreStats{
|
||||
started: true,
|
||||
resourceCount: 1,
|
||||
bytesRead: &stats.ByteCounter{
|
||||
NumBytes: 42,
|
||||
},
|
||||
cs: []data.Collection{&exchange.Collection{}},
|
||||
gc: &support.ConnectorOperationStatus{
|
||||
ObjectCount: 1,
|
||||
Successful: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expectStatus: Failed,
|
||||
expectErr: assert.Error,
|
||||
stats: restoreStats{
|
||||
started: false,
|
||||
bytesRead: &stats.ByteCounter{},
|
||||
gc: &support.ConnectorOperationStatus{},
|
||||
},
|
||||
},
|
||||
{
|
||||
expectStatus: NoData,
|
||||
expectErr: assert.NoError,
|
||||
stats: restoreStats{
|
||||
started: true,
|
||||
bytesRead: &stats.ByteCounter{},
|
||||
cs: []data.Collection{},
|
||||
gc: &support.ConnectorOperationStatus{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.expectStatus.String(), func(t *testing.T) {
|
||||
op, err := NewRestoreOperation(
|
||||
ctx,
|
||||
control.Options{},
|
||||
kw,
|
||||
sw,
|
||||
acct,
|
||||
"foo",
|
||||
selectors.Selector{},
|
||||
dest,
|
||||
evmock.NewBus())
|
||||
require.NoError(t, err)
|
||||
test.expectErr(t, op.persistResults(ctx, now, &test.stats))
|
||||
|
||||
require.NoError(t, op.persistResults(ctx, now, &rs))
|
||||
|
||||
assert.Equal(t, op.Status.String(), Completed.String(), "status")
|
||||
assert.Equal(t, op.Results.ItemsRead, len(rs.cs), "items read")
|
||||
assert.Equal(t, op.Results.ReadErrors, rs.readErr, "read errors")
|
||||
assert.Equal(t, op.Results.ItemsWritten, rs.gc.Successful, "items written")
|
||||
assert.Equal(t, rs.bytesRead.NumBytes, op.Results.BytesRead, "resource owners")
|
||||
assert.Equal(t, rs.resourceCount, op.Results.ResourceOwners, "resource owners")
|
||||
assert.Equal(t, op.Results.WriteErrors, rs.writeErr, "write errors")
|
||||
assert.Equal(t, op.Results.StartedAt, now, "started at")
|
||||
assert.Less(t, now, op.Results.CompletedAt, "completed at")
|
||||
assert.Equal(t, test.expectStatus.String(), op.Status.String(), "status")
|
||||
assert.Equal(t, len(test.stats.cs), op.Results.ItemsRead, "items read")
|
||||
assert.Equal(t, test.stats.readErr, op.Results.ReadErrors, "read errors")
|
||||
assert.Equal(t, test.stats.gc.Successful, op.Results.ItemsWritten, "items written")
|
||||
assert.Equal(t, test.stats.bytesRead.NumBytes, op.Results.BytesRead, "resource owners")
|
||||
assert.Equal(t, test.stats.resourceCount, op.Results.ResourceOwners, "resource owners")
|
||||
assert.Equal(t, test.stats.writeErr, op.Results.WriteErrors, "write errors")
|
||||
assert.Equal(t, now, op.Results.StartedAt, "started at")
|
||||
assert.Less(t, now, op.Results.CompletedAt, "completed at")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user