Merge lp:~rvb/gwacl/api-polling-fix into lp:gwacl

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: 77
Merged at revision: 78
Proposed branch: lp:~rvb/gwacl/api-polling-fix
Merge into: lp:gwacl
Diff against target: 284 lines (+123/-32)
3 files modified
managementapi.go (+8/-5)
managementapi_test.go (+111/-26)
xmlobjects.go (+4/-1)
To merge this branch: bzr merge lp:~rvb/gwacl/api-polling-fix
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+156125@code.launchpad.net

Commit message

Fix api.blockUntilCompleted so that it returns an error when the asynchronous operation fails.

Description of the change

Drive-by fix: add tests for blockUntilCompleted.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Excellent work. I suppose we'll want to improve the failure reporting later; we get some more helpful information from asynchronous operations IIRC.

This bit in managementapi_test.go looks a little weird:

80 +func makeAPI(c *C) *ManagementAPI {
81 + subscriptionId := "subscriptionId"
82 + subscriptionId = subscriptionId

What does line 82 do for us and why doesn't the compiler warn us about it?

review: Approve
Revision history for this message
Raphaël Badin (rvb) wrote :

> 80 +func makeAPI(c *C) *ManagementAPI {
> 81 + subscriptionId := "subscriptionId"
> 82 + subscriptionId = subscriptionId
>
> What does line 82 do for us and why doesn't the compiler warn us about it?

Oosp, a copy and paste error I'm guessing… I don't see what the compiler would have to say about it… apart from the fact that this line does nothing :).

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Attempt to merge into lp:gwacl failed due to conflicts:

text conflict in managementapi_test.go

lp:~rvb/gwacl/api-polling-fix updated
77. By Raphaël Badin

Merge trunk.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Yes, my point about the compiler was that the Go people made such a big thing about rooting out unused variables and such — but then they happily accept dead assignments, self-assignments, unused functions and classes, and ignored error values.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'managementapi.go'
2--- managementapi.go 2013-03-29 12:22:47 +0000
3+++ managementapi.go 2013-03-29 13:33:21 +0000
4@@ -42,22 +42,25 @@
5 var pollerInterval = 10 * time.Second
6
7 // blockUntilCompleted blocks until the operation referenced in the headers of the given response
8-// is completed.
9+// is completed. It returns an error if the given response does not contain
10+// a reference to an asychronous operation, if the response from the server
11+// cannot be parsed, or if the asynchronous operation fails.
12 // Internally, it polls the server at regular intervals until the operation is completed.
13 func (api *ManagementAPI) blockUntilCompleted(response *x509Response) error {
14- // TODO: blockUntilCompleted should return an error when the operation
15- // fails. Right now it simply block until the operation fails or
16- // succeeds.
17 if pollerInterval != 0 {
18 operationID, err := getOperationID(response)
19 if err != nil {
20 return err
21 }
22 poller := newOperationPoller(api, operationID)
23- _, err = performOperationPolling(poller, pollerInterval)
24+ operation, err := performOperationPolling(poller, pollerInterval)
25 if err != nil {
26 return err
27 }
28+ if operation.Status != SucceededOperationStatus {
29+ err := fmt.Errorf("asychronous operation failed (status: %v)", operation.Status)
30+ return err
31+ }
32 }
33 return nil
34 }
35
36=== modified file 'managementapi_test.go'
37--- managementapi_test.go 2013-03-29 12:26:23 +0000
38+++ managementapi_test.go 2013-03-29 13:33:21 +0000
39@@ -4,30 +4,123 @@
40 package gwacl
41
42 import (
43+ "encoding/xml"
44 "fmt"
45 . "launchpad.net/gocheck"
46 "net/http"
47 "time"
48 )
49
50-type managementAPIUtilitiesSuite struct{}
51+type managementAPIUtilitiesSuite struct {
52+ oldPollerInterval time.Duration
53+}
54
55 var _ = Suite(&managementAPIUtilitiesSuite{})
56
57+func (suite *managementAPIUtilitiesSuite) SetUpTest(c *C) {
58+ // Record the original poller interval. Will be restored at the end of
59+ // each test.
60+ suite.oldPollerInterval = pollerInterval
61+ // Set the poller interval to something short to speed up the tests.
62+ pollerInterval = time.Millisecond
63+}
64+
65+func (suite *managementAPIUtilitiesSuite) TearDownTest(c *C) {
66+ // Restore old poller interval.
67+ pollerInterval = suite.oldPollerInterval
68+}
69+
70+func makeX509ResponseWithOperationHeader(operationID string) *x509Response {
71+ header := http.Header{}
72+ header.Set(operationIDHeaderName, operationID)
73+ response := x509Response{
74+ StatusCode: http.StatusAccepted,
75+ Header: header,
76+ }
77+ return &response
78+}
79+
80+func makeAPI(c *C) *ManagementAPI {
81+ subscriptionId := "subscriptionId"
82+ api, err := NewManagementAPI(subscriptionId, "certFile")
83+ c.Assert(err, IsNil)
84+ return api
85+}
86+
87 func (suite *managementAPIUtilitiesSuite) TestGetOperationIDExtractsHeader(c *C) {
88- header := http.Header{}
89 operationID := "operationID"
90- header.Set(operationIDHeaderName, operationID)
91- response := x509Response{
92- StatusCode: http.StatusAccepted,
93- Header: header,
94- }
95+ response := makeX509ResponseWithOperationHeader(operationID)
96
97- returnedOperationID, err := getOperationID(&response)
98+ returnedOperationID, err := getOperationID(response)
99 c.Assert(err, IsNil)
100 c.Check(returnedOperationID, Equals, operationID)
101 }
102
103+func (suite *managementAPIUtilitiesSuite) TestBlockUntilCompletedErrorsIfOperationFails(c *C) {
104+ response := DispatcherResponse{
105+ response: &x509Response{
106+ Body: []byte(fmt.Sprintf(operationXMLTemplate, "Failed")),
107+ StatusCode: http.StatusOK,
108+ },
109+ errorObject: nil}
110+ responses := []DispatcherResponse{response}
111+ rigPreparedResponseDispatcher(responses)
112+ operationID := "operationID"
113+ operationResponse := makeX509ResponseWithOperationHeader(operationID)
114+ api := makeAPI(c)
115+
116+ err := api.blockUntilCompleted(operationResponse)
117+ c.Check(err, ErrorMatches, ".*asychronous operation failed.*")
118+}
119+
120+func (suite *managementAPIUtilitiesSuite) TestBlockUntilCompletedErrorsIfInvalidXML(c *C) {
121+ response := DispatcherResponse{
122+ response: &x509Response{
123+ Body: []byte(">invalidXML<"),
124+ StatusCode: http.StatusOK,
125+ },
126+ errorObject: nil}
127+ responses := []DispatcherResponse{response}
128+ rigPreparedResponseDispatcher(responses)
129+ operationID := "operationID"
130+ operationResponse := makeX509ResponseWithOperationHeader(operationID)
131+ api := makeAPI(c)
132+
133+ err := api.blockUntilCompleted(operationResponse)
134+ c.Check(err, FitsTypeOf, new(xml.SyntaxError))
135+}
136+
137+func (suite *managementAPIUtilitiesSuite) TestBlockUntilCompletedErrorsIfFailsToPoll(c *C) {
138+ response := DispatcherResponse{
139+ response: &x509Response{},
140+ errorObject: fmt.Errorf("unexpected error")}
141+ responses := []DispatcherResponse{response}
142+ rigPreparedResponseDispatcher(responses)
143+ operationID := "operationID"
144+ operationResponse := makeX509ResponseWithOperationHeader(operationID)
145+ api := makeAPI(c)
146+
147+ err := api.blockUntilCompleted(operationResponse)
148+ c.Check(err, ErrorMatches, ".*unexpected error.*")
149+}
150+
151+func (suite *managementAPIUtilitiesSuite) TestBlockUntilCompletedSucceedsIfOperationSucceeds(c *C) {
152+ response := DispatcherResponse{
153+ response: &x509Response{
154+ Body: []byte(fmt.Sprintf(operationXMLTemplate, "Succeeded")),
155+ StatusCode: http.StatusOK,
156+ },
157+ errorObject: nil}
158+ responses := []DispatcherResponse{response}
159+ rigPreparedResponseDispatcher(responses)
160+ operationID := "operationID"
161+ operationResponse := makeX509ResponseWithOperationHeader(operationID)
162+ api := makeAPI(c)
163+
164+ err := api.blockUntilCompleted(operationResponse)
165+ c.Assert(err, IsNil)
166+}
167+
168 type managementAPISuite struct {
169 x509DispatcherFixture
170 oldPollerInterval time.Duration
171@@ -60,14 +153,6 @@
172 c.Assert(api.session.certFile, DeepEquals, session.certFile)
173 }
174
175-func (suite *managementAPISuite) makeAPI(c *C) *ManagementAPI {
176- subscriptionId := "subscriptionId"
177- subscriptionId = subscriptionId
178- api, err := NewManagementAPI(subscriptionId, "certFile")
179- c.Assert(err, IsNil)
180- return api
181-}
182-
183 // setUpDispatcher sets up a request dispatcher that:
184 // - records requests
185 // - returns empty responses
186@@ -107,7 +192,7 @@
187 }
188
189 func (suite *managementAPISuite) TestListHostedServiceDescriptors(c *C) {
190- api := suite.makeAPI(c)
191+ api := makeAPI(c)
192 fixedResponse := x509Response{
193 StatusCode: http.StatusOK,
194 Body: []byte("<HostedServices></HostedServices>"),
195@@ -126,7 +211,7 @@
196 // TODO test that ListHostedServiceDescriptors parses the structures correctly
197
198 func (suite *managementAPISuite) TestLoadHostedService(c *C) {
199- api := suite.makeAPI(c)
200+ api := makeAPI(c)
201 fixedResponse := x509Response{
202 StatusCode: http.StatusOK,
203 Body: []byte("<HostedService></HostedService>"),
204@@ -147,7 +232,7 @@
205 // TODO test that TestLoadHostedService parses the structures correctly
206
207 func (suite *managementAPISuite) TestAddHostedService(c *C) {
208- api := suite.makeAPI(c)
209+ api := makeAPI(c)
210 recordedRequests := setUpDispatcher("operationID")
211 createHostedService := NewCreateHostedServiceWithLocation("testName", "testLabel", "East US")
212 err := api.AddHostedService(createHostedService)
213@@ -159,7 +244,7 @@
214 }
215
216 func (suite *managementAPISuite) TestDeleteHostedService(c *C) {
217- api := suite.makeAPI(c)
218+ api := makeAPI(c)
219 recordedRequests := setUpDispatcher("operationID")
220 hostedServiceName := "testName"
221 err := api.DeleteHostedService(hostedServiceName)
222@@ -170,7 +255,7 @@
223 }
224
225 func (suite *managementAPISuite) TestAddDeployment(c *C) {
226- api := suite.makeAPI(c)
227+ api := makeAPI(c)
228 recordedRequests := setUpDispatcher("operationID")
229 serviceName := "serviceName"
230 configurationSet := NewLinuxProvisioningConfiguration("testHostname12345", "test", "test123#@!", false)
231@@ -187,7 +272,7 @@
232 }
233
234 func (suite *managementAPISuite) TestDeleteDeployment(c *C) {
235- api := suite.makeAPI(c)
236+ api := makeAPI(c)
237 recordedRequests := setUpDispatcher("operationID")
238 hostedServiceName := "testHosterServiceName"
239 deploymentName := "testDeploymentName"
240@@ -199,7 +284,7 @@
241 }
242
243 func (suite *managementAPISuite) TestAddStorageAccount(c *C) {
244- api := suite.makeAPI(c)
245+ api := makeAPI(c)
246 header := http.Header{}
247 header.Set("X-Ms-Request-Id", "operationID")
248 fixedResponse := x509Response{
249@@ -226,8 +311,8 @@
250 primaryKey = "primarykey"
251 secondaryKey = "secondarykey"
252 )
253- api := suite.makeAPI(c)
254- url := api.session.composeURL("services/storageservices/"+accountName)
255+ api := makeAPI(c)
256+ url := api.session.composeURL("services/storageservices/" + accountName)
257 body := fmt.Sprintf(
258 `<StorageService>
259 <Url>%s</Url>
260@@ -251,7 +336,7 @@
261 }
262
263 func (suite *managementAPISuite) TestPerformNodeOperation(c *C) {
264- api := suite.makeAPI(c)
265+ api := makeAPI(c)
266 recordedRequests := setUpDispatcher("operationID")
267 serviceName := "serviceName"
268 deploymentName := "deploymentName"
269
270=== modified file 'xmlobjects.go'
271--- xmlobjects.go 2013-03-27 17:08:58 +0000
272+++ xmlobjects.go 2013-03-29 13:33:21 +0000
273@@ -525,7 +525,10 @@
274 // Operation Services bits
275 //
276
277-var InProgressOperationStatus = "InProgress"
278+const (
279+ InProgressOperationStatus = "InProgress"
280+ SucceededOperationStatus = "Succeeded"
281+)
282
283 type Operation struct {
284 ID string `xml:"ID"`

Subscribers

People subscribed via source and target branches

to all changes: