Merge lp:~wallyworld/gwacl/azure-management-api-change into lp:gwacl

Proposed by Ian Booth on 2013-10-31
Status: Merged
Approved by: Ian Booth on 2013-10-31
Approved revision: 230
Merged at revision: 227
Proposed branch: lp:~wallyworld/gwacl/azure-management-api-change
Merge into: lp:gwacl
Diff against target: 410 lines (+54/-50)
4 files modified
management_base.go (+22/-18)
management_base_test.go (+30/-30)
xmlobjects.go (+1/-1)
xmlobjects_test.go (+1/-1)
To merge this branch: bzr merge lp:~wallyworld/gwacl/azure-management-api-change
Reviewer Review Type Date Requested Status
Julian Edwards (community) 2013-10-31 Approve on 2013-10-31
Review via email: mp+193368@code.launchpad.net

Commit message

Azure now requires certain requests to the management api to have an updated version number in the header. Such changes are supposed to be backwards compatible but msft failed not to break existing applications. This branch includes the new api version. It also base64 encodes customdata sent with the request, as this also appears to have changed from underneath us.

With these changes, juju bootstrap now completes and the bootstrap instance spins up. Previously it would have errored part way through bootstrap. There still may be an issue with cloud-init completing properly but if this is the case, it appears to be a separate issue and I'd like to land this branch to allow others with Azure credentials to help debug.

Description of the change

Azure now requires certain requests to the management api to have an updated version number in the header. Such changes are supposed to be backwards compatible but msft failed not to break existing applications. This branch includes the new api version. It also base64 encodes customdata sent with the request, as this also appears to have changed from underneath us.

With these changes, juju bootstrap now completes and the bootstrap instance spins up. Previously it would have errored part way through bootstrap. There still may be an issue with cloud-init completing properly but if this is the case, it appears to be a separate issue and I'd like to land this branch to allow others with Azure credentials to help debug.

To post a comment you must log in.
Julian Edwards (julian-edwards) wrote :

<bigjools> wallyworld_: first comment - pease remove the factored version numbers
<wallyworld_> why?
<bigjools> I previously had someone stop doing that - each api request is separately versioned and if you factor it you will introduce subtle bugs
<wallyworld_> it kinda sucks having them all copied and pasted in the code
<bigjools> yes - but it needs to be done
<wallyworld_> ok then
<bigjools> I rooted out at least 3 bugs last time I unfactored it

Julian Edwards (julian-edwards) wrote :

453 - customdata := MakeRandomString(10)
454 + customdata := "customdata"
455 + base64EncodedCustomData := "Y3VzdG9tZGF0YQ=="

I'd feel better if you kept this random and calculated the expected base64 encoding.

Everything else looks cool.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'management_base.go'
2--- management_base.go 2013-08-09 14:59:50 +0000
3+++ management_base.go 2013-10-31 04:14:10 +0000
4@@ -11,6 +11,10 @@
5 "time"
6 )
7
8+// Note: each API call is required to include a version string in the request header.
9+// These may often be the same string, but need to be kept as strings rather than being
10+// pulled out and replaced with a constant, each API call may be individually changed.
11+
12 type ManagementAPI struct {
13 session *x509Session
14 // The interval used when polling the server.
15@@ -124,7 +128,7 @@
16 // HostedService objects.
17 // See http://msdn.microsoft.com/en-us/library/windowsazure/ee460781.aspx
18 func (api *ManagementAPI) ListHostedServices() ([]HostedServiceDescriptor, error) {
19- res, err := api.session.get("services/hostedservices", "2012-03-01")
20+ res, err := api.session.get("services/hostedservices", "2013-10-01")
21 if err != nil {
22 return nil, err
23 }
24@@ -144,7 +148,7 @@
25 if err != nil {
26 return err
27 }
28- response, err := api.session.put(URI, "2012-03-01", []byte(body), "application/xml")
29+ response, err := api.session.put(URI, "2013-10-01", []byte(body), "application/xml")
30 if err != nil {
31 return err
32 }
33@@ -155,7 +159,7 @@
34 // management API.
35 // See http://msdn.microsoft.com/en-us/library/windowsazure/ee460806.aspx
36 func (api *ManagementAPI) GetHostedServiceProperties(
37- serviceName string, embedDetail bool) (*HostedService, error) {
38+serviceName string, embedDetail bool) (*HostedService, error) {
39 checkPathComponents(serviceName)
40 URI := "services/hostedservices/" + serviceName + "?embed-detail="
41 switch embedDetail {
42@@ -164,7 +168,7 @@
43 case false:
44 URI += "false"
45 }
46- res, err := api.session.get(URI, "2012-03-01")
47+ res, err := api.session.get(URI, "2013-10-01")
48 if err != nil {
49 return nil, err
50 }
51@@ -184,7 +188,7 @@
52 if err != nil {
53 return err
54 }
55- response, err := api.session.post(URI, "2012-03-01", []byte(body), "application/xml")
56+ response, err := api.session.post(URI, "2013-10-01", []byte(body), "application/xml")
57 if err != nil {
58 return err
59 }
60@@ -199,7 +203,7 @@
61 func (api *ManagementAPI) CheckHostedServiceNameAvailability(name string) error {
62 var err error
63 response, err := api.session.get(
64- "services/hostedservices/operations/isavailable/"+name, "2012-03-01")
65+ "services/hostedservices/operations/isavailable/"+name, "2013-10-01")
66 if err != nil {
67 return err
68 }
69@@ -240,7 +244,7 @@
70 if err != nil {
71 return err
72 }
73- response, err := api.session.post(URI, "2012-03-01", []byte(body), "application/xml")
74+ response, err := api.session.post(URI, "2013-10-01", []byte(body), "application/xml")
75 if err != nil {
76 return err
77 }
78@@ -251,7 +255,7 @@
79 // See http://msdn.microsoft.com/en-us/library/windowsazure/ee460815.aspx
80 func (api *ManagementAPI) DeleteDeployment(serviceName string, deploymentName string) error {
81 path := "services/hostedservices/" + serviceName + "/deployments/" + deploymentName
82- response, err := api.session.delete(path, "2012-03-01")
83+ response, err := api.session.delete(path, "2013-10-01")
84 if err != nil {
85 if IsNotFoundError(err) {
86 return nil
87@@ -273,7 +277,7 @@
88 checkPathComponents(request.ServiceName)
89 checkPathComponents(request.DeploymentName)
90 path := "services/hostedservices/" + request.ServiceName + "/deployments/" + request.DeploymentName
91- response, err := api.session.get(path, "2012-03-01")
92+ response, err := api.session.get(path, "2013-10-01")
93 if err != nil {
94 return nil, err
95 }
96@@ -298,7 +302,7 @@
97 if err != nil {
98 return err
99 }
100- response, err := api.session.post(uri, "2012-03-01", []byte(body), "application/xml")
101+ response, err := api.session.post(uri, "2013-10-01", []byte(body), "application/xml")
102 if err != nil {
103 return err
104 }
105@@ -395,7 +399,7 @@
106 func (api *ManagementAPI) StartRole(request *StartRoleRequest) error {
107 return api.performRoleOperation(
108 request.ServiceName, request.DeploymentName, request.RoleName,
109- "2012-03-01", startRoleOperation)
110+ "2013-10-01", startRoleOperation)
111 }
112
113 type RestartRoleRequest performRoleOperationRequest
114@@ -405,7 +409,7 @@
115 func (api *ManagementAPI) RestartRole(request *RestartRoleRequest) error {
116 return api.performRoleOperation(
117 request.ServiceName, request.DeploymentName, request.RoleName,
118- "2012-03-01", restartRoleOperation)
119+ "2013-10-01", restartRoleOperation)
120 }
121
122 type ShutdownRoleRequest performRoleOperationRequest
123@@ -415,7 +419,7 @@
124 func (api *ManagementAPI) ShutdownRole(request *ShutdownRoleRequest) error {
125 return api.performRoleOperation(
126 request.ServiceName, request.DeploymentName, request.RoleName,
127- "2012-03-01", shutdownRoleOperation)
128+ "2013-10-01", shutdownRoleOperation)
129 }
130
131 type GetRoleRequest performRoleOperationRequest
132@@ -428,7 +432,7 @@
133 url := ("services/hostedservices/" + request.ServiceName +
134 "/deployments/" + request.DeploymentName + "/roles/" +
135 request.RoleName)
136- response, err := api.session.get(url, "2012-03-01")
137+ response, err := api.session.get(url, "2013-10-01")
138 if err != nil {
139 return nil, err
140 }
141@@ -463,7 +467,7 @@
142 if err != nil {
143 return err
144 }
145- response, err := api.session.put(url, "2012-03-01", []byte(role), "application/xml")
146+ response, err := api.session.put(url, "2013-10-01", []byte(role), "application/xml")
147 if err != nil {
148 return err
149 }
150@@ -483,7 +487,7 @@
151 if err != nil {
152 return err
153 }
154- response, err := api.session.post(url, "2012-03-01", []byte(body), "application/xml")
155+ response, err := api.session.post(url, "2013-10-01", []byte(body), "application/xml")
156 if err != nil {
157 return err
158 }
159@@ -536,7 +540,7 @@
160 // be nil.
161 // See http://msdn.microsoft.com/en-us/library/windowsazure/jj157196.aspx
162 func (api *ManagementAPI) GetNetworkConfiguration() (*NetworkConfiguration, error) {
163- response, err := api.session.get("services/networking/media", "2012-03-01")
164+ response, err := api.session.get("services/networking/media", "2013-10-01")
165 if err != nil {
166 if IsNotFoundError(err) {
167 return nil, nil
168@@ -561,7 +565,7 @@
169 return err
170 }
171 response, err := api.session.put(
172- "services/networking/media", "2012-03-01", []byte(body),
173+ "services/networking/media", "2013-10-01", []byte(body),
174 "application/octet-stream")
175 if err != nil {
176 return err
177
178=== modified file 'management_base_test.go'
179--- management_base_test.go 2013-08-28 00:13:04 +0000
180+++ management_base_test.go 2013-10-31 04:14:10 +0000
181@@ -412,7 +412,7 @@
182
183 c.Assert(err, IsNil)
184 expectedURL := defaultManagement + api.session.subscriptionId + "/services/hostedservices"
185- checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")
186+ checkOneRequest(c, &recordedRequests, expectedURL, "2013-10-01", nil, "GET")
187 c.Assert(descriptors[0].URL, Equals, url)
188 }
189
190@@ -442,7 +442,7 @@
191
192 c.Assert(err, IsNil)
193 expectedURL := defaultManagement + api.session.subscriptionId + "/services/hostedservices/" + serviceName
194- checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", requestPayload, "PUT")
195+ checkOneRequest(c, &recordedRequests, expectedURL, "2013-10-01", requestPayload, "PUT")
196 }
197
198 func assertGetHostedServicePropertiesRequest(c *C, api *ManagementAPI, serviceName string, embedDetail bool, httpRequest *X509Request) {
199@@ -454,7 +454,7 @@
200 }
201 expectedURL := fmt.Sprintf("%s%s/services/hostedservices/%s?%s", defaultManagement,
202 api.session.subscriptionId, serviceName, query)
203- checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")
204+ checkRequest(c, httpRequest, expectedURL, "2013-10-01", nil, "GET")
205 }
206
207 func (suite *managementBaseAPISuite) TestGetHostedServiceProperties_withoutDetails(c *C) {
208@@ -563,7 +563,7 @@
209 expectedURL := defaultManagement + api.session.subscriptionId + "/services/hostedservices"
210 expectedPayload, err := marshalXML(createHostedService)
211 c.Assert(err, IsNil)
212- checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")
213+ checkOneRequest(c, recordedRequests, expectedURL, "2013-10-01", expectedPayload, "POST")
214 }
215
216 func makeAvailabilityResponse(result, reason string) string {
217@@ -591,8 +591,8 @@
218
219 c.Assert(err, IsNil)
220 expectedURL := (defaultManagement + api.session.subscriptionId +
221- "/services/hostedservices/operations/isavailable/" + serviceName)
222- checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")
223+ "/services/hostedservices/operations/isavailable/" + serviceName)
224+ checkOneRequest(c, &recordedRequests, expectedURL, "2013-10-01", nil, "GET")
225 }
226
227 func (*managementBaseAPISuite) TestAddHostedServiceWithBadName(c *C) {
228@@ -613,8 +613,8 @@
229 c.Assert(err, ErrorMatches, reason)
230 c.Check(recordedRequests, HasLen, 1)
231 expectedURL := (defaultManagement + api.session.subscriptionId +
232- "/services/hostedservices/operations/isavailable/" + serviceName)
233- checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")
234+ "/services/hostedservices/operations/isavailable/" + serviceName)
235+ checkOneRequest(c, &recordedRequests, expectedURL, "2013-10-01", nil, "GET")
236 }
237
238 func (*managementBaseAPISuite) TestAddHostedServiceWithServerError(c *C) {
239@@ -689,14 +689,14 @@
240 expectedURL := defaultManagement + api.session.subscriptionId + "/services/hostedservices/" + serviceName + "/deployments"
241 expectedPayload, err := marshalXML(deployment)
242 c.Assert(err, IsNil)
243- checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")
244+ checkOneRequest(c, recordedRequests, expectedURL, "2013-10-01", expectedPayload, "POST")
245 }
246
247 func assertDeleteDeploymentRequest(c *C, api *ManagementAPI, hostedServiceName, deploymentName string, httpRequest *X509Request) {
248 expectedURL := fmt.Sprintf(
249 "%s%s/services/hostedservices/%s/deployments/%s", defaultManagement,
250 api.session.subscriptionId, hostedServiceName, deploymentName)
251- checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "DELETE")
252+ checkRequest(c, httpRequest, expectedURL, "2013-10-01", nil, "DELETE")
253 }
254
255 func (suite *managementBaseAPISuite) TestDeleteDeployment(c *C) {
256@@ -795,7 +795,7 @@
257 expectedURL := fmt.Sprintf(
258 "%s%s/services/hostedservices/%s/deployments/%s", defaultManagement,
259 api.session.subscriptionId, request.ServiceName, request.DeploymentName)
260- checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")
261+ checkRequest(c, httpRequest, expectedURL, "2013-10-01", nil, "GET")
262 }
263
264 func (suite *managementBaseAPISuite) TestGetDeployment(c *C) {
265@@ -838,7 +838,7 @@
266 expectedURL := defaultManagement + api.session.subscriptionId + "/services/storageservices"
267 expectedPayload, err := marshalXML(cssi)
268 c.Assert(err, IsNil)
269- checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")
270+ checkOneRequest(c, &recordedRequests, expectedURL, "2013-10-01", expectedPayload, "POST")
271 }
272
273 func (suite *managementBaseAPISuite) TestDeleteStorageAccount(c *C) {
274@@ -967,11 +967,11 @@
275 err := api.StartRole(request)
276 c.Assert(err, IsNil)
277 expectedURL := (defaultManagement + api.session.subscriptionId + "/services/hostedservices/" +
278- request.ServiceName + "/deployments/" + request.DeploymentName + "/roleinstances/" +
279- request.RoleName + "/Operations")
280+ request.ServiceName + "/deployments/" + request.DeploymentName + "/roleinstances/" +
281+ request.RoleName + "/Operations")
282 expectedPayload, err := marshalXML(startRoleOperation)
283 c.Assert(err, IsNil)
284- checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")
285+ checkOneRequest(c, recordedRequests, expectedURL, "2013-10-01", expectedPayload, "POST")
286 }
287
288 func (suite *managementBaseAPISuite) TestRestartRole(c *C) {
289@@ -981,11 +981,11 @@
290 err := api.RestartRole(request)
291 c.Assert(err, IsNil)
292 expectedURL := (defaultManagement + api.session.subscriptionId + "/services/hostedservices/" +
293- request.ServiceName + "/deployments/" + request.DeploymentName + "/roleinstances/" +
294- request.RoleName + "/Operations")
295+ request.ServiceName + "/deployments/" + request.DeploymentName + "/roleinstances/" +
296+ request.RoleName + "/Operations")
297 expectedPayload, err := marshalXML(restartRoleOperation)
298 c.Assert(err, IsNil)
299- checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")
300+ checkOneRequest(c, recordedRequests, expectedURL, "2013-10-01", expectedPayload, "POST")
301 }
302
303 func assertShutdownRoleRequest(c *C, api *ManagementAPI, request *ShutdownRoleRequest, httpRequest *X509Request) {
304@@ -995,7 +995,7 @@
305 request.DeploymentName, request.RoleName)
306 expectedPayload, err := marshalXML(shutdownRoleOperation)
307 c.Assert(err, IsNil)
308- checkRequest(c, httpRequest, expectedURL, "2012-03-01", expectedPayload, "POST")
309+ checkRequest(c, httpRequest, expectedURL, "2013-10-01", expectedPayload, "POST")
310 }
311
312 func (suite *managementBaseAPISuite) TestShutdownRole(c *C) {
313@@ -1010,9 +1010,9 @@
314
315 func assertGetRoleRequest(c *C, api *ManagementAPI, httpRequest *X509Request, serviceName, deploymentName, roleName string) {
316 expectedURL := (defaultManagement + api.session.subscriptionId +
317- "/services/hostedservices/" +
318- serviceName + "/deployments/" + deploymentName + "/roles/" + roleName)
319- checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")
320+ "/services/hostedservices/" +
321+ serviceName + "/deployments/" + deploymentName + "/roles/" + roleName)
322+ checkRequest(c, httpRequest, expectedURL, "2013-10-01", nil, "GET")
323 }
324
325 func (suite *managementBaseAPISuite) TestGetRole(c *C) {
326@@ -1038,10 +1038,10 @@
327
328 func assertUpdateRoleRequest(c *C, api *ManagementAPI, httpRequest *X509Request, serviceName, deploymentName, roleName, expectedXML string) {
329 expectedURL := (defaultManagement + api.session.subscriptionId +
330- "/services/hostedservices/" +
331- serviceName + "/deployments/" + deploymentName + "/roles/" + roleName)
332+ "/services/hostedservices/" +
333+ serviceName + "/deployments/" + deploymentName + "/roles/" + roleName)
334 checkRequest(
335- c, httpRequest, expectedURL, "2012-03-01", []byte(expectedXML), "PUT")
336+ c, httpRequest, expectedURL, "2013-10-01", []byte(expectedXML), "PUT")
337 c.Assert(httpRequest.ContentType, Equals, "application/xml")
338 }
339
340@@ -1126,7 +1126,7 @@
341
342 expectedURL := defaultManagement + api.session.subscriptionId + "/affinitygroups"
343 expectedBody, _ := cag.Serialize()
344- checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", []byte(expectedBody), "POST")
345+ checkOneRequest(c, &recordedRequests, expectedURL, "2013-10-01", []byte(expectedBody), "POST")
346 }
347
348 func (suite *managementBaseAPISuite) TestUpdateAffinityGroup(c *C) {
349@@ -1145,7 +1145,7 @@
350 c.Assert(err, IsNil)
351
352 expectedURL := (defaultManagement + api.session.subscriptionId +
353- "/affinitygroups/" + request.Name)
354+ "/affinitygroups/" + request.Name)
355 expectedBody, _ := uag.Serialize()
356 checkOneRequest(c, &recordedRequests, expectedURL, "2011-02-25", []byte(expectedBody), "PUT")
357 }
358@@ -1164,7 +1164,7 @@
359 c.Assert(err, IsNil)
360
361 expectedURL := (defaultManagement + api.session.subscriptionId +
362- "/affinitygroups/" + request.Name)
363+ "/affinitygroups/" + request.Name)
364 checkOneRequest(c, &recordedRequests, expectedURL, "2011-02-25", nil, "DELETE")
365 }
366
367@@ -1232,7 +1232,7 @@
368 expectedURL := fmt.Sprintf(
369 "%s%s/services/networking/media", defaultManagement,
370 api.session.subscriptionId)
371- checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")
372+ checkRequest(c, httpRequest, expectedURL, "2013-10-01", nil, "GET")
373 }
374
375 func (suite *managementBaseAPISuite) TestGetNetworkConfiguration(c *C) {
376@@ -1271,7 +1271,7 @@
377 expectedURL := fmt.Sprintf(
378 "%s%s/services/networking/media", defaultManagement,
379 api.session.subscriptionId)
380- checkRequest(c, httpRequest, expectedURL, "2012-03-01", body, "PUT")
381+ checkRequest(c, httpRequest, expectedURL, "2013-10-01", body, "PUT")
382 // Azure chokes when the content type is text/xml or similar.
383 c.Assert(httpRequest.ContentType, Equals, "application/octet-stream")
384 }
385
386=== modified file 'xmlobjects.go'
387--- xmlobjects.go 2013-07-25 22:02:41 +0000
388+++ xmlobjects.go 2013-10-31 04:14:10 +0000
389@@ -89,7 +89,7 @@
390 Hostname: Hostname,
391 Username: Username,
392 Password: Password,
393- CustomData: CustomData,
394+ CustomData: base64.StdEncoding.EncodeToString([]byte(CustomData)),
395 DisableSSHPasswordAuthentication: DisableSSHPasswordAuthentication,
396 }
397 }
398
399=== modified file 'xmlobjects_test.go'
400--- xmlobjects_test.go 2013-07-25 22:02:41 +0000
401+++ xmlobjects_test.go 2013-10-31 04:14:10 +0000
402@@ -1322,7 +1322,7 @@
403 c.Check(config.Hostname, Equals, hostname)
404 c.Check(config.Username, Equals, username)
405 c.Check(config.Password, Equals, password)
406- c.Check(config.CustomData, Equals, customdata)
407+ c.Check(config.CustomData, Equals, base64.StdEncoding.EncodeToString([]byte(customdata)))
408 c.Check(config.DisableSSHPasswordAuthentication, Equals, disablessh)
409 c.Check(config.ConfigurationSetType, Equals, "LinuxProvisioningConfiguration")
410 }

Subscribers

People subscribed via source and target branches

to all changes: