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)
|
opStats.writeErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opStats.readErr == nil && opStats.writeErr == nil && opStats.gc.Successful == 0 {
|
||||||
|
op.Status = NoData
|
||||||
|
}
|
||||||
|
|
||||||
op.Results.ReadErrors = opStats.readErr
|
op.Results.ReadErrors = opStats.readErr
|
||||||
op.Results.WriteErrors = opStats.writeErr
|
op.Results.WriteErrors = opStats.writeErr
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
multierror "github.com/hashicorp/go-multierror"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -35,52 +34,79 @@ func TestBackupOpSuite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *BackupOpSuite) TestBackupOperation_PersistResults() {
|
func (suite *BackupOpSuite) TestBackupOperation_PersistResults() {
|
||||||
t := suite.T()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
kw = &kopia.Wrapper{}
|
ctx = context.Background()
|
||||||
sw = &store.Wrapper{}
|
kw = &kopia.Wrapper{}
|
||||||
acct = account.Account{}
|
sw = &store.Wrapper{}
|
||||||
now = time.Now()
|
acct = account.Account{}
|
||||||
stats = backupStats{
|
now = time.Now()
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
op, err := NewBackupOperation(
|
table := []struct {
|
||||||
ctx,
|
expectStatus opStatus
|
||||||
control.Options{},
|
expectErr assert.ErrorAssertionFunc
|
||||||
kw,
|
stats backupStats
|
||||||
sw,
|
}{
|
||||||
acct,
|
{
|
||||||
selectors.Selector{},
|
expectStatus: Completed,
|
||||||
evmock.NewBus())
|
expectErr: assert.NoError,
|
||||||
require.NoError(t, err)
|
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, test.expectStatus.String(), op.Status.String(), "status")
|
||||||
|
assert.Equal(t, test.stats.gc.Successful, op.Results.ItemsRead, "items read")
|
||||||
assert.Equal(t, op.Status.String(), Completed.String(), "status")
|
assert.Equal(t, test.stats.readErr, op.Results.ReadErrors, "read errors")
|
||||||
assert.Equal(t, op.Results.ItemsRead, stats.gc.Successful, "items read")
|
assert.Equal(t, test.stats.k.TotalFileCount, op.Results.ItemsWritten, "items written")
|
||||||
assert.Equal(t, op.Results.ReadErrors, stats.readErr, "read errors")
|
assert.Equal(t, test.stats.k.TotalHashedBytes, op.Results.BytesRead, "bytes read")
|
||||||
assert.Equal(t, op.Results.ItemsWritten, stats.k.TotalFileCount, "items written")
|
assert.Equal(t, test.stats.k.TotalUploadedBytes, op.Results.BytesUploaded, "bytes written")
|
||||||
assert.Equal(t, stats.k.TotalHashedBytes, op.Results.BytesRead, "bytes read")
|
assert.Equal(t, test.stats.resourceCount, op.Results.ResourceOwners, "resource owners")
|
||||||
assert.Equal(t, stats.k.TotalUploadedBytes, op.Results.BytesUploaded, "bytes written")
|
assert.Equal(t, test.stats.writeErr, op.Results.WriteErrors, "write errors")
|
||||||
assert.Equal(t, op.Results.ResourceOwners, stats.resourceCount, "resource owners")
|
assert.Equal(t, now, op.Results.StartedAt, "started at")
|
||||||
assert.Equal(t, op.Results.WriteErrors, stats.writeErr, "write errors")
|
assert.Less(t, now, op.Results.CompletedAt, "completed at")
|
||||||
assert.Equal(t, op.Results.StartedAt, now, "started at")
|
})
|
||||||
assert.Less(t, now, op.Results.CompletedAt, "completed at")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -13,13 +13,17 @@ import (
|
|||||||
|
|
||||||
// opStatus describes the current status of an operation.
|
// opStatus describes the current status of an operation.
|
||||||
// InProgress - the standard value for any process that has not
|
// InProgress - the standard value for any process that has not
|
||||||
// arrived at an end state. The two end states are Failed and
|
// arrived at an end state. The end states are Failed, Completed,
|
||||||
// Completed.
|
// or NoData.
|
||||||
// Failed - the operation was unable to begin processing data at all.
|
// Failed - the operation was unable to begin processing data at all.
|
||||||
// No items have been written by the consumer.
|
// No items have been written by the consumer.
|
||||||
// Completed - the operation was able to process one or more of the
|
// Completed - the operation was able to process one or more of the
|
||||||
// items in the request. Both partial success (0 < N < len(items)
|
// items in the request. Both partial success (0 < N < len(items)
|
||||||
// errored) and total success (0 errors) are set as Completed.
|
// 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
|
type opStatus int
|
||||||
|
|
||||||
//go:generate stringer -type=opStatus -linecomment
|
//go:generate stringer -type=opStatus -linecomment
|
||||||
@ -28,6 +32,7 @@ const (
|
|||||||
InProgress // In Progress
|
InProgress // In Progress
|
||||||
Completed // Completed
|
Completed // Completed
|
||||||
Failed // Failed
|
Failed // Failed
|
||||||
|
NoData // No Data
|
||||||
)
|
)
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------
|
// --------------------------------------------------------------------------------
|
||||||
|
|||||||
@ -12,11 +12,12 @@ func _() {
|
|||||||
_ = x[InProgress-1]
|
_ = x[InProgress-1]
|
||||||
_ = x[Completed-2]
|
_ = x[Completed-2]
|
||||||
_ = x[Failed-3]
|
_ = 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 {
|
func (i opStatus) String() string {
|
||||||
if i < 0 || i >= opStatus(len(_opStatus_index)-1) {
|
if i < 0 || i >= opStatus(len(_opStatus_index)-1) {
|
||||||
|
|||||||
@ -195,6 +195,10 @@ func (op *RestoreOperation) persistResults(
|
|||||||
opStats.writeErr)
|
opStats.writeErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opStats.readErr == nil && opStats.writeErr == nil && opStats.gc.Successful == 0 {
|
||||||
|
op.Status = NoData
|
||||||
|
}
|
||||||
|
|
||||||
op.Results.ReadErrors = opStats.readErr
|
op.Results.ReadErrors = opStats.readErr
|
||||||
op.Results.WriteErrors = opStats.writeErr
|
op.Results.WriteErrors = opStats.writeErr
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
multierror "github.com/hashicorp/go-multierror"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
@ -38,56 +37,83 @@ func TestRestoreOpSuite(t *testing.T) {
|
|||||||
suite.Run(t, new(RestoreOpSuite))
|
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() {
|
func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
|
||||||
var (
|
var (
|
||||||
t = suite.T()
|
ctx = context.Background()
|
||||||
ctx = context.Background()
|
|
||||||
|
|
||||||
kw = &kopia.Wrapper{}
|
kw = &kopia.Wrapper{}
|
||||||
sw = &store.Wrapper{}
|
sw = &store.Wrapper{}
|
||||||
acct = account.Account{}
|
acct = account.Account{}
|
||||||
now = time.Now()
|
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)
|
dest = control.DefaultRestoreDestination(common.SimpleDateTimeFormat)
|
||||||
)
|
)
|
||||||
|
|
||||||
op, err := NewRestoreOperation(
|
table := []struct {
|
||||||
ctx,
|
expectStatus opStatus
|
||||||
control.Options{},
|
expectErr assert.ErrorAssertionFunc
|
||||||
kw,
|
stats restoreStats
|
||||||
sw,
|
}{
|
||||||
acct,
|
{
|
||||||
"foo",
|
expectStatus: Completed,
|
||||||
selectors.Selector{},
|
expectErr: assert.NoError,
|
||||||
dest,
|
stats: restoreStats{
|
||||||
evmock.NewBus())
|
started: true,
|
||||||
require.NoError(t, err)
|
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, test.expectStatus.String(), op.Status.String(), "status")
|
||||||
|
assert.Equal(t, len(test.stats.cs), op.Results.ItemsRead, "items read")
|
||||||
assert.Equal(t, op.Status.String(), Completed.String(), "status")
|
assert.Equal(t, test.stats.readErr, op.Results.ReadErrors, "read errors")
|
||||||
assert.Equal(t, op.Results.ItemsRead, len(rs.cs), "items read")
|
assert.Equal(t, test.stats.gc.Successful, op.Results.ItemsWritten, "items written")
|
||||||
assert.Equal(t, op.Results.ReadErrors, rs.readErr, "read errors")
|
assert.Equal(t, test.stats.bytesRead.NumBytes, op.Results.BytesRead, "resource owners")
|
||||||
assert.Equal(t, op.Results.ItemsWritten, rs.gc.Successful, "items written")
|
assert.Equal(t, test.stats.resourceCount, op.Results.ResourceOwners, "resource owners")
|
||||||
assert.Equal(t, rs.bytesRead.NumBytes, op.Results.BytesRead, "resource owners")
|
assert.Equal(t, test.stats.writeErr, op.Results.WriteErrors, "write errors")
|
||||||
assert.Equal(t, rs.resourceCount, op.Results.ResourceOwners, "resource owners")
|
assert.Equal(t, now, op.Results.StartedAt, "started at")
|
||||||
assert.Equal(t, op.Results.WriteErrors, rs.writeErr, "write errors")
|
assert.Less(t, now, op.Results.CompletedAt, "completed at")
|
||||||
assert.Equal(t, op.Results.StartedAt, now, "started at")
|
})
|
||||||
assert.Less(t, now, op.Results.CompletedAt, "completed at")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user