Merge lp:~rvb/juju-core/az-destroy into lp:~go-bot/juju-core/trunk

Proposed by Raphaël Badin
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
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.

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

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:
   A [revision details]
   M environs/azure/environ.go
   M environs/azure/environ_test.go

Revision history for this message
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://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go
File environs/azure/environ_test.go (right):

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode351
environs/azure/environ_test.go:351: // When destroying a hosted service,
gwacl first issues a Get reques
typo: request

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode390
environs/azure/environ_test.go:390: responses :=
buildDestroyServiceResponses(c, services)
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://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode403
environs/azure/environ_test.go:403: c.Check((*requests)[3].Method,
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://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode456
environs/azure/environ_test.go:456:
c.Check(strings.HasSuffix(transport.Exchanges[2].Request.URL.String(),
"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://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode496
environs/azure/environ_test.go:496: c.Check((*requests), HasLen,
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.

https://codereview.appspot.com/11034044/

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (7.5 KiB)

LGTM, thoroughly tested, but as always a few notes still.

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go
File environs/azure/environ.go (right):

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode134
environs/azure/environ.go:134: // Shortcut to exit quickly if
'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://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode144
environs/azure/environ.go:144: // Shutdown all the instances; If there
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://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode247
environs/azure/environ.go:247: logger.Debugf("environs/azure: destroying
environment %q", env.name)

No need to prefix the message with "environs/azure." That's built into
the logger.

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode261
environs/azure/environ.go:261: // Stop all instances.

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://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode271
environs/azure/environ.go:271: // Add any instances we've been told
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[string]bool)
for _, inst := range env.AllInstances() {
 combinedInstances[inst] = true
}
for _, inst := range ensureInstances {
 combinedInstances[inst] = true
}
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://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode281
environs/azure/environ.go:281: if err != nil {

Maybe just "return env.StopInstances(insts)"? I just can't take the
mindless repetition of error propagation any more!

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go
File environs/azure/environ_test.go (right):

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode9
environs/azure/environ_test.go:9: "io/ioutil"

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...

Read more...

Revision history for this message
Raphaël Badin (rvb) wrote :
Download full text (7.4 KiB)

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://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go
> File environs/azure/environ.go (right):

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode134
> environs/azure/environ.go:134: // Shortcut to exit quickly if
'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://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode144
> environs/azure/environ.go:144: // Shutdown all the instances; If there
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://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode247
> environs/azure/environ.go:247: logger.Debugf("environs/azure:
destroying
> environment %q", env.name)

> No need to prefix the message with "environs/azure." That's built
into the
> logger.

Well spotted, fixed.

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode261
> environs/azure/environ.go:261: // Stop all instances.

> 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://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode271
> environs/azure/environ.go:271: // Add any instances we've been told
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://codereview.appspot.com/11034044/diff/1/environs/azure/environ.go#newcode281
> environs/azure/environ.go:281: if err != nil {

> Maybe just "return env.StopInstances(insts)"? I just can't take the
mindless
> repetition of error propagation any more!

Done.

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go
> File environs/azure/environ_test.go (right):

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode9
> environs/azure/environ_test.go:9: "io/ioutil"

> 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 ...

Read more...

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

On 2013/07/09 10:12:41, rvb wrote:

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode351
> > environs/azure/environ_test.go:351: // When destroying a hosted
service, gwacl
> > first issues a Get reques
> >
> > This makes it sound as if gwacl.DeleteHostedService is doing all
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 DestroyHostedService does.

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

https://codereview.appspot.com/11034044/

Revision history for this message
Raphaël Badin (rvb) wrote :
Download full text (3.7 KiB)

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/makeAzureService).

> 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://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode351
> environs/azure/environ_test.go:351: // When destroying a hosted
service, gwacl
> first issues a Get reques
> typo: request

Fixed.

https://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode390
> environs/azure/environ_test.go:390: responses :=
buildDestroyServiceResponses(c,
> 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://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode403
> environs/azure/environ_test.go:403: c.Check((*requests)[3].Method,
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://codereview.appspot.com/11034044/diff/1/environs/azure/environ_test.go#newcode456
> environs/azure/environ_test.go:456:
> c.Check(strings.HasSuffix(transport.Exchanges[2].Request.URL.String(),
> "blob-2"), IsTrue)
> Like this one is actually asserting that ...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+}

Subscribers

People subscribed via source and target branches

to status/vote changes: