Skip to content

Commit 37ddc58

Browse files
authored
feat: add storage retry tests (#51)
1 parent acf856b commit 37ddc58

File tree

3 files changed

+255
-8
lines changed

3 files changed

+255
-8
lines changed

storage/v1/proto/google/cloud/conformance/storage/v1/tests.proto

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,59 @@ message PostPolicyV4Test {
158158
PolicyInput policyInput = 2;
159159
PolicyOutput policyOutput = 3;
160160
}
161+
162+
/*
163+
------------------------------------------------------------------------------
164+
Data types for retry conformance tests
165+
------------------------------------------------------------------------------
166+
*/
167+
168+
message RetryTests {
169+
repeated RetryTest retryTests = 1;
170+
}
171+
172+
// A list of instructions to send as headers to the GCS emulator. Each
173+
// instruction will force a specified failure for that request.
174+
message InstructionList {
175+
repeated string instructions = 1;
176+
}
177+
178+
// Test resources that are necessary for a method call. For example,
179+
// storage.objects.get would require BUCKET and OBJECT.
180+
enum Resource {
181+
BUCKET = 0;
182+
OBJECT = 1;
183+
NOTIFICATION = 2;
184+
HMAC_KEY = 3;
185+
}
186+
187+
// A particular storage API method and required resources in order to test it.
188+
// Methods must be implemented in tests for each language.
189+
message Method {
190+
string name = 1; // e.g. storage.objects.get
191+
repeated Resource resources = 2;
192+
}
193+
194+
// Schema for a retry test, corresponding to a single scenario from the design
195+
// doc.
196+
message RetryTest {
197+
// Scenario number
198+
int32 id = 1;
199+
200+
// Human-readable description of the test case.
201+
string description = 2;
202+
203+
// list of emulator instruction sets.
204+
repeated InstructionList cases = 3;
205+
206+
// List of API methods to be tested.
207+
repeated Method methods = 4;
208+
209+
// Whether a precondition is provided (for conditionally-idempotent methods
210+
// only).
211+
bool preconditionProvided = 5;
212+
213+
// Whether we expect the method calls to eventually succeed after the client
214+
// library retries.
215+
bool expectSuccess = 6;
216+
}

storage/v1/retry_tests.json

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
{
2+
"retryTests": [
3+
{
4+
"id": 1,
5+
"description": "always_idempotent",
6+
"cases": [
7+
{
8+
"instructions": ["return-503", "return-503"]
9+
},
10+
{
11+
"instructions": ["return-reset-connection", "return-reset-connection"]
12+
},
13+
{
14+
"instructions": ["return-reset-connection", "return-503"]
15+
}
16+
],
17+
"methods": [
18+
{"name": "storage.bucket_acl.get", "resources": ["BUCKET"]},
19+
{"name": "storage.bucket_acl.list", "resources": ["BUCKET"]},
20+
{"name": "storage.buckets.delete", "resources": ["BUCKET"]},
21+
{"name": "storage.buckets.get", "resources": ["BUCKET"]},
22+
{"name": "storage.buckets.getIamPolicy", "resources": ["BUCKET"]},
23+
{"name": "storage.buckets.insert", "resources": []},
24+
{"name": "storage.buckets.list", "resources": ["BUCKET"]},
25+
{"name": "storage.buckets.lockRetentionPolicy", "resources": ["BUCKET"]},
26+
{"name": "storage.buckets.testIamPermission", "resources": ["BUCKET"]},
27+
{"name": "storage.default_object_acl.get", "resources": ["BUCKET"]},
28+
{"name": "storage.default_object_acl.list", "resources": ["BUCKET"]},
29+
{"name": "storage.hmacKey.delete", "resources": []},
30+
{"name": "storage.hmacKey.get", "resources": []},
31+
{"name": "storage.hmacKey.list", "resources": []},
32+
{"name": "storage.notification.delete", "resources": ["BUCKET", "NOTIFICATION"]},
33+
{"name": "storage.notification.get", "resources": ["BUCKET", "NOTIFICATION"]},
34+
{"name": "storage.notification.list", "resources": ["BUCKET", "NOTIFICATION"]},
35+
{"name": "storage.object_acl.get", "resources": ["BUCKET", "OBJECT"]},
36+
{"name": "storage.object_acl.list", "resources": ["BUCKET", "OBJECT"]},
37+
{"name": "storage.objects.get", "resources": ["BUCKET", "OBJECT"]},
38+
{"name": "storage.objects.list", "resources": ["BUCKET", "OBJECT"]},
39+
{"name": "storage.serviceaccount.get", "resources": []}
40+
],
41+
"preconditionProvided": false,
42+
"expectSuccess": true
43+
},
44+
{
45+
"id": 2,
46+
"description": "conditionally_idempotent_retries_when_precondition_is_present",
47+
"cases": [
48+
{
49+
"instructions": ["return-503", "return-503"]
50+
},
51+
{
52+
"instructions": ["return-reset-connection", "return-reset-connection"]
53+
},
54+
{
55+
"instructions": ["return-reset-connection", "return-503"]
56+
}
57+
],
58+
"methods": [
59+
{"name": "storage.buckets.patch", "resources": ["BUCKET"]},
60+
{"name": "storage.buckets.setIamPolicy", "resources": ["BUCKET"]},
61+
{"name": "storage.buckets.update", "resources": ["BUCKET"]},
62+
{"name": "storage.hmacKey.update", "resources": []},
63+
{"name": "storage.objects.compose", "resources": ["BUCKET", "OBJECT"]},
64+
{"name": "storage.objects.copy", "resources": ["BUCKET", "OBJECT"]},
65+
{"name": "storage.objects.delete", "resources": ["BUCKET", "OBJECT"]},
66+
{"name": "storage.objects.insert", "resources": ["BUCKET"]},
67+
{"name": "storage.objects.patch", "resources": ["BUCKET", "OBJECT"]},
68+
{"name": "storage.objects.rewrite", "resources": ["BUCKET", "OBJECT"]},
69+
{"name": "storage.objects.update", "resources": ["BUCKET", "OBJECT"]}
70+
],
71+
"preconditionProvided": true,
72+
"expectSuccess": true
73+
},
74+
{
75+
"id": 3,
76+
"description": "conditionally_idempotent_no_retries_when_precondition_is_absent",
77+
"cases": [
78+
{
79+
"instructions": ["return-503", "return-503"]
80+
},
81+
{
82+
"instructions": ["return-reset-connection", "return-reset-connection"]
83+
},
84+
{
85+
"instructions": ["return-reset-connection", "return-503"]
86+
}
87+
],
88+
"methods": [
89+
{"name": "storage.buckets.patch", "resources": ["BUCKET"]},
90+
{"name": "storage.buckets.setIamPolicy", "resources": ["BUCKET"]},
91+
{"name": "storage.buckets.update", "resources": ["BUCKET"]},
92+
{"name": "storage.hmacKey.update", "resources": []},
93+
{"name": "storage.objects.compose", "resources": ["BUCKET", "OBJECT"]},
94+
{"name": "storage.objects.copy", "resources": ["BUCKET", "OBJECT"]},
95+
{"name": "storage.objects.delete", "resources": ["BUCKET", "OBJECT"]},
96+
{"name": "storage.objects.insert", "resources": ["BUCKET"]},
97+
{"name": "storage.objects.patch", "resources": ["BUCKET", "OBJECT"]},
98+
{"name": "storage.objects.rewrite", "resources": ["BUCKET", "OBJECT"]},
99+
{"name": "storage.objects.update", "resources": ["BUCKET", "OBJECT"]}
100+
],
101+
"preconditionProvided": false,
102+
"expectSuccess": false
103+
},
104+
{
105+
"id": 4,
106+
"description": "non_idempotent",
107+
"cases": [
108+
{
109+
"instructions": []
110+
}
111+
],
112+
"methods": [
113+
],
114+
"preconditionProvided": false,
115+
"expectSuccess": false
116+
},
117+
{
118+
"id": 6,
119+
"description": "non-retryable errors",
120+
"cases": [
121+
{
122+
"instructions": ["return-400", "return-400"]
123+
},
124+
{
125+
"instructions": ["return-401", "return-401"]
126+
}
127+
],
128+
"methods": [
129+
{"name": "storage.bucket_acl.delete", "resources": ["BUCKET"]},
130+
{"name": "storage.bucket_acl.get", "resources": ["BUCKET"]},
131+
{"name": "storage.bucket_acl.insert", "resources": ["BUCKET"]},
132+
{"name": "storage.bucket_acl.list", "resources": ["BUCKET"]},
133+
{"name": "storage.bucket_acl.patch", "resources": ["BUCKET"]},
134+
{"name": "storage.bucket_acl.update", "resources": ["BUCKET"]},
135+
{"name": "storage.buckets.delete", "resources": ["BUCKET"]},
136+
{"name": "storage.buckets.get", "resources": ["BUCKET"]},
137+
{"name": "storage.buckets.getIamPolicy", "resources": ["BUCKET"]},
138+
{"name": "storage.buckets.insert", "resources": ["BUCKET"]},
139+
{"name": "storage.buckets.list", "resources": ["BUCKET"]},
140+
{"name": "storage.buckets.lockRetentionPolicy", "resources": ["BUCKET"]},
141+
{"name": "storage.buckets.patch", "resources": ["BUCKET"]},
142+
{"name": "storage.buckets.setIamPolicy", "resources": ["BUCKET"]},
143+
{"name": "storage.buckets.testIamPermission", "resources": ["BUCKET"]},
144+
{"name": "storage.buckets.update", "resources": ["BUCKET"]},
145+
{"name": "storage.default_object_acl.delete", "resources": ["BUCKET"]},
146+
{"name": "storage.default_object_acl.get", "resources": ["BUCKET"]},
147+
{"name": "storage.default_object_acl.insert", "resources": ["BUCKET"]},
148+
{"name": "storage.default_object_acl.list", "resources": ["BUCKET"]},
149+
{"name": "storage.default_object_acl.patch", "resources": ["BUCKET"]},
150+
{"name": "storage.default_object_acl.update", "resources": ["BUCKET"]},
151+
{"name": "storage.hmacKey.create", "resources": []},
152+
{"name": "storage.hmacKey.delete", "resources": []},
153+
{"name": "storage.hmacKey.get", "resources": []},
154+
{"name": "storage.hmacKey.list", "resources": []},
155+
{"name": "storage.hmacKey.update", "resources": []},
156+
{"name": "storage.notification.delete", "resources": ["BUCKET", "NOTIFICATION"]},
157+
{"name": "storage.notification.get", "resources": ["BUCKET", "NOTIFICATION"]},
158+
{"name": "storage.notification.insert", "resources": ["BUCKET", "NOTIFICATION"]},
159+
{"name": "storage.notification.list", "resources": ["BUCKET", "NOTIFICATION"]},
160+
{"name": "storage.object_acl.delete", "resources": ["BUCKET", "OBJECT"]},
161+
{"name": "storage.object_acl.get", "resources": ["BUCKET", "OBJECT"]},
162+
{"name": "storage.object_acl.insert", "resources": ["BUCKET", "OBJECT"]},
163+
{"name": "storage.object_acl.list", "resources": ["BUCKET", "OBJECT"]},
164+
{"name": "storage.object_acl.patch", "resources": ["BUCKET", "OBJECT"]},
165+
{"name": "storage.object_acl.update", "resources": ["BUCKET", "OBJECT"]},
166+
{"name": "storage.objects.compose", "resources": ["BUCKET", "OBJECT"]},
167+
{"name": "storage.objects.copy", "resources": ["BUCKET", "OBJECT"]},
168+
{"name": "storage.objects.delete", "resources": ["BUCKET", "OBJECT"]},
169+
{"name": "storage.objects.get", "resources": ["BUCKET", "OBJECT"]},
170+
{"name": "storage.objects.insert", "resources": ["BUCKET"]},
171+
{"name": "storage.objects.list", "resources": ["BUCKET", "OBJECT"]},
172+
{"name": "storage.objects.patch", "resources": ["BUCKET", "OBJECT"]},
173+
{"name": "storage.objects.rewrite", "resources": ["BUCKET", "OBJECT"]},
174+
{"name": "storage.objects.update", "resources": ["BUCKET", "OBJECT"]},
175+
{"name": "storage.serviceaccount.get", "resources": []}
176+
],
177+
"preconditionProvided": false,
178+
"expectSuccess": false
179+
}
180+
]
181+
}

storage/v1/validator.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package main
1616

1717
import (
1818
"bytes"
19+
"github.com/golang/protobuf/proto"
1920
"io/ioutil"
2021
"log"
2122
"os"
@@ -40,17 +41,26 @@ func main() {
4041
continue
4142
}
4243

43-
log.Printf("Validating: %v/%v", dir, f.Name())
44+
filename := dir + "/" + f.Name()
45+
if f.Name() == "v4_signatures.json" {
46+
validateDataAgainstProtos(filename, &storage_v1_tests.TestFile{})
47+
}
4448

45-
inBytes, err := ioutil.ReadFile(dir + "/" + f.Name())
46-
if err != nil {
47-
log.Fatal(err)
49+
if f.Name() == "retry_tests.json" {
50+
validateDataAgainstProtos(filename, &storage_v1_tests.RetryTests{})
4851
}
52+
}
53+
}
4954

50-
var testfile storage_v1_tests.TestFile
55+
func validateDataAgainstProtos(filename string, protoType proto.Message) {
56+
log.Printf("Validating: %v", filename)
5157

52-
if err := jsonpb.Unmarshal(bytes.NewBuffer(inBytes), &testfile); err != nil {
53-
log.Fatal(err)
54-
}
58+
inBytes, err := ioutil.ReadFile(filename)
59+
if err != nil {
60+
log.Fatal(err)
61+
}
62+
63+
if err := jsonpb.Unmarshal(bytes.NewBuffer(inBytes), protoType); err != nil {
64+
log.Fatal(err)
5565
}
5666
}

0 commit comments

Comments
 (0)