Merge lp:~rvb/juju-core/az-destroy into lp:~go-bot/juju-core/trunk
- az-destroy
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Raphaël Badin |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1412 |
Proposed branch: | lp:~rvb/juju-core/az-destroy |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
323 lines (+234/-16) 2 files modified
environs/azure/environ.go (+57/-10) environs/azure/environ_test.go (+177/-6) |
To merge this branch: | bzr merge lp:~rvb/juju-core/az-destroy |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+173657@code.launchpad.net |
Commit message
Azure provider: Destroy() and StopInstances().
Description of the change
Azure provider: Destroy() and StopInstances().
This branch implements environ's Destroy() and StopInstances() in the Azure provider. As always with the provider code, most of the non-obvious code is in the tests. Since the testing utilities provided by gwacl return a slice of the fake requests made, it's tempting to make an extensive usage of that information and basically duplicate the unit-tests for the called gwacl methods. Instead of doing that, I chose a middle ground and checked that the requests look right without going too deep; this should be sufficient to test that the provider methods have the expected behavior and get tests to fail if the gwacl methods change significantly.
Raphaël Badin (rvb) wrote : | # |
John A Meinel (jameinel) wrote : | # |
I don't think you have to change this, but there are a couple bits that
might make it easier to support.
'makeService' is a bit confusing because Juju has a 'service' concept.
So clarifying what you are creating here would be nice.
Just asserting that we call GET seems a bit weak, vs at least asserting
what *type* of thing you are GETing.
I do still prefer the double approach to the mocking-HTTP requests.
Mostly because when you do change gwacl to fix things, it can easily
break the juju-core test suite without the juju-core code itself
actually being incorrect. (eg you discover that you know enough that you
can just DELETE the services without needing to GET their details
first.)
If it isn't really possible to assert more about the types of URLs being
accessed, then LGTM.
https:/
File environs/
https:/
environs/
gwacl first issues a Get reques
typo: request
https:/
environs/
buildDestroySer
How does this match up with the 1 instance == 1 service model that JTV
has brought up might be changing?
Given that Juju has its own notion of what a service is, maybe the
helper could be "makeAzureService" to distinguish and avoid confusion?
https:/
environs/
Equals, "DELETE")
It feels like this could use slightly more assertion if we are going to
do it this way.
Specifically that we are GET and or DELETE a service (somehow the
"service1" is in the content, or something). To handle the "I wanted to
delete a service, but I actually just deleted an instance" or something
along those lines.
https:/
environs/
c.Check(
"blob-2"), IsTrue)
Like this one is actually asserting that you are DELETEing something
"blob-1" related. (Otherwise this test could have passed if you just
DELETE services.)
https:/
environs/
1+len(services)*2)
So I think what I'd like to see is not testing of the formatting of the
requests (that belongs in lp:gwacl), but slightly more sanity checking
than just GET. So something that shows it is a GET of all instances, and
then a GET + DELETE of a given service, etc.
Jeroen T. Vermeulen (jtv) wrote : | # |
LGTM, thoroughly tested, but as always a few notes still.
https:/
File environs/
https:/
environs/
'instances' is an empty slice or nil.
I'd prefer it if you didn't do this unless the comment could cite a very
good reason, such as breakage or a specific, unacceptable performance
overhead. Otherwise it's pointless cyclomatic complexity, which means
reduced test coverage.
https:/
environs/
are errors, return only the
Typo: "if" after semicolon, not "If"!
Also, "shutdown" is the noun and "shut down" is the verb. (Same in the
line below).
https:/
environs/
environment %q", env.name)
No need to prefix the message with "environs/azure." That's built into
the logger.
https:/
environs/
You're trying to delete all blobs and *then* stopping the instances.
Has Azure fixed the problems with deleting VHDs yet? Because otherwise
I imagine you'd just get errors about VHD files being still in use by
the VMs that have them mounted.
https:/
environs/
about but haven't yet shown
I think I recognize this code from the ec2/openstack providers... What
an ugly piece of code it is.
From a Python background, I would say chuck both sources of instances
into the same set, then pass that set as an iterable. Unfortunately in
Go, that comes out as:
combinedInstances := make(map[
for _, inst := range env.AllInstances() {
combinedInstan
}
for _, inst := range ensureInstances {
combinedInstan
}
instances := make([]string, 0)
for inst := range combinedInstances {
instances = append(instances, inst)
}
More regular perhaps, but still an insane amount of work for what it is.
https:/
environs/
Maybe just "return env.StopInstanc
mindless repetition of error propagation any more!
https:/
File environs/
https:/
environs/
By the way, there's a new convention for formatting our imports — but as
far as I can tell, no cleanup drive. So we're expected to fix this
ourselves as we go along, I guess.
The convention is: still one import statement, b...
Raphaël Badin (rvb) wrote : | # |
On 2013/07/09 09:05:34, jtv.canonical wrote:
> LGTM, thoroughly tested, but as always a few notes still.
Thanks for the review!
https:/
> File environs/
https:/
> environs/
'instances' is an
> empty slice or nil.
> I'd prefer it if you didn't do this unless the comment could cite a
very good
> reason, such as breakage or a specific, unacceptable performance
overhead.
> Otherwise it's pointless cyclomatic complexity, which means reduced
test
> coverage.
Fixed.
https:/
> environs/
are
> errors, return only the
> Typo: "if" after semicolon, not "If"!
> Also, "shutdown" is the noun and "shut down" is the verb. (Same in
the line
> below).
Fixed.
https:/
> environs/
destroying
> environment %q", env.name)
> No need to prefix the message with "environs/azure." That's built
into the
> logger.
Well spotted, fixed.
https:/
> environs/
> You're trying to delete all blobs and *then* stopping the instances.
Has Azure
> fixed the problems with deleting VHDs yet? Because otherwise I
imagine you'd
> just get errors about VHD files being still in use by the VMs that
have them
> mounted.
I think you're mixing up two different things here: the objects that are
in the storage (the tools, the state-info file, etc) and the VHD.
https:/
> environs/
about but
> haven't yet shown
> I think I recognize this code from the ec2/openstack providers...
What an ugly
> piece of code it is.
It is ugly indeed, this should not be part of the provider's code but
until this gets refactored, I'd prefer to keep that an exact copy of the
code the other providers use so that this can be easily refactored
later.
https:/
> environs/
> Maybe just "return env.StopInstanc
mindless
> repetition of error propagation any more!
Done.
https:/
> File environs/
https:/
> environs/
> By the way, there's a new convention for formatting our imports — but
as far as
> I can tell, no cleanup drive. So we're expected to fix this ourselves
as we go
> along, I guess.
> The convention ...
Jeroen T. Vermeulen (jtv) wrote : | # |
On 2013/07/09 10:12:41, rvb wrote:
https:/
> > environs/
service, gwacl
> > first issues a Get reques
> >
> > This makes it sound as if gwacl.DeleteHos
that, when
> > actually apart from the one mention of a GET request, you're
describing what
> the
> > provider code does. My first reaction was: "why isn't this
knowledge kept
> > inside gwacl then?"
> No, that's what DestroyHostedSe
In that case, it's painful to have this detailed knowledge of gwacl's
implementation embedded here instead of in gwacl. Can you think of a
useful way to move it?
> > I think you can test this at a higher level, with less hassle and
less code.
> > Look for setDummyStorage; that will set up your azureEnviron with a
fake
> > storage. Then you can test directly for things like "my file gets
deleted
> from
> > storage" without having to muck about with requests and responses.
Leave that
> > stuff to the storage tests.
> I don't think that's possible because:
> a) I need to test the destroying the environment actually deletes all
the files.
> b) The casting statement inside Destroy() prevents me from using
> setDummyStorage.
It's a shame about b) because a) is not actually an obstacle — it's
exactly the sort of thing I just described you could test more easily
this way!
This suggests to me that there's a DeleteAll method missing on
StorageWriter...
> I indented the XML strings better… I don't really see why I would use
dedent,
> XML does not really care about white spaces between tags.
I assumed you did it the ugly way for some good reason that was going to
become obvious. :-P
Raphaël Badin (rvb) wrote : | # |
Thanks for the review!
On 2013/07/09 08:00:44, jameinel wrote:
> I don't think you have to change this, but there are a couple bits
that might
> make it easier to support.
> 'makeService' is a bit confusing because Juju has a 'service' concept.
So
> clarifying what you are creating here would be nice.
Good point, I've renamed all the helpers (for instance
s/makeService/
> Just asserting that we call GET seems a bit weak, vs at least
asserting what
> *type* of thing you are GETing.
You're right, I've improved the tests to check that they target the
right objects.
> I do still prefer the double approach to the mocking-HTTP requests.
Mostly
> because when you do change gwacl to fix things, it can easily break
the
> juju-core test suite without the juju-core code itself actually being
incorrect.
> (eg you discover that you know enough that you can just DELETE the
services
> without needing to GET their details first.)
This is a bit late for this and we deliberately choose not to do a test
double in gwacl. This saves a lot of code in gwacl but the drawback is
that some of gwacl's internal leak into the tests of third party
applications, such as the juju provider, which use gwacl. Again, this
is a tradeoff and the main goal is to allow us to move quickly. It
maintenance is easier this way, once this is all stabilized (I'm talking
about gwacl here and how we map Juju services onto gwacl objects), it
might be good to move to a model where all the tests will be based on a
test double.
https:/
> environs/
service, gwacl
> first issues a Get reques
> typo: request
Fixed.
https:/
> environs/
buildDestroySer
> services)
> How does this match up with the 1 instance == 1 service model that JTV
has
> brought up might be changing?
This works under that assumption, you're right… I've added a comment in
the code (the actual code for StopInstances, not the test code).
> Given that Juju has its own notion of what a service is, maybe the
helper could
> be "makeAzureService" to distinguish and avoid confusion?
Done.
https:/
> environs/
Equals,
> "DELETE")
> It feels like this could use slightly more assertion if we are going
to do it
> this way.
> Specifically that we are GET and or DELETE a service (somehow the
"service1" is
> in the content, or something). To handle the "I wanted to delete a
service, but
> I actually just deleted an instance" or something along those lines.
https:/
> environs/
> c.Check(
> "blob-2"), IsTrue)
> Like this one is actually asserting that ...
Preview Diff
1 | === modified file 'environs/azure/environ.go' |
2 | --- environs/azure/environ.go 2013-07-08 08:34:47 +0000 |
3 | +++ environs/azure/environ.go 2013-07-09 13:50:30 +0000 |
4 | @@ -5,6 +5,8 @@ |
5 | |
6 | import ( |
7 | "fmt" |
8 | + "sync" |
9 | + |
10 | "launchpad.net/gwacl" |
11 | "launchpad.net/juju-core/constraints" |
12 | "launchpad.net/juju-core/environs" |
13 | @@ -12,7 +14,6 @@ |
14 | "launchpad.net/juju-core/instance" |
15 | "launchpad.net/juju-core/state" |
16 | "launchpad.net/juju-core/state/api" |
17 | - "sync" |
18 | ) |
19 | |
20 | type azureEnviron struct { |
21 | @@ -130,19 +131,32 @@ |
22 | } |
23 | |
24 | // StopInstances is specified in the Environ interface. |
25 | -func (env *azureEnviron) StopInstances([]instance.Instance) error { |
26 | - panic("unimplemented") |
27 | +func (env *azureEnviron) StopInstances(instances []instance.Instance) error { |
28 | + // Each Juju instance is an Azure Service (instance==service), destroy |
29 | + // all the Azure services. |
30 | + // Acquire management API object. |
31 | + context, err := env.getManagementAPI() |
32 | + if err != nil { |
33 | + return err |
34 | + } |
35 | + defer env.releaseManagementAPI(context) |
36 | + // Shut down all the instances; if there are errors, return only the |
37 | + // first one (but try to shut down all instances regardless). |
38 | + var firstErr error |
39 | + for _, instance := range instances { |
40 | + request := &gwacl.DestroyHostedServiceRequest{ServiceName: string(instance.Id())} |
41 | + err := context.DestroyHostedService(request) |
42 | + if err != nil && firstErr == nil { |
43 | + firstErr = err |
44 | + } |
45 | + } |
46 | + return firstErr |
47 | } |
48 | |
49 | // Instances is specified in the Environ interface. |
50 | func (env *azureEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { |
51 | // The instance list is built using the list of all the relevant |
52 | // Azure Services (instance==service). |
53 | - // If the list of ids is empty, return nil as specified by the |
54 | - // interface |
55 | - if len(ids) == 0 { |
56 | - return nil, environs.ErrNoInstances |
57 | - } |
58 | // Acquire management API object. |
59 | context, err := env.getManagementAPI() |
60 | if err != nil { |
61 | @@ -223,8 +237,41 @@ |
62 | } |
63 | |
64 | // Destroy is specified in the Environ interface. |
65 | -func (env *azureEnviron) Destroy(insts []instance.Instance) error { |
66 | - panic("unimplemented") |
67 | +func (env *azureEnviron) Destroy(ensureInsts []instance.Instance) error { |
68 | + logger.Debugf("destroying environment %q", env.name) |
69 | + |
70 | + // Delete storage. |
71 | + st := env.Storage().(*azureStorage) |
72 | + context, err := st.getStorageContext() |
73 | + if err != nil { |
74 | + return err |
75 | + } |
76 | + request := &gwacl.DeleteAllBlobsRequest{Container: st.getContainer()} |
77 | + err = context.DeleteAllBlobs(request) |
78 | + if err != nil { |
79 | + return fmt.Errorf("cannot clean up storage: %v", err) |
80 | + } |
81 | + |
82 | + // Stop all instances. |
83 | + insts, err := env.AllInstances() |
84 | + if err != nil { |
85 | + return fmt.Errorf("cannot get instances: %v", err) |
86 | + } |
87 | + found := make(map[instance.Id]bool) |
88 | + for _, inst := range insts { |
89 | + found[inst.Id()] = true |
90 | + } |
91 | + |
92 | + // Add any instances we've been told about but haven't yet shown |
93 | + // up in the instance list. |
94 | + for _, inst := range ensureInsts { |
95 | + id := inst.Id() |
96 | + if !found[id] { |
97 | + insts = append(insts, inst) |
98 | + found[id] = true |
99 | + } |
100 | + } |
101 | + return env.StopInstances(insts) |
102 | } |
103 | |
104 | // OpenPorts is specified in the Environ interface. |
105 | |
106 | === modified file 'environs/azure/environ_test.go' |
107 | --- environs/azure/environ_test.go 2013-07-08 08:34:47 +0000 |
108 | +++ environs/azure/environ_test.go 2013-07-09 13:50:30 +0000 |
109 | @@ -6,6 +6,11 @@ |
110 | import ( |
111 | "encoding/base64" |
112 | "fmt" |
113 | + "io/ioutil" |
114 | + "net/http" |
115 | + "strings" |
116 | + "sync" |
117 | + |
118 | . "launchpad.net/gocheck" |
119 | "launchpad.net/gwacl" |
120 | "launchpad.net/juju-core/environs" |
121 | @@ -15,9 +20,6 @@ |
122 | "launchpad.net/juju-core/instance" |
123 | "launchpad.net/juju-core/testing" |
124 | . "launchpad.net/juju-core/testing/checkers" |
125 | - "net/http" |
126 | - "strings" |
127 | - "sync" |
128 | ) |
129 | |
130 | type EnvironSuite struct { |
131 | @@ -112,7 +114,7 @@ |
132 | // The real test is that this does not panic. |
133 | } |
134 | |
135 | -func patchWithServiceListResponse(c *C, services []gwacl.HostedServiceDescriptor) *[]*gwacl.X509Request { |
136 | +func buildAzureServiceListResponse(c *C, services []gwacl.HostedServiceDescriptor) []gwacl.DispatcherResponse { |
137 | list := gwacl.HostedServiceDescriptorList{HostedServices: services} |
138 | listXML, err := list.Serialize() |
139 | c.Assert(err, IsNil) |
140 | @@ -121,8 +123,12 @@ |
141 | http.StatusOK, |
142 | nil, |
143 | )} |
144 | - requests := gwacl.PatchManagementAPIResponses(responses) |
145 | - return requests |
146 | + return responses |
147 | +} |
148 | + |
149 | +func patchWithServiceListResponse(c *C, services []gwacl.HostedServiceDescriptor) *[]*gwacl.X509Request { |
150 | + responses := buildAzureServiceListResponse(c, services) |
151 | + return gwacl.PatchManagementAPIResponses(responses) |
152 | } |
153 | |
154 | func (suite EnvironSuite) TestGetEnvPrefixContainsEnvName(c *C) { |
155 | @@ -339,3 +345,168 @@ |
156 | c.Check(stateInfo.Addrs, DeepEquals, []string{expectedDNSName + statePortSuffix}) |
157 | c.Check(apiInfo.Addrs, DeepEquals, []string{expectedDNSName + apiPortSuffix}) |
158 | } |
159 | + |
160 | +// buildDestroyAzureServiceResponses returns a slice containing the responses that a fake Azure server |
161 | +// can use to simulate the deletion of the given list of services. |
162 | +func buildDestroyAzureServiceResponses(c *C, services []*gwacl.HostedService) []gwacl.DispatcherResponse { |
163 | + responses := []gwacl.DispatcherResponse{} |
164 | + for _, service := range services { |
165 | + // When destroying a hosted service, gwacl first issues a Get request |
166 | + // to fetch the properties of the services. Then it destroys all the |
167 | + // deployments found in this service (none in this case, we make sure |
168 | + // the service does not contain deployments to keep the testing simple) |
169 | + // And it finally deletes the service itself. |
170 | + if len(service.Deployments) != 0 { |
171 | + panic("buildDestroyAzureServiceResponses does not support services with deployments!") |
172 | + } |
173 | + serviceXML, err := service.Serialize() |
174 | + c.Assert(err, IsNil) |
175 | + serviceGetResponse := gwacl.NewDispatcherResponse( |
176 | + []byte(serviceXML), |
177 | + http.StatusOK, |
178 | + nil, |
179 | + ) |
180 | + responses = append(responses, serviceGetResponse) |
181 | + serviceDeleteResponse := gwacl.NewDispatcherResponse( |
182 | + nil, |
183 | + http.StatusOK, |
184 | + nil, |
185 | + ) |
186 | + responses = append(responses, serviceDeleteResponse) |
187 | + } |
188 | + return responses |
189 | +} |
190 | + |
191 | +func makeAzureService(name string) (*gwacl.HostedService, *gwacl.HostedServiceDescriptor) { |
192 | + service1 := &gwacl.HostedService{ServiceName: name} |
193 | + service1Desc := &gwacl.HostedServiceDescriptor{ServiceName: name} |
194 | + return service1, service1Desc |
195 | +} |
196 | + |
197 | +func (EnvironSuite) TestStopInstancesDestroysMachines(c *C) { |
198 | + service1Name := "service1" |
199 | + service1, service1Desc := makeAzureService(service1Name) |
200 | + service2Name := "service2" |
201 | + service2, service2Desc := makeAzureService(service2Name) |
202 | + services := []*gwacl.HostedService{service1, service2} |
203 | + responses := buildDestroyAzureServiceResponses(c, services) |
204 | + requests := gwacl.PatchManagementAPIResponses(responses) |
205 | + env := makeEnviron(c) |
206 | + instances := convertToInstances([]gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc}) |
207 | + |
208 | + err := env.StopInstances(instances) |
209 | + c.Check(err, IsNil) |
210 | + |
211 | + // It takes 2 API calls to delete each service: |
212 | + // - one GET request to fetch the properties about the service; |
213 | + // - one DELETE request to delete the service. |
214 | + c.Check(len(*requests), Equals, len(services)*2) |
215 | + c.Check((*requests)[0].Method, Equals, "GET") |
216 | + c.Check((*requests)[1].Method, Equals, "DELETE") |
217 | + c.Check((*requests)[2].Method, Equals, "GET") |
218 | + c.Check((*requests)[3].Method, Equals, "DELETE") |
219 | +} |
220 | + |
221 | +// makeAzureStorageMocking creates a test azureStorage object that will talk to a |
222 | +// fake http server set up to always return the given http.Response object. |
223 | +func makeAzureStorageMocking(transport http.RoundTripper, container string, account string) azureStorage { |
224 | + client := &http.Client{Transport: transport} |
225 | + storageContext := gwacl.NewTestStorageContext(client) |
226 | + storageContext.Account = account |
227 | + context := &testStorageContext{container: container, storageContext: storageContext} |
228 | + azStorage := azureStorage{context} |
229 | + return azStorage |
230 | +} |
231 | + |
232 | +var blobListWith2BlobsResponse = ` |
233 | + <?xml version="1.0" encoding="utf-8"?> |
234 | + <EnumerationResults ContainerName="http://myaccount.blob.core.windows.net/mycontainer"> |
235 | + <Prefix>prefix</Prefix> |
236 | + <Marker>marker</Marker> |
237 | + <MaxResults>maxresults</MaxResults> |
238 | + <Delimiter>delimiter</Delimiter> |
239 | + <Blobs> |
240 | + <Blob> |
241 | + <Name>blob-1</Name> |
242 | + <Url>blob-url1</Url> |
243 | + </Blob> |
244 | + <Blob> |
245 | + <Name>blob-2</Name> |
246 | + <Url>blob-url2</Url> |
247 | + </Blob> |
248 | + </Blobs> |
249 | + <NextMarker /> |
250 | + </EnumerationResults>` |
251 | + |
252 | +func (EnvironSuite) TestDestroyCleansUpStorage(c *C) { |
253 | + env := makeEnviron(c) |
254 | + container := "container" |
255 | + transport := &gwacl.MockingTransport{} |
256 | + transport.AddExchange(&http.Response{StatusCode: http.StatusOK, Body: ioutil.NopCloser(strings.NewReader(blobListWith2BlobsResponse))}, nil) |
257 | + transport.AddExchange(&http.Response{StatusCode: http.StatusAccepted, Body: nil}, nil) |
258 | + transport.AddExchange(&http.Response{StatusCode: http.StatusAccepted, Body: nil}, nil) |
259 | + azStorage := makeAzureStorageMocking(transport, container, "account") |
260 | + env.storage = &azStorage |
261 | + services := []gwacl.HostedServiceDescriptor{} |
262 | + patchWithServiceListResponse(c, services) |
263 | + instances := convertToInstances([]gwacl.HostedServiceDescriptor{}) |
264 | + |
265 | + err := env.Destroy(instances) |
266 | + |
267 | + c.Check(err, IsNil) |
268 | + c.Check(transport.Exchanges[1].Request.Method, Equals, "DELETE") |
269 | + c.Check(strings.HasSuffix(transport.Exchanges[1].Request.URL.String(), "blob-1"), IsTrue) |
270 | + c.Check(transport.Exchanges[2].Request.Method, Equals, "DELETE") |
271 | + c.Check(strings.HasSuffix(transport.Exchanges[2].Request.URL.String(), "blob-2"), IsTrue) |
272 | +} |
273 | + |
274 | +var emptyListResponse = ` |
275 | + <?xml version="1.0" encoding="utf-8"?> |
276 | + <EnumerationResults ContainerName="http://myaccount.blob.core.windows.net/mycontainer"> |
277 | + <Prefix>prefix</Prefix> |
278 | + <Marker>marker</Marker> |
279 | + <MaxResults>maxresults</MaxResults> |
280 | + <Delimiter>delimiter</Delimiter> |
281 | + <Blobs></Blobs> |
282 | + <NextMarker /> |
283 | + </EnumerationResults>` |
284 | + |
285 | +func (EnvironSuite) TestDestroyStopsAllInstances(c *C) { |
286 | + env := makeEnviron(c) |
287 | + // Setup an empty storage. |
288 | + container := "container" |
289 | + response := makeResponse(emptyListResponse, http.StatusOK) |
290 | + azStorage, _ := makeAzureStorage(response, container, "account") |
291 | + env.storage = &azStorage |
292 | + // Simulate 2 nodes corresponding to two Azure services. |
293 | + prefix := env.getEnvPrefix() |
294 | + service1Name := prefix + "service1" |
295 | + service2Name := prefix + "service2" |
296 | + service1, service1Desc := makeAzureService(service1Name) |
297 | + service2, service2Desc := makeAzureService(service2Name) |
298 | + services := []*gwacl.HostedService{service1, service2} |
299 | + // The call to AllInstances() will return only one service (service1). |
300 | + listInstancesResponses := buildAzureServiceListResponse(c, []gwacl.HostedServiceDescriptor{*service1Desc}) |
301 | + destroyResponses := buildDestroyAzureServiceResponses(c, services) |
302 | + responses := append(listInstancesResponses, destroyResponses...) |
303 | + requests := gwacl.PatchManagementAPIResponses(responses) |
304 | + |
305 | + // Call Destroy with service1 and service2. |
306 | + instances := convertToInstances([]gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc}) |
307 | + err := env.Destroy(instances) |
308 | + c.Check(err, IsNil) |
309 | + |
310 | + // One request to get the list of all the environment's instances. |
311 | + // Then two requests per destroyed machine (one to fetch the |
312 | + // service's information, one to delete it). |
313 | + c.Check((*requests), HasLen, 1+len(services)*2) |
314 | + c.Check((*requests)[0].Method, Equals, "GET") |
315 | + c.Check((*requests)[1].Method, Equals, "GET") |
316 | + c.Check(strings.Contains((*requests)[1].URL, service1Name), IsTrue) |
317 | + c.Check((*requests)[2].Method, Equals, "DELETE") |
318 | + c.Check(strings.Contains((*requests)[2].URL, service1Name), IsTrue) |
319 | + c.Check((*requests)[3].Method, Equals, "GET") |
320 | + c.Check(strings.Contains((*requests)[3].URL, service2Name), IsTrue) |
321 | + c.Check((*requests)[4].Method, Equals, "DELETE") |
322 | + c.Check(strings.Contains((*requests)[4].URL, service2Name), IsTrue) |
323 | +} |
Reviewers: mp+173657_ code.launchpad. net,
Message:
Please take a look.
Description:
Azure provider: Destroy() and StopInstances().
This branch implements environ's Destroy() and StopInstances() in the
Azure provider. As always with the provider code, most of the
non-obvious code is in the tests. Since the testing utilities provided
by gwacl return a slice of the fake requests made, it's tempting to make
an extensive usage of that information and basically duplicate the
unit-tests for the called gwacl methods. Instead of doing that, I chose
a middle ground and checked that the requests look right without going
too deep; this should be sufficient to test that the provider methods
have the expected behavior and get tests to fail if the gwacl methods
change significantly.
https:/ /code.launchpad .net/~rvb/ juju-core/ az-destroy/ +merge/ 173657
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/11034044/
Affected files: azure/environ. go azure/environ_ test.go
A [revision details]
M environs/
M environs/