Merge lp:~allenap/gwacl/destroy-deployment into lp:gwacl
- destroy-deployment
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | 172 |
Merged at revision: | 158 |
Proposed branch: | lp:~allenap/gwacl/destroy-deployment |
Merge into: | lp:gwacl |
Diff against target: |
400 lines (+304/-15) 3 files modified
management.go (+62/-0) management_base_test.go (+38/-15) management_test.go (+204/-0) |
To merge this branch: | bzr merge lp:~allenap/gwacl/destroy-deployment |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Julian Edwards (community) | Approve | ||
Review via email: mp+172625@code.launchpad.net |
Commit message
New utility method, DestroyDeployment, to delete a deployment and all of its assets.
Description of the change
Julian Edwards (julian-edwards) wrote : | # |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Sigh, something has completely mangled the quoted code.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://
iEYEARECAAYFAlH
gGAAnjEnYMyNfX4
=A/Ao
-----END PGP SIGNATURE-----
Raphaël Badin (rvb) wrote : | # |
[0]
35 + // 1. Stop VMs.
36 + for _, roleInstance := range deployment.
I was wondering if it's really required to stop the VM…? because we know for a fact that it fails almost all the time and it takes ~15 minutes.
[1]
> Perhaps at least make it re-entrant such that repeated calls cope with
> partial removals from previous calls? In other words, does something
> like ShutdownRole return an error if it's already shut down?
Very good point, I think this means we should ignore 404 errors when trying to delete stuff.
- 169. By Gavin Panella
-
Switch from 404 to 500 in test.
- 170. By Gavin Panella
-
Succeed when deployment is not found.
- 171. By Gavin Panella
-
Remove duplication.
- 172. By Gavin Panella
-
Succeed when deployment assets are not found.
Gavin Panella (allenap) wrote : | # |
> [0]
>
> 35 + // 1. Stop VMs.
> 36 + for _, roleInstance := range deployment.
>
> I was wondering if it's really required to stop the VM…? because we know for a
> fact that it fails almost all the time and it takes ~15 minutes.
I'm going to leave this in for now. One of us can remove it later if
it's really not necessary.
>
> [1]
>
> > Perhaps at least make it re-entrant such that repeated calls cope with
> > partial removals from previous calls? In other words, does something
> > like ShutdownRole return an error if it's already shut down?
>
> Very good point, I think this means we should ignore 404 errors when trying to
> delete stuff.
Done.
Gavin Panella (allenap) wrote : | # |
> Since this is identical to GetDeploymentRe
> have a base DeploymentRequest and then embed it in the other structs,
> like this:
>
> type DeploymentRequest struct {
> ServiceName string
> DeploymentName string
> }
>
> type DestroyDeployme
> DeploymentRequest
> }
>
> type GetDeploymentRe
> DeploymentRequest
> }
>
> I think the Role stuff could do this as well, e.g.:
>
> type ShutdownRoleRequest {
> DeploymentRequest
> RoleName string
> }
>
> Having said that, it might obfuscate the parameters and confuse
> callers. Ho hum.
This would be very unweildy because you have to initialise like so:
request := &ShutdownRoleRe
},
RoleName: "...",
}
From what I've learned so far, struct embedding is just a bit of
misleading sugar, but probably there are some use cases out there
somewhere.
Where they are the same, we can do an alias-like thing:
type GetDeploymentRe
Let's do a sweep at the end for this stuff, and live with a small
amount of duplication for now.
> I rebelled against you the other day and used backslashes instead of
> the []s - I think they obfuscate the string too much.
>
> c.Check(err, ErrorMatches, `GET request failed \(404: Not Found\)`)
>
> is clearer IMO.
:) We shall have to agree to differ. My in-brain regex parser has
evolved different precedence rules.
Thanks for the review!
Julian Edwards (julian-edwards) wrote : | # |
On Wednesday 03 Jul 2013 09:53:28 you wrote:
> This would be very unweildy because you have to initialise like so:
>
> request := &ShutdownRoleRe
> DeploymentRequest: DeploymentRequest{
> ServiceName: "...",
> DeploymentName: "...",
> },
> RoleName: "...",
> }
>
Oooo wtf? Really? I thought embedding just saves the typing!
> >From what I've learned so far, struct embedding is just a bit of
>
> misleading sugar, but probably there are some use cases out there
> somewhere.
>
> Where they are the same, we can do an alias-like thing:
>
> type GetDeploymentRe
That works equally well for now, but makes it a little harder to add more
params later.
Having said all that I'm now tending towards just duplicating stuff.
>
> Let's do a sweep at the end for this stuff, and live with a small
> amount of duplication for now.
Yeah, agreed.
>
> > I rebelled against you the other day and used backslashes instead of
> > the []s - I think they obfuscate the string too much.
> >
> > c.Check(err, ErrorMatches, `GET request failed \(404: Not Found\)`)
> >
> > is clearer IMO.
> :
> :) We shall have to agree to differ. My in-brain regex parser has
>
> evolved different precedence rules.
See, there's your problem, your brain can parse regex!
> Thanks for the review!
A pleasure.
Raphaël Badin (rvb) wrote : | # |
>> [0]
>>
>> 35 + // 1. Stop VMs.
>> 36 + for _, roleInstance := range deployment.
>>
>> I was wondering if it's really required to stop the VM…? because we know for a
>> fact that it fails almost all the time and it takes ~15 minutes.
> I'm going to leave this in for now. One of us can remove it later if
> it's really not necessary.
I've tested this a bit and stopping the instances always fails with a 500 error for me. I strongly suggest testing if *removing* the VMs instead of stopping them helps.
Raphaël Badin (rvb) wrote : | # |
Also, we should update example/
Raphaël Badin (rvb) wrote : | # |
> I've tested this a bit and stopping the instances always fails with a 500 error for me.
What I meant here is that the stopping operation effectively works (i.e. the instance is stopped) but returns an error. Since AFAIK we don't have a method to remove VMs, maybe we could simply ignore the error from the stopping operation. If something really wrong happens and the machine cannot be stopped, we won't be able to remove the disk so the next call to the API from DestroyDeployment() will fail anyway.
Preview Diff
1 | === modified file 'management.go' |
2 | --- management.go 2013-06-28 16:41:40 +0000 |
3 | +++ management.go 2013-07-03 09:38:29 +0000 |
4 | @@ -3,6 +3,10 @@ |
5 | |
6 | package gwacl |
7 | |
8 | +import ( |
9 | + "sort" |
10 | +) |
11 | + |
12 | type ListInstancesRequest struct { |
13 | ServiceName string |
14 | } |
15 | @@ -66,3 +70,61 @@ |
16 | } |
17 | return deployments, nil |
18 | } |
19 | + |
20 | +type DestroyDeploymentRequest struct { |
21 | + ServiceName string |
22 | + DeploymentName string |
23 | +} |
24 | + |
25 | +// DestroyDeployment brings down all resources within a deployment - running |
26 | +// instances, disks, etc. - then deletes the deployment itself. |
27 | +func (api *ManagementAPI) DestroyDeployment(request *DestroyDeploymentRequest) error { |
28 | + deployment, err := api.GetDeployment(&GetDeploymentRequest{ |
29 | + ServiceName: request.ServiceName, |
30 | + DeploymentName: request.DeploymentName, |
31 | + }) |
32 | + if err != nil { |
33 | + if IsNotFoundError(err) { |
34 | + return nil |
35 | + } |
36 | + return err |
37 | + } |
38 | + // 1. Stop VMs. |
39 | + for _, roleInstance := range deployment.RoleInstanceList { |
40 | + err = api.ShutdownRole(&ShutdownRoleRequest{ |
41 | + ServiceName: request.ServiceName, |
42 | + DeploymentName: request.DeploymentName, |
43 | + RoleName: roleInstance.RoleName, |
44 | + }) |
45 | + if err != nil && !IsNotFoundError(err) { |
46 | + return err |
47 | + } |
48 | + } |
49 | + // 2. Delete VM disks. |
50 | + diskNameMap := make(map[string]bool) |
51 | + for _, role := range deployment.RoleList { |
52 | + for _, osVHD := range role.OSVirtualHardDisk { |
53 | + diskNameMap[osVHD.DiskName] = true |
54 | + } |
55 | + } |
56 | + // Sort the disk names to aid testing. |
57 | + diskNames := []string{} |
58 | + for diskName := range diskNameMap { |
59 | + diskNames = append(diskNames, diskName) |
60 | + } |
61 | + sort.Strings(diskNames) |
62 | + // Actually delete them. |
63 | + for _, diskName := range diskNames { |
64 | + err = api.DeleteDisk(diskName) |
65 | + if err != nil && !IsNotFoundError(err) { |
66 | + return err |
67 | + } |
68 | + } |
69 | + // 3. Delete deployment. |
70 | + err = api.DeleteDeployment(request.ServiceName, request.DeploymentName) |
71 | + if err != nil && !IsNotFoundError(err) { |
72 | + return err |
73 | + } |
74 | + // Done. |
75 | + return nil |
76 | +} |
77 | |
78 | === modified file 'management_base_test.go' |
79 | --- management_base_test.go 2013-07-03 00:17:53 +0000 |
80 | +++ management_base_test.go 2013-07-03 09:38:29 +0000 |
81 | @@ -508,16 +508,22 @@ |
82 | checkOneRequest(c, recordedRequests, expectedURL, expectedPayload, "POST") |
83 | } |
84 | |
85 | +func assertDeleteDeploymentRequest(c *C, api *ManagementAPI, hostedServiceName, deploymentName string, httpRequest *X509Request) { |
86 | + expectedURL := fmt.Sprintf( |
87 | + "%s%s/services/hostedservices/%s/deployments/%s", AZURE_URL, |
88 | + api.session.subscriptionId, hostedServiceName, deploymentName) |
89 | + checkRequest(c, httpRequest, expectedURL, nil, "DELETE") |
90 | +} |
91 | + |
92 | func (suite *managementBaseAPISuite) TestDeleteDeployment(c *C) { |
93 | api := makeAPI(c) |
94 | recordedRequests := setUpDispatcher("operationID") |
95 | hostedServiceName := "testHosterServiceName" |
96 | deploymentName := "testDeploymentName" |
97 | err := api.DeleteDeployment(hostedServiceName, deploymentName) |
98 | - |
99 | c.Assert(err, IsNil) |
100 | - expectedURL := AZURE_URL + api.session.subscriptionId + "/services/hostedservices/" + hostedServiceName + "/deployments/" + deploymentName |
101 | - checkOneRequest(c, recordedRequests, expectedURL, []byte{}, "DELETE") |
102 | + c.Assert(*recordedRequests, HasLen, 1) |
103 | + assertDeleteDeploymentRequest(c, api, hostedServiceName, deploymentName, (*recordedRequests)[0]) |
104 | } |
105 | |
106 | var getDeploymentResponse = ` |
107 | @@ -595,6 +601,13 @@ |
108 | </Deployment> |
109 | ` |
110 | |
111 | +func assertGetDeploymentRequest(c *C, api *ManagementAPI, request *GetDeploymentRequest, httpRequest *X509Request) { |
112 | + expectedURL := fmt.Sprintf( |
113 | + "%s%s/services/hostedservices/%s/deployments/%s", AZURE_URL, |
114 | + api.session.subscriptionId, request.ServiceName, request.DeploymentName) |
115 | + checkRequest(c, httpRequest, expectedURL, nil, "GET") |
116 | +} |
117 | + |
118 | func (suite *managementBaseAPISuite) TestGetDeployment(c *C) { |
119 | api := makeAPI(c) |
120 | fixedResponse := x509Response{ |
121 | @@ -611,10 +624,8 @@ |
122 | request := &GetDeploymentRequest{ServiceName: serviceName, DeploymentName: deploymentName} |
123 | deployment, err := api.GetDeployment(request) |
124 | c.Assert(err, IsNil) |
125 | - |
126 | - expectedURL := AZURE_URL + api.session.subscriptionId + "/services/hostedservices/" + serviceName + "/deployments/" + deploymentName |
127 | - checkOneRequest(c, &recordedRequests, expectedURL, []byte{}, "GET") |
128 | - |
129 | + c.Assert(recordedRequests, HasLen, 1) |
130 | + assertGetDeploymentRequest(c, api, request, recordedRequests[0]) |
131 | c.Check(deployment.Name, Equals, deploymentName) |
132 | } |
133 | |
134 | @@ -682,6 +693,12 @@ |
135 | c.Check(keys.URL, Equals, url) |
136 | } |
137 | |
138 | +func assertDeleteDiskRequest(c *C, api *ManagementAPI, diskName string, httpRequest *X509Request) { |
139 | + expectedURL := fmt.Sprintf("%s%s/services/disks/%s", AZURE_URL, |
140 | + api.session.subscriptionId, diskName) |
141 | + checkRequest(c, httpRequest, expectedURL, nil, "DELETE") |
142 | +} |
143 | + |
144 | func (suite *managementBaseAPISuite) TestDeleteDisk(c *C) { |
145 | // The current implementation of DeleteDisk() works around a bug in |
146 | // Windows Azure by polling the server. See the documentation in the file |
147 | @@ -694,8 +711,8 @@ |
148 | err := api.DeleteDisk(diskName) |
149 | |
150 | c.Assert(err, IsNil) |
151 | - expectedURL := api.session.composeURL("services/disks/" + diskName) |
152 | - checkOneRequest(c, recordedRequests, expectedURL, []byte{}, "DELETE") |
153 | + c.Assert(*recordedRequests, HasLen, 1) |
154 | + assertDeleteDiskRequest(c, api, diskName, (*recordedRequests)[0]) |
155 | } |
156 | |
157 | func (suite *managementBaseAPISuite) TestPerformNodeOperation(c *C) { |
158 | @@ -742,18 +759,24 @@ |
159 | checkOneRequest(c, recordedRequests, expectedURL, expectedPayload, "POST") |
160 | } |
161 | |
162 | +func assertShutdownRoleRequest(c *C, api *ManagementAPI, request *ShutdownRoleRequest, httpRequest *X509Request) { |
163 | + expectedURL := fmt.Sprintf( |
164 | + "%s%s/services/hostedservices/%s/deployments/%s/roleinstances/%s/Operations", |
165 | + AZURE_URL, api.session.subscriptionId, request.ServiceName, |
166 | + request.DeploymentName, request.RoleName) |
167 | + expectedPayload, err := marshalXML(shutdownRoleOperation) |
168 | + c.Assert(err, IsNil) |
169 | + checkRequest(c, httpRequest, expectedURL, expectedPayload, "POST") |
170 | +} |
171 | + |
172 | func (suite *managementBaseAPISuite) TestShutdownRole(c *C) { |
173 | api := makeAPI(c) |
174 | recordedRequests := setUpDispatcher("operationID") |
175 | request := &ShutdownRoleRequest{"serviceName", "deploymentName", "roleName"} |
176 | err := api.ShutdownRole(request) |
177 | c.Assert(err, IsNil) |
178 | - expectedURL := (AZURE_URL + api.session.subscriptionId + "/services/hostedservices/" + |
179 | - request.ServiceName + "/deployments/" + request.DeploymentName + "/roleinstances/" + |
180 | - request.RoleName + "/Operations") |
181 | - expectedPayload, err := marshalXML(shutdownRoleOperation) |
182 | - c.Assert(err, IsNil) |
183 | - checkOneRequest(c, recordedRequests, expectedURL, expectedPayload, "POST") |
184 | + c.Assert(*recordedRequests, HasLen, 1) |
185 | + assertShutdownRoleRequest(c, api, request, (*recordedRequests)[0]) |
186 | } |
187 | |
188 | func (suite *managementBaseAPISuite) TestGetRole(c *C) { |
189 | |
190 | === modified file 'management_test.go' |
191 | --- management_test.go 2013-06-28 18:09:28 +0000 |
192 | +++ management_test.go 2013-07-03 09:38:29 +0000 |
193 | @@ -162,3 +162,207 @@ |
194 | // No deployments are returned. |
195 | c.Check(len(deployments), Equals, 0) |
196 | } |
197 | + |
198 | +type suiteDestroyDeployment struct{} |
199 | + |
200 | +var _ = Suite(&suiteDestroyDeployment{}) |
201 | + |
202 | +var exampleDeployment = &Deployment{ |
203 | + RoleInstanceList: makeNamedRoleInstances("one", "two"), |
204 | + RoleList: []Role{ |
205 | + {OSVirtualHardDisk: []OSVirtualHardDisk{ |
206 | + {DiskName: "disk1"}, {DiskName: "disk2"}}}, |
207 | + {OSVirtualHardDisk: []OSVirtualHardDisk{ |
208 | + {DiskName: "disk1"}, {DiskName: "disk3"}}}, |
209 | + }, |
210 | +} |
211 | + |
212 | +var exampleOkayResponse = DispatcherResponse{ |
213 | + response: &x509Response{StatusCode: http.StatusOK}, |
214 | +} |
215 | + |
216 | +var exampleFailResponse = DispatcherResponse{ |
217 | + response: &x509Response{StatusCode: http.StatusInternalServerError}, |
218 | +} |
219 | + |
220 | +var exampleNotFoundResponse = DispatcherResponse{ |
221 | + response: &x509Response{StatusCode: http.StatusNotFound}, |
222 | +} |
223 | + |
224 | +func (suite *suiteDestroyDeployment) TestHappyPath(c *C) { |
225 | + var responses []DispatcherResponse |
226 | + // Prepare. |
227 | + responses = append(responses, makeOKXMLResponse(c, exampleDeployment)...) |
228 | + // For shutting down VMs. |
229 | + responses = append(responses, exampleOkayResponse, exampleOkayResponse) |
230 | + // For deleting disks. |
231 | + responses = append(responses, exampleOkayResponse, exampleOkayResponse, exampleOkayResponse) |
232 | + // For other requests. |
233 | + responses = append(responses, exampleOkayResponse) |
234 | + record := []*X509Request{} |
235 | + rigRecordingPreparedResponseDispatcher(&record, responses) |
236 | + // Exercise DestroyDeployment. |
237 | + api := makeAPI(c) |
238 | + request := &DestroyDeploymentRequest{ |
239 | + ServiceName: "service-name", |
240 | + DeploymentName: "deployment-name", |
241 | + } |
242 | + err := api.DestroyDeployment(request) |
243 | + c.Assert(err, IsNil) |
244 | + c.Check(record, HasLen, 7) |
245 | + // The first request is for the deployment. |
246 | + assertGetDeploymentRequest(c, api, &GetDeploymentRequest{ |
247 | + request.ServiceName, request.DeploymentName}, record[0]) |
248 | + // The second is to shutdown RoleInstance "one". |
249 | + assertShutdownRoleRequest(c, api, &ShutdownRoleRequest{ |
250 | + request.ServiceName, request.DeploymentName, "one"}, record[1]) |
251 | + // The third is to shutdown RoleInstance "two", similar to like the second. |
252 | + assertShutdownRoleRequest(c, api, &ShutdownRoleRequest{ |
253 | + request.ServiceName, request.DeploymentName, "two"}, record[2]) |
254 | + // The fourth, fifth and sixth requests delete disks. |
255 | + assertDeleteDiskRequest(c, api, "disk1", record[3]) |
256 | + assertDeleteDiskRequest(c, api, "disk2", record[4]) |
257 | + assertDeleteDiskRequest(c, api, "disk3", record[5]) |
258 | + // The fourth request deletes the deployment. |
259 | + assertDeleteDeploymentRequest(c, api, request.ServiceName, |
260 | + request.DeploymentName, record[6]) |
261 | +} |
262 | + |
263 | +func (suite *suiteDestroyDeployment) TestOkayWhenDeploymentNotFound(c *C) { |
264 | + var responses []DispatcherResponse |
265 | + // Prepare. |
266 | + responses = []DispatcherResponse{exampleNotFoundResponse} |
267 | + record := []*X509Request{} |
268 | + rigRecordingPreparedResponseDispatcher(&record, responses) |
269 | + // Exercise DestroyDeployment. |
270 | + api := makeAPI(c) |
271 | + request := &DestroyDeploymentRequest{ |
272 | + ServiceName: "service-name", |
273 | + DeploymentName: "deployment-name", |
274 | + } |
275 | + err := api.DestroyDeployment(request) |
276 | + c.Assert(err, IsNil) |
277 | + c.Check(record, HasLen, 1) |
278 | +} |
279 | + |
280 | +func (suite *suiteDestroyDeployment) TestOkayWhenAssetsNotFound(c *C) { |
281 | + var responses []DispatcherResponse |
282 | + // Prepare. |
283 | + responses = append(responses, makeOKXMLResponse(c, exampleDeployment)...) |
284 | + // For shutting down VMs. |
285 | + responses = append(responses, exampleNotFoundResponse, exampleNotFoundResponse) |
286 | + // For deleting disks. |
287 | + responses = append(responses, exampleNotFoundResponse, exampleNotFoundResponse, exampleNotFoundResponse) |
288 | + // For other requests. |
289 | + responses = append(responses, exampleNotFoundResponse) |
290 | + record := []*X509Request{} |
291 | + rigRecordingPreparedResponseDispatcher(&record, responses) |
292 | + // Exercise DestroyDeployment. |
293 | + api := makeAPI(c) |
294 | + request := &DestroyDeploymentRequest{ |
295 | + ServiceName: "service-name", |
296 | + DeploymentName: "deployment-name", |
297 | + } |
298 | + err := api.DestroyDeployment(request) |
299 | + c.Assert(err, IsNil) |
300 | + c.Check(record, HasLen, 7) |
301 | + // The first request is for the deployment. |
302 | + assertGetDeploymentRequest(c, api, &GetDeploymentRequest{ |
303 | + request.ServiceName, request.DeploymentName}, record[0]) |
304 | + // The second is to shutdown RoleInstance "one". |
305 | + assertShutdownRoleRequest(c, api, &ShutdownRoleRequest{ |
306 | + request.ServiceName, request.DeploymentName, "one"}, record[1]) |
307 | + // The third is to shutdown RoleInstance "two", similar to like the second. |
308 | + assertShutdownRoleRequest(c, api, &ShutdownRoleRequest{ |
309 | + request.ServiceName, request.DeploymentName, "two"}, record[2]) |
310 | + // The fourth, fifth and sixth requests delete disks. |
311 | + assertDeleteDiskRequest(c, api, "disk1", record[3]) |
312 | + assertDeleteDiskRequest(c, api, "disk2", record[4]) |
313 | + assertDeleteDiskRequest(c, api, "disk3", record[5]) |
314 | + // The fourth request deletes the deployment. |
315 | + assertDeleteDeploymentRequest(c, api, request.ServiceName, |
316 | + request.DeploymentName, record[6]) |
317 | +} |
318 | + |
319 | +func (suite *suiteDestroyDeployment) TestFailsGettingDeployment(c *C) { |
320 | + var responses []DispatcherResponse |
321 | + // Prepare. |
322 | + responses = []DispatcherResponse{exampleFailResponse} |
323 | + record := []*X509Request{} |
324 | + rigRecordingPreparedResponseDispatcher(&record, responses) |
325 | + // Exercise DestroyDeployment. |
326 | + api := makeAPI(c) |
327 | + request := &DestroyDeploymentRequest{ |
328 | + ServiceName: "service-name", |
329 | + DeploymentName: "deployment-name", |
330 | + } |
331 | + err := api.DestroyDeployment(request) |
332 | + c.Assert(err, NotNil) |
333 | + c.Check(err, ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") |
334 | + c.Check(record, HasLen, 1) |
335 | +} |
336 | + |
337 | +func (suite *suiteDestroyDeployment) TestFailsShuttingDownRole(c *C) { |
338 | + var responses []DispatcherResponse |
339 | + // Prepare. |
340 | + responses = append(responses, makeOKXMLResponse(c, exampleDeployment)...) |
341 | + responses = append(responses, exampleFailResponse) |
342 | + record := []*X509Request{} |
343 | + rigRecordingPreparedResponseDispatcher(&record, responses) |
344 | + // Exercise DestroyDeployment. |
345 | + api := makeAPI(c) |
346 | + request := &DestroyDeploymentRequest{ |
347 | + ServiceName: "service-name", |
348 | + DeploymentName: "deployment-name", |
349 | + } |
350 | + err := api.DestroyDeployment(request) |
351 | + c.Assert(err, NotNil) |
352 | + c.Check(err, ErrorMatches, "POST request failed [(]500: Internal Server Error[)]") |
353 | + c.Check(record, HasLen, 2) |
354 | +} |
355 | + |
356 | +func (suite *suiteDestroyDeployment) TestFailsDeletingDisk(c *C) { |
357 | + var responses []DispatcherResponse |
358 | + // Prepare. |
359 | + responses = append(responses, makeOKXMLResponse(c, exampleDeployment)...) |
360 | + // For shutting down VMs. |
361 | + responses = append(responses, exampleOkayResponse, exampleOkayResponse) |
362 | + // For deleting disks. |
363 | + responses = append(responses, exampleFailResponse) |
364 | + record := []*X509Request{} |
365 | + rigRecordingPreparedResponseDispatcher(&record, responses) |
366 | + // Exercise DestroyDeployment. |
367 | + api := makeAPI(c) |
368 | + request := &DestroyDeploymentRequest{ |
369 | + ServiceName: "service-name", |
370 | + DeploymentName: "deployment-name", |
371 | + } |
372 | + err := api.DestroyDeployment(request) |
373 | + c.Assert(err, NotNil) |
374 | + c.Check(err, ErrorMatches, "DELETE request failed [(]500: Internal Server Error[)]") |
375 | + c.Check(record, HasLen, 4) |
376 | +} |
377 | + |
378 | +func (suite *suiteDestroyDeployment) TestFailsDeletingDeployment(c *C) { |
379 | + var responses []DispatcherResponse |
380 | + // Prepare. |
381 | + responses = append(responses, makeOKXMLResponse(c, exampleDeployment)...) |
382 | + // For shutting down VMs. |
383 | + responses = append(responses, exampleOkayResponse, exampleOkayResponse) |
384 | + // For deleting disks. |
385 | + responses = append(responses, exampleOkayResponse, exampleOkayResponse, exampleOkayResponse) |
386 | + // For other requests. |
387 | + responses = append(responses, exampleFailResponse) |
388 | + record := []*X509Request{} |
389 | + rigRecordingPreparedResponseDispatcher(&record, responses) |
390 | + // Exercise DestroyDeployment. |
391 | + api := makeAPI(c) |
392 | + request := &DestroyDeploymentRequest{ |
393 | + ServiceName: "service-name", |
394 | + DeploymentName: "deployment-name", |
395 | + } |
396 | + err := api.DestroyDeployment(request) |
397 | + c.Assert(err, NotNil) |
398 | + c.Check(err, ErrorMatches, "DELETE request failed [(]500: Internal Server Error[)]") |
399 | + c.Check(record, HasLen, 7) |
400 | +} |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
review: approve
Thanks, nice change. Looks fine to me, just some minor comments you
can ignore:
> === modified file 'management.go' --- management.go 2013-06-28 quest struct { ntRequest struct { + ServiceName
> 16:41:40 +0000 +++ management.go 2013-07-02 16:49:33 +0000 @@ -3,6
> +3,10 @@
>
> package gwacl
>
> +import ( + "sort" +) + type ListInstancesRe
> ServiceName string } @@ -66,3 +70,58 @@ } return deployments, nil
> } + +type DestroyDeployme
> string + DeploymentName string +}
Since this is identical to GetDeploymentRe quest I wonder if we can
have a base DeploymentRequest and then embed it in the other structs,
like this:
type DeploymentRequest struct {
ServiceName string
DeploymentName string
}
type DestroyDeployme ntRequest struct { Request
Deployment
}
type GetDeploymentRe quest { equest
DeploymentR
}
I think the Role stuff could do this as well, e.g.:
type ShutdownRoleRequest { equest
DeploymentR
RoleName string
}
Having said that, it might obfuscate the parameters and confuse
callers. Ho hum.
> + +// DestroyDeployment brings down all resources within a nt(request *DestroyDeploym entRequest) error { + nt(&GetDeployme ntRequest{ + ServiceName, + DeploymentName: DeploymentName, + }) + if err != nil { + RoleInstanceLis t { + err = e(&ShutdownRole Request{ + ServiceName: ServiceName, + DeploymentName: DeploymentName, + RoleName: RoleName, + }) + if err != nil { + string] bool) + for _, role := range ardDisk { + diskNameMap[ osVHD.DiskName] = diskNames) + // Actually delete them. + for _, diskName) + if err != nil { + yment(request. ServiceName, DeploymentName) + if err != nil { + return err +
> deployment - running +// instances, disks, etc. - then deletes the
> deployment itself. +func (api *ManagementAPI)
> DestroyDeployme
> deployment, err := api.GetDeployme
> ServiceName: request.
> request.
> return err + } + // 1. Stop VMs. + for _, roleInstance :=
> range deployment.
> api.ShutdownRol
> request.
> request.
> roleInstance.
> return err + } + } + // 2. Delete VM disks. +
> diskNameMap := make(map[
> deployment.RoleList { + for _, osVHD := range
> role.OSVirtualH
> true + } + } + // Sort the disk names to aid testing.
> + diskNames := []string{} + for diskName := range diskNameMap
> { + diskNames = append(diskNames, diskName) + } +
> sort.Strings(
> diskName := range diskNames { + err =
> api.DeleteDisk(
> return err + } + } + // 3. Delete deployment. + err
> = api.DeleteDeplo
> request.
> } + // Done. + return nil +}
Given our experience with random failures leaving half of a deployment
behind I wonder if we can't make this better. But I can't really
think of anything to suggest :/
Perhaps at least make it re-entrant such that repeated calls cope with
partial removals from previous calls? In other words, does something
like ShutdownRole return an error if it's already shut down?
> base_test. go' --- base_test. go 2013-07-...
> === modified file 'management_
> management_