Merge lp:~jtv/gwacl/block-on-accepted into lp:gwacl

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: 88
Merged at revision: 87
Proposed branch: lp:~jtv/gwacl/block-on-accepted
Merge into: lp:gwacl
Diff against target: 97 lines (+28/-9)
2 files modified
managementapi.go (+20/-8)
x509session.go (+8/-1)
To merge this branch: bzr merge lp:~jtv/gwacl/block-on-accepted
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+156502@code.launchpad.net

Commit message

Block on additional API operations that *might* require polling.

Description of the change

You'll notice that I did not test for this. Believe me, it took a lot of soul-searching. Am I not just making up excuses?

But the cost-benefit ratio is far out of whack. It's just a trivial function call (so in Go, 4 lines of code with a cyclomatic complexity of 2, sigh) and it happens in many many places. Most of this branch would be brain-dead repetitive testing. And the main risk that a test suite should guard us against is for is forgetting to make the call in the first place. If we can forget to add the call in one function, chances are we'll forget the test for that function as well!

Instead, I documented the need to call blockUntilCompleted() on the post(), put(), and delete() methods.

Jeroen

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

Looks good, but I confess I'm not entirely happy with the lack of tests. Looks like waiting for the operation to complete is a pretty important piece of what this lib does now.

> And the main risk that a test suite should guard us against is for is forgetting to make the call in the first place.
> If we can forget to add the call in one function, chances are we'll forget the test for that function as well!

The tests for the management methods are pretty repetitive and we often copy and paste an existing test when creating a new test for a new method. For this reason, I don't think we are at risk of forgetting to test that the call to blockUntilCompleted() is there because it will already be in the copied test.

Also, you talk about adding new methods but the tests also guard us against non-intended changes to the existing code.

How about something along the lines of: http://paste.ubuntu.com/5670027/

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

It's a cool trick, but it's also sneaking yet another aspect into the existing tests, increasing duplication of coverage and reducing exactly the sort of flexibility you were talking about the other day: what will we have to change if Azure works slightly differently than we've been led to believe? And no matter how we look at it, we still have to cover a very wide API surface without omissions. I did consider regressions, but I can't see them as the biggest risk and I am wary of diminishing returns.

So here's another idea: a thin layer on top of get()/post()/delete()/put(), inside ManagementAPI. For get() it can have the deserialization built in, and for post()/delete()/put() it includes the polling. We just make the right way to do it easier than the wrong way, and eliminate some annoying duplication to boot.

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

(Better land this in the meantime, because the clock is ticking and we can resolve this in a fresh branch)

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-04-02 08:51:18 +0000
3+++ managementapi.go 2013-04-02 09:18:24 +0000
4@@ -135,8 +135,11 @@
5 }
6
7 func (api *ManagementAPI) DeleteHostedService(serviceName string) error {
8- _, err := api.session.delete("services/hostedservices/" + serviceName)
9- return err
10+ response, err := api.session.delete("services/hostedservices/" + serviceName)
11+ if err != nil {
12+ return err
13+ }
14+ return api.blockUntilCompleted(response)
15 }
16
17 // AddDeployment adds a virtual machine deployment.
18@@ -156,8 +159,11 @@
19
20 func (api *ManagementAPI) DeleteDeployment(serviceName string, deploymentName string) error {
21 path := "services/hostedservices/" + serviceName + "/deployments/" + deploymentName
22- _, err := api.session.delete(path)
23- return err
24+ response, err := api.session.delete(path)
25+ if err != nil {
26+ return err
27+ }
28+ return api.blockUntilCompleted(response)
29 }
30
31 // AddStorageAccount starts the creation of a storage account. This is
32@@ -179,8 +185,11 @@
33
34 // DeleteStorageAccount deletes a storage account.
35 func (api *ManagementAPI) DeleteStorageAccount(storageAccountName string) error {
36- _, err := api.session.delete("services/storageservices/" + storageAccountName)
37- return err
38+ response, err := api.session.delete("services/storageservices/" + storageAccountName)
39+ if err != nil {
40+ return err
41+ }
42+ return api.blockUntilCompleted(response)
43 }
44
45 // GetStorageAccountKeys retrieves a storage account's primary and secondary
46@@ -200,8 +209,11 @@
47 }
48
49 func (api *ManagementAPI) DeleteDisk(diskName string) error {
50- _, err := api.session.delete("services/disks/" + diskName)
51- return err
52+ response, err := api.session.delete("services/disks/" + diskName)
53+ if err != nil {
54+ return err
55+ }
56+ return api.blockUntilCompleted(response)
57 }
58
59 // Perform an operation on the specified role (as defined by serviceName, deploymentName and
60
61=== modified file 'x509session.go'
62--- x509session.go 2013-04-01 09:16:04 +0000
63+++ x509session.go 2013-04-02 09:18:24 +0000
64@@ -64,6 +64,8 @@
65 // It returns the response body and/or an error. If the error is a
66 // ServerError, the returned body will be the one received from the server.
67 // For any other kind of error, the returned body will be nil.
68+// Be aware that Azure may perform POST operations asynchronously. If you are
69+// not sure, call blockUntilCompleted() on the response.
70 func (session *x509Session) post(path string, body []byte, contentType string) (*x509Response, error) {
71 request := newX509RequestPOST(session.composeURL(path), body, contentType)
72 response, err := _X509Dispatcher(session, request)
73@@ -75,6 +77,8 @@
74 }
75
76 // delete performs a DELETE request to the Azure management API.
77+// Be aware that Azure may perform DELETE operations asynchronously. If you
78+// are not sure, call blockUntilCompleted() on the response.
79 func (session *x509Session) delete(path string) (*x509Response, error) {
80 request := newX509RequestDELETE(session.composeURL(path))
81 response, err := _X509Dispatcher(session, request)
82@@ -86,11 +90,14 @@
83 }
84
85 // put performs a PUT request to the Azure management API.
86+// Be aware that Azure may perform PUT operations asynchronously. If you are
87+// not sure, call blockUntilCompleted() on the response.
88 func (session *x509Session) put(path string, body []byte) (*x509Response, error) {
89 request := newX509RequestPUT(session.composeURL(path), body)
90 response, err := _X509Dispatcher(session, request)
91 if err != nil {
92 return nil, err
93 }
94- return response, session.getServerError(response.StatusCode, response.Body, "POST request failed")
95+ err = session.getServerError(response.StatusCode, response.Body, "POST request failed")
96+ return response, err
97 }

Subscribers

People subscribed via source and target branches