Skip to content

Commit 18fcc2f

Browse files
authored
br: Fail backup if any snapshots failed (#53038) (#53113)
close #53037
1 parent cf18333 commit 18fcc2f

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed

br/pkg/aws/BUILD.bazel

+2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ go_test(
2727
name = "aws_test",
2828
srcs = ["ebs_test.go"],
2929
embed = [":aws"],
30+
shard_count = 3,
3031
deps = [
3132
"@com_github_aws_aws_sdk_go//aws",
3233
"@com_github_aws_aws_sdk_go//service/ec2",
34+
"@com_github_aws_aws_sdk_go//service/ec2/ec2iface",
3335
"@com_github_stretchr_testify//require",
3436
],
3537
)

br/pkg/aws/ebs.go

+3
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ func (e *EC2Session) WaitSnapshotsCreated(snapIDMap map[string]string, progress
251251
if *s.State == ec2.SnapshotStateCompleted {
252252
log.Info("snapshot completed", zap.String("id", *s.SnapshotId))
253253
totalVolumeSize += *s.VolumeSize
254+
} else if *s.State == ec2.SnapshotStateError {
255+
log.Error("snapshot failed", zap.String("id", *s.SnapshotId), zap.String("error", (*s.StateMessage)))
256+
return 0, errors.Errorf("snapshot %s failed", *s.SnapshotId)
254257
} else {
255258
log.Debug("snapshot creating...", zap.Stringer("snap", s))
256259
uncompletedSnapshots = append(uncompletedSnapshots, s.SnapshotId)

br/pkg/aws/ebs_test.go

+126
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
package aws
1515

1616
import (
17+
"context"
1718
"testing"
1819

1920
awsapi "github.com/aws/aws-sdk-go/aws"
2021
"github.com/aws/aws-sdk-go/service/ec2"
22+
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
2123
"github.com/stretchr/testify/require"
2224
)
2325

@@ -76,3 +78,127 @@ func TestHandleDescribeVolumesResponse(t *testing.T) {
7678
require.Equal(t, int64(4), createdVolumeSize)
7779
require.Equal(t, 1, len(unfinishedVolumes))
7880
}
81+
82+
type mockEC2 struct {
83+
ec2iface.EC2API
84+
output ec2.DescribeSnapshotsOutput
85+
}
86+
87+
func (m mockEC2) DescribeSnapshots(*ec2.DescribeSnapshotsInput) (*ec2.DescribeSnapshotsOutput, error) {
88+
return &m.output, nil
89+
}
90+
91+
func NewMockEc2Session(mock mockEC2) *EC2Session {
92+
return &EC2Session{
93+
ec2: mock,
94+
}
95+
}
96+
97+
func TestWaitSnapshotsCreated(t *testing.T) {
98+
snapIdMap := map[string]string{
99+
"vol-1": "snap-1",
100+
"vol-2": "snap-2",
101+
}
102+
103+
cases := []struct {
104+
desc string
105+
snapshotsOutput ec2.DescribeSnapshotsOutput
106+
expectedSize int64
107+
expectErr bool
108+
expectTimeout bool
109+
}{
110+
{
111+
desc: "snapshots are all completed",
112+
snapshotsOutput: ec2.DescribeSnapshotsOutput{
113+
Snapshots: []*ec2.Snapshot{
114+
{
115+
SnapshotId: awsapi.String("snap-1"),
116+
VolumeSize: awsapi.Int64(1),
117+
State: awsapi.String(ec2.SnapshotStateCompleted),
118+
},
119+
{
120+
SnapshotId: awsapi.String("snap-2"),
121+
VolumeSize: awsapi.Int64(2),
122+
State: awsapi.String(ec2.SnapshotStateCompleted),
123+
},
124+
},
125+
},
126+
expectedSize: 3,
127+
expectErr: false,
128+
},
129+
{
130+
desc: "snapshot failed",
131+
snapshotsOutput: ec2.DescribeSnapshotsOutput{
132+
Snapshots: []*ec2.Snapshot{
133+
{
134+
SnapshotId: awsapi.String("snap-1"),
135+
VolumeSize: awsapi.Int64(1),
136+
State: awsapi.String(ec2.SnapshotStateCompleted),
137+
},
138+
{
139+
SnapshotId: awsapi.String("snap-2"),
140+
State: awsapi.String(ec2.SnapshotStateError),
141+
StateMessage: awsapi.String("snapshot failed"),
142+
},
143+
},
144+
},
145+
expectedSize: 0,
146+
expectErr: true,
147+
},
148+
{
149+
desc: "snapshots pending",
150+
snapshotsOutput: ec2.DescribeSnapshotsOutput{
151+
Snapshots: []*ec2.Snapshot{
152+
{
153+
SnapshotId: awsapi.String("snap-1"),
154+
VolumeSize: awsapi.Int64(1),
155+
State: awsapi.String(ec2.SnapshotStateCompleted),
156+
},
157+
{
158+
SnapshotId: awsapi.String("snap-2"),
159+
State: awsapi.String(ec2.SnapshotStatePending),
160+
},
161+
},
162+
},
163+
expectTimeout: true,
164+
},
165+
}
166+
167+
for _, c := range cases {
168+
e := NewMockEc2Session(mockEC2{
169+
output: c.snapshotsOutput,
170+
})
171+
172+
if c.expectTimeout {
173+
func() {
174+
// We wait 5s before checking snapshots
175+
ctx, cancel := context.WithTimeout(context.Background(), 6)
176+
defer cancel()
177+
178+
done := make(chan struct{})
179+
go func() {
180+
_, _ = e.WaitSnapshotsCreated(snapIdMap, nil)
181+
done <- struct{}{}
182+
}()
183+
184+
select {
185+
case <-done:
186+
t.Fatal("WaitSnapshotsCreated should not return before timeout")
187+
case <-ctx.Done():
188+
require.True(t, true)
189+
}
190+
}()
191+
192+
continue
193+
}
194+
195+
size, err := e.WaitSnapshotsCreated(snapIdMap, nil)
196+
if c.expectErr {
197+
require.Error(t, err)
198+
} else {
199+
require.NoError(t, err)
200+
}
201+
202+
require.Equal(t, c.expectedSize, size)
203+
}
204+
}

0 commit comments

Comments
 (0)