Merge lp:~axwalk/juju-core/azure-provider-roleisinstance into lp:~go-bot/juju-core/trunk
- azure-provider-roleisinstance
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Andrew Wilkins |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2554 |
Proposed branch: | lp:~axwalk/juju-core/azure-provider-roleisinstance |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
2709 lines (+1124/-918) 5 files modified
dependencies.tsv (+1/-1) provider/azure/environ.go (+420/-176) provider/azure/environ_test.go (+321/-241) provider/azure/instance.go (+133/-146) provider/azure/instance_test.go (+249/-354) |
To merge this branch: | bzr merge lp:~axwalk/juju-core/azure-provider-roleisinstance |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+210353@code.launchpad.net |
Commit message
provider/azure: enable load-balancing/
This change is required to enable Availability Set
support for Azure. The existing implementation was
fundamentally incompatible with Availability Sets,
so the implementation has been significantly altered,
while maintaining backwards compatibility.
In the old implementation, instances are associated
one-to-one with Cloud Services. In the new
implementation, instances are associated with Roles
within a Cloud Service; there may be multiple Roles
within a single Cloud Service.
If a service is exposed, then its ports will be load-
balanced across each instance within the cloud service
which opens that port. We currently open ports 22 for
all instances, and the Mongo and API server ports for
state server instances. State server instances are
allocated to a single Cloud Service.
There are a number of incidental improvements, e.g.
using an updated version of DeleteHostedService which
destroys associated disks/blobs server side.
All instances are now allocated to an Availability Set
called "juju", which is scoped to the Cloud Service.
We do not currently group units into Cloud Services
automatically, however the ground work is laid to do
this. A follow-up will implement this work.
When we enable grouping, the same time we will have to
make changes to how we connect to machines via SSH,
either by proxying through an arbitrary machine in the
Cloud Service, or by not load balancing port 22 and
connecting to the machine's auto-allocated public port.
Description of the change
provider/azure: enable load-balancing/
This change is required to enable Availability Set
support for Azure. The existing implementation was
fundamentally incompatible with Availability Sets,
so the implementation has been significantly altered,
while maintaining backwards compatibility.
In the old implementation, instances are associated
one-to-one with Cloud Services. In the new
implementation, instances are associated with Roles
within a Cloud Service; there may be multiple Roles
within a single Cloud Service.
If a service is exposed, then its ports will be load-
balanced across each instance within the cloud service
which opens that port. We currently open ports 22 for
all instances, and the Mongo and API server ports for
state server instances. State server instances are
allocated to a single Cloud Service.
There are a number of incidental improvements, e.g.
using an updated version of DeleteHostedService which
destroys associated disks/blobs server side.
All instances are now allocated to an Availability Set
called "juju", which is scoped to the Cloud Service.
We do not currently group units into Cloud Services
automatically, however the ground work is laid to do
this. A follow-up will implement this work.
When we enable grouping, the same time we will have to
make changes to how we connect to machines via SSH,
either by proxying through an arbitrary machine in the
Cloud Service, or by not load balancing port 22 and
connecting to the machine's auto-allocated public port.
Andrew Wilkins (axwalk) wrote : | # |
Tim Penhey (thumper) wrote : | # |
Lots of changes here, comments below.
I'm hoping that CI for Azure would help catch any actual problems here
as it is a bit beyond a simple review :-)
https:/
File provider/
https:/
provider/
{
oh... didn't know you could do that...
reassigning to err I mean...
https:/
provider/
*gwacl.
instance.Instance, resultErr error) {
it isn't entirely clear why 'createRole' returns an instance
https:/
provider/
deploymentNameV
perhaps a blank before this line? wrapping lines makes it harder to pick
out.
https:/
provider/
way of getting the
s/provie/provide/
https:/
provider/
why skip services where we have more than one deployment?
https:/
File provider/
https:/
provider/
&azureInstance{})
Hmm... didn't know about this checker, but perhaps you should make that
one an Assert not a Check, because if it fails, the next line will
panic.
https:/
File provider/
https:/
provider/
azInstance.
Which parts of the *azureInstance are being protected by the mutex?
https:/
File provider/
https:/
provider/
Normally at least want to include the testbase.
the logging.
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
https:/
File provider/
https:/
provider/
*gwacl.
instance.Instance, resultErr error) {
On 2014/03/13 04:15:05, thumper wrote:
> it isn't entirely clear why 'createRole' returns an instance
Renamed to instance. Instance and role are equivalent, but I'll keep it
to Juju terminology.
https:/
provider/
deploymentNameV
On 2014/03/13 04:15:05, thumper wrote:
> perhaps a blank before this line? wrapping lines makes it harder to
pick out.
Done.
https:/
provider/
way of getting the
On 2014/03/13 04:15:05, thumper wrote:
> s/provie/provide/
Done.
https:/
provider/
On 2014/03/13 04:15:05, thumper wrote:
> why skip services where we have more than one deployment?
Because if there's more than one, that means someone's been mucking
about with the objects. Juju only creates one deployment per Cloud
Service. You can have up to two (Prod & Dev), but we only do Production.
https:/
File provider/
https:/
provider/
&azureInstance{})
On 2014/03/13 04:15:05, thumper wrote:
> Hmm... didn't know about this checker, but perhaps you should make
that one an
> Assert not a Check, because if it fails, the next line will panic.
Done.
https:/
File provider/
https:/
provider/
azInstance.
On 2014/03/13 04:15:05, thumper wrote:
> Which parts of the *azureInstance are being protected by the mutex?
Only roleInstance (I've been told the convention is to put those fields
that are guarded below the mutex). All others are immutable and
thread-safe.
https:/
File provider/
https:/
provider/
On 2014/03/13 04:15:05, thumper wrote:
> Normally at least want to include the testbase.
the
> logging.
Done.
William Reade (fwereade) wrote : | # |
pre-comment for discussion
https:/
File provider/
https:/
provider/
oooh, this is going to break sometime. HA state server nodes will not
necessarily be started with this field set. How much will we depend upon
this? If it's just cosmetic it's maybe not such a big deal, but really I
think it's up to juju itself to supply a sensible label to be applied if
possible.
https:/
provider/
userData, machineConfig.
heh, this is the same problem, but more significant. We'll have to start
distinguishing between StateServer and BootstrapMachine or something in
MachineConfig -- StateServer wouldn't affect cloudinit (because the MA
will turn itself into a stateserver when it connects) but can be handled
in this case, while Bootstrap will be the thing that controls the
special...
wait. This is more complicated than I thought. Would you ping me when
you're back from supper please?
Roger Peppe (rogpeppe) wrote : | # |
LGTM with some thoughts and suggestions below.
https:/
File provider/
https:/
provider/
base64.
Shouldn't gwacl be doing this decoding, given that it's doing the
encoding itself? (it seems a bit odd that base-64 encoding is required -
the azure docs distinctly show a non-base64-encoded example)
https:/
provider/
necessary Azure entities in order
// createInstance creates all of the Azure entities
// necessary for a new instance. ...
?
https:/
provider/
On 2014/03/19 09:54:08, fwereade wrote:
> oooh, this is going to break sometime. HA state server nodes will not
> necessarily be started with this field set. How much will we depend
upon this?
> If it's just cosmetic it's maybe not such a big deal, but really I
think it's up
> to juju itself to supply a sensible label to be applied if possible.
I believe that it's not possible to assign a machine to an availability
set after it's been started, so unless this field *is* set at instance
start time, there's nothing we can do.
Perhaps it *might* make sense to add a "group" (or something similarly
named) field in MachineConfig. Then all the logic for deciding which
machines are grouped together can be outside of the provider logic (the
providers can remain blissfully ignorant of juju services); different
providers might decide for themselves how to interpret the group
(ignoring it would be acceptable).
https:/
File provider/
https:/
provider/
is an RWMutex really worth it here? i'd just use a mutex - it's smaller,
and space is a more likely problem than time in this case, i'd say.
https:/
provider/
could we name the fields here please?
https:/
provider/
trivial formatting: i think we usually put the } on a new line, so that
all the fields look the same.
https:/
provider/
s/} else {/}/
(save a level of indentation)
Andrew Wilkins (axwalk) wrote : | # |
https:/
File provider/
https:/
provider/
On 2014/03/19 09:54:08, fwereade wrote:
> oooh, this is going to break sometime. HA state server nodes will not
> necessarily be started with this field set. How much will we depend
upon this?
> If it's just cosmetic it's maybe not such a big deal, but really I
think it's up
> to juju itself to supply a sensible label to be applied if possible.
Based on discussion on IRC, I will change this to use
DistributionIns
return state server instances if the machine being provisioned is a
state server, otherwise it will return common service instances.
The whole label thing will just disappear, since the cloud service can
be extracted from the instance ID.
William Reade (fwereade) wrote : | # |
WIP for DistributionIns
Andrew Wilkins (axwalk) wrote : | # |
Oops, never did mail this.
https:/
File provider/
https:/
provider/
base64.
On 2014/03/19 11:44:35, rog wrote:
> Shouldn't gwacl be doing this decoding, given that it's doing the
encoding
> itself? (it seems a bit odd that base-64 encoding is required - the
azure docs
> distinctly show a non-base64-encoded example)
I agree, the base64 encoding/decoding is a bit clumsy and leaky. I don't
think we'll be using the labels much longer, though.
https:/
provider/
necessary Azure entities in order
On 2014/03/19 11:44:35, rog wrote:
> // createInstance creates all of the Azure entities
> // necessary for a new instance. ...
> ?
Done.
https:/
File provider/
https:/
provider/
On 2014/03/19 11:44:35, rog wrote:
> is an RWMutex really worth it here? i'd just use a mutex - it's
smaller, and
> space is a more likely problem than time in this case, i'd say.
Done.
https:/
provider/
On 2014/03/19 11:44:35, rog wrote:
> could we name the fields here please?
Done.
https:/
provider/
On 2014/03/19 11:44:35, rog wrote:
> trivial formatting: i think we usually put the } on a new line, so
that all the
> fields look the same.
Yeah, this is existing code, I usually do that too. I've reformatted it.
https:/
provider/
On 2014/03/19 11:44:35, rog wrote:
> s/} else {/}/
> (save a level of indentation)
Done.
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
William Reade (fwereade) wrote : | # |
Thanks, this is great. LGTM.
https:/
File provider/
https:/
provider/
something more useful?
It's considered ok for status to use the provider's vocabulary.
https:/
provider/
This bothers me a bit, but it feels more like an azure problem than our
problem.
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
https:/
File provider/
https:/
provider/
something more useful?
On 2014/04/03 07:31:15, fwereade wrote:
> It's considered ok for status to use the provider's vocabulary.
Removed.
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~axwalk/juju-core/azure-provider-roleisinstance into lp:juju-core failed. Below is the output from the failed tests.
"/home/
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
? launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? ...
Preview Diff
1 | === modified file 'dependencies.tsv' |
2 | --- dependencies.tsv 2014-04-02 11:35:49 +0000 |
3 | +++ dependencies.tsv 2014-04-03 07:35:44 +0000 |
4 | @@ -16,6 +16,6 @@ |
5 | launchpad.net/gomaasapi bzr martin.packman@canonical.com-20140401145548-bcmsw2gic5e576ns 48 |
6 | launchpad.net/goose bzr tarmac-20140124165235-h9rloooc531udms5 116 |
7 | launchpad.net/goyaml bzr gustavo@niemeyer.net-20131114120802-abe042syx64z2m7s 50 |
8 | -launchpad.net/gwacl bzr tarmac-20140205045433-81h182mhz24fzp5e 231 |
9 | +launchpad.net/gwacl bzr tarmac-20140312041035-ac7gw7kcqjx7db63 234 |
10 | launchpad.net/lpad bzr gustavo@niemeyer.net-20120626194701-536yx0g9jdq2ik3h 64 |
11 | launchpad.net/tomb bzr gustavo@niemeyer.net-20130531003818-70ikdgklbxopn8x4 17 |
12 | |
13 | === modified file 'provider/azure/environ.go' |
14 | --- provider/azure/environ.go 2014-04-03 03:37:45 +0000 |
15 | +++ provider/azure/environ.go 2014-04-03 07:35:44 +0000 |
16 | @@ -4,8 +4,11 @@ |
17 | package azure |
18 | |
19 | import ( |
20 | + "encoding/base64" |
21 | "fmt" |
22 | "net/http" |
23 | + "regexp" |
24 | + "strings" |
25 | "sync" |
26 | "time" |
27 | |
28 | @@ -23,16 +26,12 @@ |
29 | "launchpad.net/juju-core/provider/common" |
30 | "launchpad.net/juju-core/state" |
31 | "launchpad.net/juju-core/state/api" |
32 | + "launchpad.net/juju-core/state/api/params" |
33 | "launchpad.net/juju-core/utils" |
34 | - "launchpad.net/juju-core/utils/parallel" |
35 | + "launchpad.net/juju-core/utils/set" |
36 | ) |
37 | |
38 | const ( |
39 | - // In our initial implementation, each instance gets its own hosted |
40 | - // service, deployment and role in Azure. The role always gets this |
41 | - // hostname (instance==service). |
42 | - roleHostname = "default" |
43 | - |
44 | // deploymentSlot says in which slot to deploy instances. Azure |
45 | // supports 'Production' or 'Staging'. |
46 | // This provider always deploys to Production. Think twice about |
47 | @@ -48,6 +47,10 @@ |
48 | // environement, in CIDR notation. This is the network used for |
49 | // machine-to-machine communication. |
50 | networkDefinition = "10.0.0.0/8" |
51 | + |
52 | + // stateServerLabel is the label applied to the cloud service created |
53 | + // for state servers. |
54 | + stateServerLabel = "juju-state-server" |
55 | ) |
56 | |
57 | type azureEnviron struct { |
58 | @@ -210,14 +213,40 @@ |
59 | return azure.AddVirtualNetworkSite(&virtualNetwork) |
60 | } |
61 | |
62 | +// deleteVnetAttempt is an AttemptyStrategy for use |
63 | +// when attempting delete a virtual network. This is |
64 | +// necessary as Azure apparently does not release all |
65 | +// references to the vnet even when all cloud services |
66 | +// are deleted. |
67 | +var deleteVnetAttempt = utils.AttemptStrategy{ |
68 | + Total: 30 * time.Second, |
69 | + Delay: 1 * time.Second, |
70 | +} |
71 | + |
72 | +var networkInUse = regexp.MustCompile(".*The virtual network .* is currently in use.*") |
73 | + |
74 | func (env *azureEnviron) deleteVirtualNetwork() error { |
75 | azure, err := env.getManagementAPI() |
76 | if err != nil { |
77 | return err |
78 | } |
79 | defer env.releaseManagementAPI(azure) |
80 | - vnetName := env.getVirtualNetworkName() |
81 | - return azure.RemoveVirtualNetworkSite(vnetName) |
82 | + for a := deleteVnetAttempt.Start(); a.Next(); { |
83 | + vnetName := env.getVirtualNetworkName() |
84 | + err = azure.RemoveVirtualNetworkSite(vnetName) |
85 | + if err == nil { |
86 | + return nil |
87 | + } |
88 | + if err, ok := err.(*gwacl.AzureError); ok { |
89 | + if err.StatusCode() == 400 && networkInUse.MatchString(err.Message) { |
90 | + // Retry on "virtual network XYZ is currently in use". |
91 | + continue |
92 | + } |
93 | + } |
94 | + // Any other error should be returned. |
95 | + break |
96 | + } |
97 | + return err |
98 | } |
99 | |
100 | // getContainerName returns the name of the private storage account container |
101 | @@ -294,7 +323,7 @@ |
102 | // name it chooses (based on the given prefix), but recognizes that the name |
103 | // may not be available. If the name is not available, it does not treat that |
104 | // as an error but just returns nil. |
105 | -func attemptCreateService(azure *gwacl.ManagementAPI, prefix string, affinityGroupName string, location string) (*gwacl.CreateHostedService, error) { |
106 | +func attemptCreateService(azure *gwacl.ManagementAPI, prefix, affinityGroupName, label string) (*gwacl.CreateHostedService, error) { |
107 | var err error |
108 | name := gwacl.MakeRandomHostedServiceName(prefix) |
109 | err = azure.CheckHostedServiceNameAvailability(name) |
110 | @@ -302,7 +331,10 @@ |
111 | // The calling function should retry. |
112 | return nil, nil |
113 | } |
114 | - req := gwacl.NewCreateHostedServiceWithLocation(name, name, location) |
115 | + if label == "" { |
116 | + label = name |
117 | + } |
118 | + req := gwacl.NewCreateHostedServiceWithLocation(name, label, "") |
119 | req.AffinityGroup = affinityGroupName |
120 | err = azure.AddHostedService(req) |
121 | if err != nil { |
122 | @@ -313,19 +345,19 @@ |
123 | |
124 | // newHostedService creates a hosted service. It will make up a unique name, |
125 | // starting with the given prefix. |
126 | -func newHostedService(azure *gwacl.ManagementAPI, prefix string, affinityGroupName string, location string) (*gwacl.CreateHostedService, error) { |
127 | +func newHostedService(azure *gwacl.ManagementAPI, prefix, affinityGroupName, label string) (*gwacl.HostedService, error) { |
128 | var err error |
129 | - var svc *gwacl.CreateHostedService |
130 | - for tries := 10; tries > 0 && err == nil && svc == nil; tries-- { |
131 | - svc, err = attemptCreateService(azure, prefix, affinityGroupName, location) |
132 | + var createdService *gwacl.CreateHostedService |
133 | + for tries := 10; tries > 0 && err == nil && createdService == nil; tries-- { |
134 | + createdService, err = attemptCreateService(azure, prefix, affinityGroupName, label) |
135 | } |
136 | if err != nil { |
137 | return nil, fmt.Errorf("could not create hosted service: %v", err) |
138 | } |
139 | - if svc == nil { |
140 | + if createdService == nil { |
141 | return nil, fmt.Errorf("could not come up with a unique hosted service name - is your randomizer initialized?") |
142 | } |
143 | - return svc, nil |
144 | + return azure.GetHostedServiceProperties(createdService.ServiceName, true) |
145 | } |
146 | |
147 | // SupportedArchitectures is specified on the EnvironCapability interface. |
148 | @@ -385,6 +417,90 @@ |
149 | return spec.InstanceType.Id, spec.Image.Id, nil |
150 | } |
151 | |
152 | +// createInstance creates all of the Azure entities necessary for a |
153 | +// new instance. This includes Cloud Service, Deployment and Role. |
154 | +// |
155 | +// If serviceName is non-empty, then createInstance will assign to |
156 | +// the Cloud Service with that name. Otherwise, a new Cloud Service |
157 | +// will be created. |
158 | +func (env *azureEnviron) createInstance(azure *gwacl.ManagementAPI, role *gwacl.Role, serviceName string, stateServer bool) (resultInst instance.Instance, resultErr error) { |
159 | + var inst instance.Instance |
160 | + defer func() { |
161 | + if inst != nil && resultErr != nil { |
162 | + if err := env.StopInstances([]instance.Instance{inst}); err != nil { |
163 | + // Failure upon failure. Log it, but return the original error. |
164 | + logger.Errorf("error releasing failed instance: %v", err) |
165 | + } |
166 | + } |
167 | + }() |
168 | + var err error |
169 | + var service *gwacl.HostedService |
170 | + if serviceName != "" { |
171 | + service, err = azure.GetHostedServiceProperties(serviceName, true) |
172 | + } else { |
173 | + // If we're creating a cloud service for state servers, |
174 | + // we will want to open additional ports. We need to |
175 | + // record this against the cloud service, so we use a |
176 | + // special label for the purpose. |
177 | + var label string |
178 | + if stateServer { |
179 | + label = stateServerLabel |
180 | + } |
181 | + service, err = newHostedService(azure, env.getEnvPrefix(), env.getAffinityGroupName(), label) |
182 | + } |
183 | + if err != nil { |
184 | + return nil, err |
185 | + } |
186 | + if len(service.Deployments) == 0 { |
187 | + // This is a newly created cloud service, so we |
188 | + // should destroy it if anything below fails. |
189 | + defer func() { |
190 | + if resultErr != nil { |
191 | + azure.DeleteHostedService(service.ServiceName) |
192 | + // Destroying the hosted service destroys the instance, |
193 | + // so ensure StopInstances isn't called. |
194 | + inst = nil |
195 | + } |
196 | + }() |
197 | + // Create an initial deployment. |
198 | + deployment := gwacl.NewDeploymentForCreateVMDeployment( |
199 | + deploymentNameV2(service.ServiceName), |
200 | + deploymentSlot, |
201 | + deploymentNameV2(service.ServiceName), |
202 | + []gwacl.Role{*role}, |
203 | + env.getVirtualNetworkName(), |
204 | + ) |
205 | + if err := azure.AddDeployment(deployment, service.ServiceName); err != nil { |
206 | + return nil, err |
207 | + } |
208 | + service.Deployments = append(service.Deployments, *deployment) |
209 | + } else { |
210 | + // Update the deployment. |
211 | + deployment := &service.Deployments[0] |
212 | + if err := azure.AddRole(&gwacl.AddRoleRequest{ |
213 | + ServiceName: service.ServiceName, |
214 | + DeploymentName: deployment.Name, |
215 | + PersistentVMRole: (*gwacl.PersistentVMRole)(role), |
216 | + }); err != nil { |
217 | + return nil, err |
218 | + } |
219 | + deployment.RoleList = append(deployment.RoleList, *role) |
220 | + } |
221 | + return env.getInstance(service, role.RoleName) |
222 | +} |
223 | + |
224 | +// deploymentNameV1 returns the deployment name used |
225 | +// in the original implementation of the Azure provider. |
226 | +func deploymentNameV1(serviceName string) string { |
227 | + return serviceName |
228 | +} |
229 | + |
230 | +// deploymentNameV2 returns the deployment name used |
231 | +// in the current implementation of the Azure provider. |
232 | +func deploymentNameV2(serviceName string) string { |
233 | + return serviceName + "-v2" |
234 | +} |
235 | + |
236 | // StartInstance is specified in the InstanceBroker interface. |
237 | func (env *azureEnviron) StartInstance(args environs.StartInstanceParams) (_ instance.Instance, _ *instance.HardwareCharacteristics, err error) { |
238 | |
239 | @@ -417,24 +533,7 @@ |
240 | } |
241 | defer env.releaseManagementAPI(azure) |
242 | |
243 | - snap := env.getSnapshot() |
244 | - location := snap.ecfg.location() |
245 | - service, err := newHostedService(azure.ManagementAPI, env.getEnvPrefix(), env.getAffinityGroupName(), location) |
246 | - if err != nil { |
247 | - return nil, nil, err |
248 | - } |
249 | - serviceName := service.ServiceName |
250 | - |
251 | - // If we fail after this point, clean up the hosted service. |
252 | - defer func() { |
253 | - if err != nil { |
254 | - azure.DestroyHostedService( |
255 | - &gwacl.DestroyHostedServiceRequest{ |
256 | - ServiceName: serviceName, |
257 | - }) |
258 | - } |
259 | - }() |
260 | - |
261 | + location := env.getSnapshot().ecfg.location() |
262 | instanceType, sourceImageName, err := env.selectInstanceTypeAndImage(&instances.InstanceConstraint{ |
263 | Region: location, |
264 | Series: args.Tools.OneSeries(), |
265 | @@ -445,42 +544,38 @@ |
266 | return nil, nil, err |
267 | } |
268 | |
269 | - // virtualNetworkName is the virtual network to which all the |
270 | - // deployments in this environment belong. |
271 | - virtualNetworkName := env.getVirtualNetworkName() |
272 | + // We use the cloud service label as a way to group instances with |
273 | + // the same affinity, so that machines can be be allocated to the |
274 | + // same availability set. |
275 | + var cloudServiceName string |
276 | + // TODO(axw) replace "false &&" with mode check once |
277 | + // availability-sets-enabled change is landed. |
278 | + if false && args.DistributionGroup != nil { |
279 | + instanceIds, err := args.DistributionGroup() |
280 | + if err != nil { |
281 | + return nil, nil, err |
282 | + } |
283 | + for _, id := range instanceIds { |
284 | + cloudServiceName, _ = env.splitInstanceId(id) |
285 | + if cloudServiceName != "" { |
286 | + break |
287 | + } |
288 | + } |
289 | + logger.Debugf("using existing cloud service: %q", cloudServiceName) |
290 | + } |
291 | |
292 | - // 1. Create an OS Disk. |
293 | vhd := env.newOSDisk(sourceImageName) |
294 | - |
295 | - // 2. Create a Role for a Linux machine. |
296 | - role := env.newRole(instanceType, vhd, userData, roleHostname) |
297 | - |
298 | - // 3. Create the Deployment object. |
299 | - deployment := env.newDeployment(role, serviceName, serviceName, virtualNetworkName) |
300 | - |
301 | - err = azure.AddDeployment(deployment, serviceName) |
302 | - if err != nil { |
303 | - return nil, nil, err |
304 | - } |
305 | - |
306 | - var inst instance.Instance |
307 | - |
308 | - // From here on, remember to shut down the instance before returning |
309 | - // any error. |
310 | - defer func() { |
311 | - if err != nil && inst != nil { |
312 | - err2 := env.StopInstances([]instance.Instance{inst}) |
313 | - if err2 != nil { |
314 | - // Failure upon failure. Log it, but return |
315 | - // the original error. |
316 | - logger.Errorf("error releasing failed instance: %v", err) |
317 | - } |
318 | + // If we're creating machine-0, we'll want to expose port 22. |
319 | + // All other machines get an auto-generated public port for SSH. |
320 | + stateServer := false |
321 | + for _, job := range args.MachineConfig.Jobs { |
322 | + if job == params.JobManageEnviron { |
323 | + stateServer = true |
324 | + break |
325 | } |
326 | - }() |
327 | - |
328 | - // Assign the returned instance to 'inst' so that the deferred method |
329 | - // above can perform its check. |
330 | - inst, err = env.getInstance(serviceName) |
331 | + } |
332 | + role := env.newRole(instanceType, vhd, userData, stateServer) |
333 | + inst, err := env.createInstance(azure.ManagementAPI, role, cloudServiceName, stateServer) |
334 | if err != nil { |
335 | return nil, nil, err |
336 | } |
337 | @@ -490,17 +585,52 @@ |
338 | |
339 | // getInstance returns an up-to-date version of the instance with the given |
340 | // name. |
341 | -func (env *azureEnviron) getInstance(instanceName string) (instance.Instance, error) { |
342 | - context, err := env.getManagementAPI() |
343 | - if err != nil { |
344 | - return nil, err |
345 | - } |
346 | - defer env.releaseManagementAPI(context) |
347 | - service, err := context.GetHostedServiceProperties(instanceName, false) |
348 | - if err != nil { |
349 | - return nil, fmt.Errorf("could not get instance %q: %v", instanceName, err) |
350 | - } |
351 | - instance := &azureInstance{service.HostedServiceDescriptor, env} |
352 | +func (env *azureEnviron) getInstance(hostedService *gwacl.HostedService, roleName string) (instance.Instance, error) { |
353 | + if n := len(hostedService.Deployments); n != 1 { |
354 | + return nil, fmt.Errorf("expected one deployment for %q, got %d", hostedService.ServiceName, n) |
355 | + } |
356 | + deployment := &hostedService.Deployments[0] |
357 | + |
358 | + var maskStateServerPorts bool |
359 | + var instanceId instance.Id |
360 | + switch deployment.Name { |
361 | + case deploymentNameV1(hostedService.ServiceName): |
362 | + // Old style instance. |
363 | + instanceId = instance.Id(hostedService.ServiceName) |
364 | + if n := len(deployment.RoleList); n != 1 { |
365 | + return nil, fmt.Errorf("expected one role for %q, got %d", deployment.Name, n) |
366 | + } |
367 | + roleName = deployment.RoleList[0].RoleName |
368 | + // In the old implementation of the Azure provider, |
369 | + // all machines opened the state and API server ports. |
370 | + maskStateServerPorts = true |
371 | + |
372 | + case deploymentNameV2(hostedService.ServiceName): |
373 | + instanceId = instance.Id(fmt.Sprintf("%s-%s", hostedService.ServiceName, roleName)) |
374 | + // Newly created state server machines are put into |
375 | + // the cloud service with the stateServerLabel label. |
376 | + if decoded, err := base64.StdEncoding.DecodeString(hostedService.Label); err == nil { |
377 | + maskStateServerPorts = string(decoded) == stateServerLabel |
378 | + } |
379 | + } |
380 | + |
381 | + var roleInstance *gwacl.RoleInstance |
382 | + for _, role := range deployment.RoleInstanceList { |
383 | + if role.RoleName == roleName { |
384 | + roleInstance = &role |
385 | + break |
386 | + } |
387 | + } |
388 | + |
389 | + instance := &azureInstance{ |
390 | + environ: env, |
391 | + hostedService: &hostedService.HostedServiceDescriptor, |
392 | + instanceId: instanceId, |
393 | + deploymentName: deployment.Name, |
394 | + roleName: roleName, |
395 | + roleInstance: roleInstance, |
396 | + maskStateServerPorts: maskStateServerPorts, |
397 | + } |
398 | return instance, nil |
399 | } |
400 | |
401 | @@ -519,29 +649,40 @@ |
402 | |
403 | // getInitialEndpoints returns a slice of the endpoints every instance should have open |
404 | // (ssh port, etc). |
405 | -func (env *azureEnviron) getInitialEndpoints() []gwacl.InputEndpoint { |
406 | +func (env *azureEnviron) getInitialEndpoints(stateServer bool) []gwacl.InputEndpoint { |
407 | + // TODO(axw) either proxy ssh traffic through one of the |
408 | + // randomly chosen VMs to the internal address, or otherwise |
409 | + // don't load balance SSH and provide a way of getting the |
410 | + // local port. |
411 | cfg := env.Config() |
412 | - return []gwacl.InputEndpoint{ |
413 | - { |
414 | - LocalPort: 22, |
415 | - Name: "sshport", |
416 | - Port: 22, |
417 | - Protocol: "tcp", |
418 | - }, |
419 | - // TODO: Ought to have this only for state servers. |
420 | - { |
421 | + endpoints := []gwacl.InputEndpoint{{ |
422 | + LocalPort: 22, |
423 | + Name: "sshport", |
424 | + Port: 22, |
425 | + Protocol: "tcp", |
426 | + }} |
427 | + if stateServer { |
428 | + endpoints = append(endpoints, []gwacl.InputEndpoint{{ |
429 | LocalPort: cfg.StatePort(), |
430 | + Port: cfg.StatePort(), |
431 | + Protocol: "tcp", |
432 | Name: "stateport", |
433 | - Port: cfg.StatePort(), |
434 | - Protocol: "tcp", |
435 | - }, |
436 | - // TODO: Ought to have this only for API servers. |
437 | - { |
438 | + }, { |
439 | LocalPort: cfg.APIPort(), |
440 | + Port: cfg.APIPort(), |
441 | + Protocol: "tcp", |
442 | Name: "apiport", |
443 | - Port: cfg.APIPort(), |
444 | - Protocol: "tcp", |
445 | - }} |
446 | + }}...) |
447 | + } |
448 | + for i, endpoint := range endpoints { |
449 | + endpoint.LoadBalancedEndpointSetName = endpoint.Name |
450 | + endpoint.LoadBalancerProbe = &gwacl.LoadBalancerProbe{ |
451 | + Port: endpoint.Port, |
452 | + Protocol: "TCP", |
453 | + } |
454 | + endpoints[i] = endpoint |
455 | + } |
456 | + return endpoints |
457 | } |
458 | |
459 | // newRole creates a gwacl.Role object (an Azure Virtual Machine) which uses |
460 | @@ -550,101 +691,204 @@ |
461 | // The VM will have: |
462 | // - an 'ubuntu' user defined with an unguessable (randomly generated) password |
463 | // - its ssh port (TCP 22) open |
464 | +// (if a state server) |
465 | // - its state port (TCP mongoDB) port open |
466 | // - its API port (TCP) open |
467 | // |
468 | // roleSize is the name of one of Azure's machine types, e.g. ExtraSmall, |
469 | // Large, A6 etc. |
470 | -func (env *azureEnviron) newRole(roleSize string, vhd *gwacl.OSVirtualHardDisk, userData string, roleHostname string) *gwacl.Role { |
471 | +func (env *azureEnviron) newRole(roleSize string, vhd *gwacl.OSVirtualHardDisk, userData string, stateServer bool) *gwacl.Role { |
472 | + roleName := gwacl.MakeRandomRoleName("juju") |
473 | // Create a Linux Configuration with the username and the password |
474 | // empty and disable SSH with password authentication. |
475 | - hostname := roleHostname |
476 | + hostname := roleName |
477 | username := "ubuntu" |
478 | password := gwacl.MakeRandomPassword() |
479 | linuxConfigurationSet := gwacl.NewLinuxProvisioningConfigurationSet(hostname, username, password, userData, "true") |
480 | - // Generate a Network Configuration with the initially required ports |
481 | - // open. |
482 | - networkConfigurationSet := gwacl.NewNetworkConfigurationSet(env.getInitialEndpoints(), nil) |
483 | - roleName := gwacl.MakeRandomRoleName("juju") |
484 | - // The ordering of these configuration sets is significant for the tests. |
485 | - return gwacl.NewRole( |
486 | - roleSize, roleName, |
487 | + // Generate a Network Configuration with the initially required ports open. |
488 | + networkConfigurationSet := gwacl.NewNetworkConfigurationSet(env.getInitialEndpoints(stateServer), nil) |
489 | + role := gwacl.NewRole( |
490 | + roleSize, roleName, vhd, |
491 | []gwacl.ConfigurationSet{*linuxConfigurationSet, *networkConfigurationSet}, |
492 | - []gwacl.OSVirtualHardDisk{*vhd}) |
493 | -} |
494 | - |
495 | -// newDeployment creates and returns a gwacl Deployment object. |
496 | -func (env *azureEnviron) newDeployment(role *gwacl.Role, deploymentName string, deploymentLabel string, virtualNetworkName string) *gwacl.Deployment { |
497 | - // Use the service name as the label for the deployment. |
498 | - return gwacl.NewDeploymentForCreateVMDeployment(deploymentName, deploymentSlot, deploymentLabel, []gwacl.Role{*role}, virtualNetworkName) |
499 | -} |
500 | - |
501 | -// Spawn this many goroutines to issue requests for destroying services. |
502 | -// TODO: this is currently set to 1 because of a problem in Azure: |
503 | -// removing Services in the same affinity group concurrently causes a conflict. |
504 | -// This conflict is wrongly reported by Azure as a BadRequest (400). |
505 | -// This has been reported to Windows Azure. |
506 | -var maxConcurrentDeletes = 1 |
507 | + ) |
508 | + role.AvailabilitySetName = "juju" |
509 | + return role |
510 | +} |
511 | |
512 | // StartInstance is specified in the InstanceBroker interface. |
513 | func (env *azureEnviron) StopInstances(instances []instance.Instance) error { |
514 | - // Each Juju instance is an Azure Service (instance==service), destroy |
515 | - // all the Azure services. |
516 | - // Acquire management API object. |
517 | context, err := env.getManagementAPI() |
518 | if err != nil { |
519 | return err |
520 | } |
521 | defer env.releaseManagementAPI(context) |
522 | |
523 | - // Destroy all the services in parallel. |
524 | - run := parallel.NewRun(maxConcurrentDeletes) |
525 | + // Map services to role names we want to delete. |
526 | + serviceInstances := make(map[string]map[string]bool) |
527 | for _, instance := range instances { |
528 | - serviceName := string(instance.Id()) |
529 | - run.Do(func() error { |
530 | - request := &gwacl.DestroyHostedServiceRequest{ServiceName: serviceName} |
531 | - return context.DestroyHostedService(request) |
532 | - }) |
533 | - } |
534 | - return run.Wait() |
535 | + instance, ok := instance.(*azureInstance) |
536 | + if !ok { |
537 | + continue |
538 | + } |
539 | + serviceName := instance.hostedService.ServiceName |
540 | + deleteRoleNames, ok := serviceInstances[serviceName] |
541 | + if !ok { |
542 | + deleteRoleNames = make(map[string]bool) |
543 | + serviceInstances[serviceName] = deleteRoleNames |
544 | + } |
545 | + deleteRoleNames[instance.roleName] = true |
546 | + } |
547 | + |
548 | + // Load the properties of each service, so we know whether to |
549 | + // delete the entire service. |
550 | + // |
551 | + // Note: concurrent operations on Affinity Groups have been |
552 | + // found to cause conflict responses, so we do everything serially. |
553 | + for serviceName, deleteRoleNames := range serviceInstances { |
554 | + service, err := context.GetHostedServiceProperties(serviceName, true) |
555 | + if err != nil { |
556 | + return err |
557 | + } else if len(service.Deployments) != 1 { |
558 | + continue |
559 | + } |
560 | + // Filter the instances that have no corresponding role. |
561 | + var roleNames set.Strings |
562 | + for _, role := range service.Deployments[0].RoleList { |
563 | + roleNames.Add(role.RoleName) |
564 | + } |
565 | + for roleName := range deleteRoleNames { |
566 | + if !roleNames.Contains(roleName) { |
567 | + delete(deleteRoleNames, roleName) |
568 | + } |
569 | + } |
570 | + // If we're deleting all the roles, we need to delete the |
571 | + // entire cloud service or we'll get an error. |
572 | + if len(deleteRoleNames) == roleNames.Size() { |
573 | + if err := context.DeleteHostedService(serviceName); err != nil { |
574 | + return err |
575 | + } |
576 | + } else { |
577 | + for roleName := range deleteRoleNames { |
578 | + if err := context.DeleteRole(&gwacl.DeleteRoleRequest{ |
579 | + ServiceName: serviceName, |
580 | + DeploymentName: service.Deployments[0].Name, |
581 | + RoleName: roleName, |
582 | + DeleteMedia: true, |
583 | + }); err != nil { |
584 | + return err |
585 | + } |
586 | + } |
587 | + } |
588 | + } |
589 | + return nil |
590 | +} |
591 | + |
592 | +// destroyAllServices destroys all Cloud Services and deployments contained. |
593 | +// This is needed to clean up broken environments, in which there are cloud |
594 | +// services with no deployments. |
595 | +func (env *azureEnviron) destroyAllServices() error { |
596 | + context, err := env.getManagementAPI() |
597 | + if err != nil { |
598 | + return err |
599 | + } |
600 | + defer env.releaseManagementAPI(context) |
601 | + |
602 | + request := &gwacl.ListPrefixedHostedServicesRequest{ServiceNamePrefix: env.getEnvPrefix()} |
603 | + services, err := context.ListPrefixedHostedServices(request) |
604 | + if err != nil { |
605 | + return err |
606 | + } |
607 | + for _, service := range services { |
608 | + if err := context.DeleteHostedService(service.ServiceName); err != nil { |
609 | + return err |
610 | + } |
611 | + } |
612 | + return nil |
613 | +} |
614 | + |
615 | +// splitInstanceId splits the specified instance.Id into its |
616 | +// cloud-service and role parts. Both values will be empty |
617 | +// if the instance-id is non-matching, and role will be empty |
618 | +// for legacy instance-ids. |
619 | +func (env *azureEnviron) splitInstanceId(id instance.Id) (service, role string) { |
620 | + prefix := env.getEnvPrefix() |
621 | + if !strings.HasPrefix(string(id), prefix) { |
622 | + return "", "" |
623 | + } |
624 | + fields := strings.Split(string(id)[len(prefix):], "-") |
625 | + service = prefix + fields[0] |
626 | + if len(fields) > 1 { |
627 | + role = fields[1] |
628 | + } |
629 | + return service, role |
630 | } |
631 | |
632 | // Instances is specified in the Environ interface. |
633 | func (env *azureEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { |
634 | - // The instance list is built using the list of all the relevant |
635 | - // Azure Services (instance==service). |
636 | - // Acquire management API object. |
637 | context, err := env.getManagementAPI() |
638 | if err != nil { |
639 | return nil, err |
640 | } |
641 | defer env.releaseManagementAPI(context) |
642 | |
643 | - // Prepare gwacl request object. |
644 | - serviceNames := make([]string, len(ids)) |
645 | + type instanceId struct { |
646 | + serviceName, roleName string |
647 | + } |
648 | + |
649 | + instancesIds := make([]instanceId, len(ids)) |
650 | + var serviceNames set.Strings |
651 | for i, id := range ids { |
652 | - serviceNames[i] = string(id) |
653 | + serviceName, roleName := env.splitInstanceId(id) |
654 | + if serviceName == "" { |
655 | + continue |
656 | + } |
657 | + instancesIds[i] = instanceId{ |
658 | + serviceName: serviceName, |
659 | + roleName: roleName, |
660 | + } |
661 | + serviceNames.Add(serviceName) |
662 | } |
663 | - request := &gwacl.ListSpecificHostedServicesRequest{ServiceNames: serviceNames} |
664 | |
665 | - // Issue 'ListSpecificHostedServices' request with gwacl. |
666 | - services, err := context.ListSpecificHostedServices(request) |
667 | + // Map service names to gwacl.HostedServices. |
668 | + services, err := context.ListSpecificHostedServices(&gwacl.ListSpecificHostedServicesRequest{ |
669 | + ServiceNames: serviceNames.Values(), |
670 | + }) |
671 | if err != nil { |
672 | return nil, err |
673 | } |
674 | - |
675 | - // If no instances were found, return ErrNoInstances. |
676 | if len(services) == 0 { |
677 | return nil, environs.ErrNoInstances |
678 | } |
679 | - |
680 | - instances := convertToInstances(services, env) |
681 | - |
682 | - // Check if we got a partial result. |
683 | - if len(ids) != len(instances) { |
684 | - return instances, environs.ErrPartialInstances |
685 | - } |
686 | - return instances, nil |
687 | + hostedServices := make(map[string]*gwacl.HostedService) |
688 | + for _, s := range services { |
689 | + hostedService, err := context.GetHostedServiceProperties(s.ServiceName, true) |
690 | + if err != nil { |
691 | + return nil, err |
692 | + } |
693 | + hostedServices[s.ServiceName] = hostedService |
694 | + } |
695 | + |
696 | + err = nil |
697 | + instances := make([]instance.Instance, len(ids)) |
698 | + for i, id := range instancesIds { |
699 | + if id.serviceName == "" { |
700 | + // Previously determined to be an invalid instance ID. |
701 | + continue |
702 | + } |
703 | + hostedService := hostedServices[id.serviceName] |
704 | + instance, err := env.getInstance(hostedService, id.roleName) |
705 | + if err == nil { |
706 | + instances[i] = instance |
707 | + } else { |
708 | + logger.Debugf("failed to get instance for role %q in service %q: %v", id.roleName, hostedService.ServiceName, err) |
709 | + } |
710 | + } |
711 | + for _, instance := range instances { |
712 | + if instance == nil { |
713 | + err = environs.ErrPartialInstances |
714 | + } |
715 | + } |
716 | + return instances, err |
717 | } |
718 | |
719 | // AllInstances is specified in the InstanceBroker interface. |
720 | @@ -659,11 +903,29 @@ |
721 | defer env.releaseManagementAPI(context) |
722 | |
723 | request := &gwacl.ListPrefixedHostedServicesRequest{ServiceNamePrefix: env.getEnvPrefix()} |
724 | - services, err := context.ListPrefixedHostedServices(request) |
725 | + serviceDescriptors, err := context.ListPrefixedHostedServices(request) |
726 | if err != nil { |
727 | return nil, err |
728 | } |
729 | - return convertToInstances(services, env), nil |
730 | + |
731 | + var instances []instance.Instance |
732 | + for _, sd := range serviceDescriptors { |
733 | + hostedService, err := context.GetHostedServiceProperties(sd.ServiceName, true) |
734 | + if err != nil { |
735 | + return nil, err |
736 | + } else if len(hostedService.Deployments) != 1 { |
737 | + continue |
738 | + } |
739 | + deployment := &hostedService.Deployments[0] |
740 | + for _, role := range deployment.RoleList { |
741 | + instance, err := env.getInstance(hostedService, role.RoleName) |
742 | + if err != nil { |
743 | + return nil, err |
744 | + } |
745 | + instances = append(instances, instance) |
746 | + } |
747 | + } |
748 | + return instances, nil |
749 | } |
750 | |
751 | // getEnvPrefix returns the prefix used to name the objects specific to this |
752 | @@ -672,16 +934,6 @@ |
753 | return fmt.Sprintf("juju-%s-", env.Name()) |
754 | } |
755 | |
756 | -// convertToInstances converts a slice of gwacl.HostedServiceDescriptor objects |
757 | -// into a slice of instance.Instance objects. |
758 | -func convertToInstances(services []gwacl.HostedServiceDescriptor, env *azureEnviron) []instance.Instance { |
759 | - instances := make([]instance.Instance, len(services)) |
760 | - for i, service := range services { |
761 | - instances[i] = &azureInstance{service, env} |
762 | - } |
763 | - return instances |
764 | -} |
765 | - |
766 | // Storage is specified in the Environ interface. |
767 | func (env *azureEnviron) Storage() storage.Storage { |
768 | return env.getSnapshot().storage |
769 | @@ -692,22 +944,15 @@ |
770 | logger.Debugf("destroying environment %q", env.name) |
771 | |
772 | // Stop all instances. |
773 | - insts, err := env.AllInstances() |
774 | - if err != nil { |
775 | - return fmt.Errorf("cannot get instances: %v", err) |
776 | - } |
777 | - err = env.StopInstances(insts) |
778 | - if err != nil { |
779 | - return fmt.Errorf("cannot stop instances: %v", err) |
780 | + if err := env.destroyAllServices(); err != nil { |
781 | + return fmt.Errorf("cannot destroy instances: %v", err) |
782 | } |
783 | |
784 | // Delete vnet and affinity group. |
785 | - err = env.deleteVirtualNetwork() |
786 | - if err != nil { |
787 | + if err := env.deleteVirtualNetwork(); err != nil { |
788 | return fmt.Errorf("cannot delete the environment's virtual network: %v", err) |
789 | } |
790 | - err = env.deleteAffinityGroup() |
791 | - if err != nil { |
792 | + if err := env.deleteAffinityGroup(); err != nil { |
793 | return fmt.Errorf("cannot delete the environment's affinity group: %v", err) |
794 | } |
795 | |
796 | @@ -716,8 +961,7 @@ |
797 | // half way through the Destroy() method, the storage won't be cleaned |
798 | // up and thus an attempt to re-boostrap the environment will lead to |
799 | // a "error: environment is already bootstrapped" error. |
800 | - err = env.Storage().RemoveAll() |
801 | - if err != nil { |
802 | + if err := env.Storage().RemoveAll(); err != nil { |
803 | return fmt.Errorf("cannot clean up storage: %v", err) |
804 | } |
805 | return nil |
806 | |
807 | === modified file 'provider/azure/environ_test.go' |
808 | --- provider/azure/environ_test.go 2014-03-28 13:27:45 +0000 |
809 | +++ provider/azure/environ_test.go 2014-04-03 07:35:44 +0000 |
810 | @@ -144,7 +144,7 @@ |
811 | // The real test is that this does not panic. |
812 | } |
813 | |
814 | -func getAzureServiceListResponse(c *gc.C, services []gwacl.HostedServiceDescriptor) []gwacl.DispatcherResponse { |
815 | +func getAzureServiceListResponse(c *gc.C, services ...gwacl.HostedServiceDescriptor) []gwacl.DispatcherResponse { |
816 | list := gwacl.HostedServiceDescriptorList{HostedServices: services} |
817 | listXML, err := list.Serialize() |
818 | c.Assert(err, gc.IsNil) |
819 | @@ -156,22 +156,34 @@ |
820 | return responses |
821 | } |
822 | |
823 | -// getAzureServiceResponses returns the slice of responses |
824 | -// (gwacl.DispatcherResponse) which correspond to the API requests used to |
825 | -// get the properties of a Service. |
826 | -func getAzureServiceResponses(c *gc.C, service gwacl.HostedService) []gwacl.DispatcherResponse { |
827 | +// getAzureServiceResponses returns a gwacl.DispatcherResponse corresponding |
828 | +// to the API request used to get the properties of a Service. |
829 | +func getAzureServiceResponse(c *gc.C, service gwacl.HostedService) gwacl.DispatcherResponse { |
830 | serviceXML, err := service.Serialize() |
831 | c.Assert(err, gc.IsNil) |
832 | - responses := []gwacl.DispatcherResponse{gwacl.NewDispatcherResponse( |
833 | - []byte(serviceXML), |
834 | - http.StatusOK, |
835 | - nil, |
836 | - )} |
837 | - return responses |
838 | + return gwacl.NewDispatcherResponse([]byte(serviceXML), http.StatusOK, nil) |
839 | } |
840 | |
841 | func patchWithServiceListResponse(c *gc.C, services []gwacl.HostedServiceDescriptor) *[]*gwacl.X509Request { |
842 | - responses := getAzureServiceListResponse(c, services) |
843 | + responses := getAzureServiceListResponse(c, services...) |
844 | + return gwacl.PatchManagementAPIResponses(responses) |
845 | +} |
846 | + |
847 | +func patchInstancesResponses(c *gc.C, prefix string, services ...*gwacl.HostedService) *[]*gwacl.X509Request { |
848 | + descriptors := make([]gwacl.HostedServiceDescriptor, len(services)) |
849 | + for i, service := range services { |
850 | + descriptors[i] = service.HostedServiceDescriptor |
851 | + } |
852 | + responses := getAzureServiceListResponse(c, descriptors...) |
853 | + for _, service := range services { |
854 | + if !strings.HasPrefix(service.ServiceName, prefix) { |
855 | + continue |
856 | + } |
857 | + serviceXML, err := service.Serialize() |
858 | + c.Assert(err, gc.IsNil) |
859 | + serviceGetResponse := gwacl.NewDispatcherResponse([]byte(serviceXML), http.StatusOK, nil) |
860 | + responses = append(responses, serviceGetResponse) |
861 | + } |
862 | return gwacl.PatchManagementAPIResponses(responses) |
863 | } |
864 | |
865 | @@ -201,25 +213,34 @@ |
866 | func (suite *environSuite) TestAllInstances(c *gc.C) { |
867 | env := makeEnviron(c) |
868 | prefix := env.getEnvPrefix() |
869 | - services := []gwacl.HostedServiceDescriptor{{ServiceName: "deployment-in-another-env"}, {ServiceName: prefix + "deployment-1"}, {ServiceName: prefix + "deployment-2"}} |
870 | - requests := patchWithServiceListResponse(c, services) |
871 | + service1 := makeLegacyDeployment(env, prefix+"service1") |
872 | + service2 := makeDeployment(env, prefix+"service2") |
873 | + service3 := makeDeployment(env, "not"+prefix+"service3") |
874 | + |
875 | + requests := patchInstancesResponses(c, prefix, service1, service2, service3) |
876 | instances, err := env.AllInstances() |
877 | c.Assert(err, gc.IsNil) |
878 | - c.Check(len(instances), gc.Equals, 2) |
879 | - c.Check(instances[0].Id(), gc.Equals, instance.Id(prefix+"deployment-1")) |
880 | - c.Check(instances[1].Id(), gc.Equals, instance.Id(prefix+"deployment-2")) |
881 | - c.Check(len(*requests), gc.Equals, 1) |
882 | + c.Check(len(instances), gc.Equals, 3) |
883 | + c.Check(instances[0].Id(), gc.Equals, instance.Id(prefix+"service1")) |
884 | + service2Role1Name := service2.Deployments[0].RoleList[0].RoleName |
885 | + service2Role2Name := service2.Deployments[0].RoleList[1].RoleName |
886 | + c.Check(instances[1].Id(), gc.Equals, instance.Id(prefix+"service2-"+service2Role1Name)) |
887 | + c.Check(instances[2].Id(), gc.Equals, instance.Id(prefix+"service2-"+service2Role2Name)) |
888 | + c.Check(len(*requests), gc.Equals, 3) |
889 | } |
890 | |
891 | func (suite *environSuite) TestInstancesReturnsFilteredList(c *gc.C) { |
892 | - services := []gwacl.HostedServiceDescriptor{{ServiceName: "deployment-1"}, {ServiceName: "deployment-2"}} |
893 | - requests := patchWithServiceListResponse(c, services) |
894 | env := makeEnviron(c) |
895 | - instances, err := env.Instances([]instance.Id{"deployment-1"}) |
896 | + prefix := env.getEnvPrefix() |
897 | + service := makeDeployment(env, prefix+"service") |
898 | + requests := patchInstancesResponses(c, prefix, service) |
899 | + role1Name := service.Deployments[0].RoleList[0].RoleName |
900 | + instId := instance.Id(prefix + "service-" + role1Name) |
901 | + instances, err := env.Instances([]instance.Id{instId}) |
902 | c.Assert(err, gc.IsNil) |
903 | c.Check(len(instances), gc.Equals, 1) |
904 | - c.Check(instances[0].Id(), gc.Equals, instance.Id("deployment-1")) |
905 | - c.Check(len(*requests), gc.Equals, 1) |
906 | + c.Check(instances[0].Id(), gc.Equals, instId) |
907 | + c.Check(len(*requests), gc.Equals, 2) |
908 | } |
909 | |
910 | func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoInstancesRequested(c *gc.C) { |
911 | @@ -241,14 +262,22 @@ |
912 | } |
913 | |
914 | func (suite *environSuite) TestInstancesReturnsPartialInstancesIfSomeInstancesAreNotFound(c *gc.C) { |
915 | - services := []gwacl.HostedServiceDescriptor{{ServiceName: "deployment-1"}, {ServiceName: "deployment-2"}} |
916 | - requests := patchWithServiceListResponse(c, services) |
917 | env := makeEnviron(c) |
918 | - instances, err := env.Instances([]instance.Id{"deployment-1", "unknown-deployment"}) |
919 | + prefix := env.getEnvPrefix() |
920 | + service := makeDeployment(env, prefix+"service") |
921 | + |
922 | + role1Name := service.Deployments[0].RoleList[0].RoleName |
923 | + role2Name := service.Deployments[0].RoleList[1].RoleName |
924 | + inst1Id := instance.Id(prefix + "service-" + role1Name) |
925 | + inst2Id := instance.Id(prefix + "service-" + role2Name) |
926 | + patchInstancesResponses(c, prefix, service) |
927 | + |
928 | + instances, err := env.Instances([]instance.Id{inst1Id, "unknown", inst2Id}) |
929 | c.Assert(err, gc.Equals, environs.ErrPartialInstances) |
930 | - c.Check(len(instances), gc.Equals, 1) |
931 | - c.Check(instances[0].Id(), gc.Equals, instance.Id("deployment-1")) |
932 | - c.Check(len(*requests), gc.Equals, 1) |
933 | + c.Check(len(instances), gc.Equals, 3) |
934 | + c.Check(instances[0].Id(), gc.Equals, inst1Id) |
935 | + c.Check(instances[1], gc.IsNil) |
936 | + c.Check(instances[2].Id(), gc.Equals, inst2Id) |
937 | } |
938 | |
939 | func (*environSuite) TestStorage(c *gc.C) { |
940 | @@ -470,22 +499,23 @@ |
941 | } |
942 | |
943 | func (s *environSuite) TestStateInfo(c *gc.C) { |
944 | - instanceID := "my-instance" |
945 | - patchWithServiceListResponse(c, []gwacl.HostedServiceDescriptor{{ |
946 | - ServiceName: instanceID, |
947 | - }}) |
948 | env := makeEnviron(c) |
949 | s.setDummyStorage(c, env) |
950 | + prefix := env.getEnvPrefix() |
951 | + |
952 | + service := makeDeployment(env, prefix+"myservice") |
953 | + instId := instance.Id(service.ServiceName + "-" + service.Deployments[0].RoleList[0].RoleName) |
954 | + patchInstancesResponses(c, prefix, service) |
955 | err := bootstrap.SaveState( |
956 | env.Storage(), |
957 | - &bootstrap.BootstrapState{StateInstances: []instance.Id{instance.Id(instanceID)}}) |
958 | + &bootstrap.BootstrapState{StateInstances: []instance.Id{instId}}, |
959 | + ) |
960 | c.Assert(err, gc.IsNil) |
961 | |
962 | stateInfo, apiInfo, err := env.StateInfo() |
963 | c.Assert(err, gc.IsNil) |
964 | - |
965 | config := env.Config() |
966 | - dnsName := "my-instance." + AZURE_DOMAIN_NAME |
967 | + dnsName := prefix + "myservice." + AZURE_DOMAIN_NAME |
968 | stateServerAddr := fmt.Sprintf("%s:%d", dnsName, config.StatePort()) |
969 | apiServerAddr := fmt.Sprintf("%s:%d", dnsName, config.APIPort()) |
970 | c.Check(stateInfo.Addrs, gc.DeepEquals, []string{stateServerAddr}) |
971 | @@ -502,6 +532,14 @@ |
972 | return &body |
973 | } |
974 | |
975 | +// getHostedServicePropertiesServiceName extracts the service name parameter |
976 | +// from the GetHostedServiceProperties request URL. |
977 | +func getHostedServicePropertiesServiceName(c *gc.C, request *gwacl.X509Request) string { |
978 | + url, err := url.Parse(request.URL) |
979 | + c.Assert(err, gc.IsNil) |
980 | + return path.Base(url.Path) |
981 | +} |
982 | + |
983 | // makeNonAvailabilityResponse simulates a reply to the |
984 | // CheckHostedServiceNameAvailability call saying that a name is not available. |
985 | func makeNonAvailabilityResponse(c *gc.C) []byte { |
986 | @@ -524,7 +562,7 @@ |
987 | func (*environSuite) TestAttemptCreateServiceCreatesService(c *gc.C) { |
988 | prefix := "myservice" |
989 | affinityGroup := "affinity-group" |
990 | - location := "location" |
991 | + |
992 | responses := []gwacl.DispatcherResponse{ |
993 | gwacl.NewDispatcherResponse(makeAvailabilityResponse(c), http.StatusOK, nil), |
994 | gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), |
995 | @@ -533,7 +571,7 @@ |
996 | azure, err := gwacl.NewManagementAPI("subscription", "", "West US") |
997 | c.Assert(err, gc.IsNil) |
998 | |
999 | - service, err := attemptCreateService(azure, prefix, affinityGroup, location) |
1000 | + service, err := attemptCreateService(azure, prefix, affinityGroup, "") |
1001 | c.Assert(err, gc.IsNil) |
1002 | |
1003 | c.Assert(*requests, gc.HasLen, 2) |
1004 | @@ -541,11 +579,8 @@ |
1005 | c.Check(body.ServiceName, gc.Equals, service.ServiceName) |
1006 | c.Check(body.AffinityGroup, gc.Equals, affinityGroup) |
1007 | c.Check(service.ServiceName, gc.Matches, prefix+".*") |
1008 | - c.Check(service.Location, gc.Equals, location) |
1009 | - |
1010 | - label, err := base64.StdEncoding.DecodeString(service.Label) |
1011 | - c.Assert(err, gc.IsNil) |
1012 | - c.Check(string(label), gc.Equals, service.ServiceName) |
1013 | + // We specify AffinityGroup, so Location should be empty. |
1014 | + c.Check(service.Location, gc.Equals, "") |
1015 | } |
1016 | |
1017 | func (*environSuite) TestAttemptCreateServiceReturnsNilIfNameNotUnique(c *gc.C) { |
1018 | @@ -556,7 +591,7 @@ |
1019 | azure, err := gwacl.NewManagementAPI("subscription", "", "West US") |
1020 | c.Assert(err, gc.IsNil) |
1021 | |
1022 | - service, err := attemptCreateService(azure, "service", "affinity-group", "location") |
1023 | + service, err := attemptCreateService(azure, "service", "affinity-group", "") |
1024 | c.Check(err, gc.IsNil) |
1025 | c.Check(service, gc.IsNil) |
1026 | } |
1027 | @@ -570,7 +605,7 @@ |
1028 | azure, err := gwacl.NewManagementAPI("subscription", "", "West US") |
1029 | c.Assert(err, gc.IsNil) |
1030 | |
1031 | - _, err = attemptCreateService(azure, "service", "affinity-group", "location") |
1032 | + _, err = attemptCreateService(azure, "service", "affinity-group", "") |
1033 | c.Assert(err, gc.NotNil) |
1034 | c.Check(err, gc.ErrorMatches, ".*Not Found.*") |
1035 | } |
1036 | @@ -578,24 +613,30 @@ |
1037 | func (*environSuite) TestNewHostedServiceCreatesService(c *gc.C) { |
1038 | prefix := "myservice" |
1039 | affinityGroup := "affinity-group" |
1040 | - location := "location" |
1041 | responses := []gwacl.DispatcherResponse{ |
1042 | gwacl.NewDispatcherResponse(makeAvailabilityResponse(c), http.StatusOK, nil), |
1043 | gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), |
1044 | + getAzureServiceResponse(c, gwacl.HostedService{ |
1045 | + HostedServiceDescriptor: gwacl.HostedServiceDescriptor{ |
1046 | + ServiceName: "anything", |
1047 | + }, |
1048 | + }), |
1049 | } |
1050 | requests := gwacl.PatchManagementAPIResponses(responses) |
1051 | azure, err := gwacl.NewManagementAPI("subscription", "", "West US") |
1052 | c.Assert(err, gc.IsNil) |
1053 | |
1054 | - service, err := newHostedService(azure, prefix, affinityGroup, location) |
1055 | + service, err := newHostedService(azure, prefix, affinityGroup, "") |
1056 | c.Assert(err, gc.IsNil) |
1057 | |
1058 | - c.Assert(*requests, gc.HasLen, 2) |
1059 | + c.Assert(*requests, gc.HasLen, 3) |
1060 | body := parseCreateServiceRequest(c, (*requests)[1]) |
1061 | - c.Check(body.ServiceName, gc.Equals, service.ServiceName) |
1062 | + requestedServiceName := getHostedServicePropertiesServiceName(c, (*requests)[2]) |
1063 | + c.Check(body.ServiceName, gc.Matches, prefix+".*") |
1064 | + c.Check(body.ServiceName, gc.Equals, requestedServiceName) |
1065 | c.Check(body.AffinityGroup, gc.Equals, affinityGroup) |
1066 | - c.Check(service.ServiceName, gc.Matches, prefix+".*") |
1067 | - c.Check(service.Location, gc.Equals, location) |
1068 | + c.Check(service.ServiceName, gc.Equals, "anything") |
1069 | + c.Check(service.Location, gc.Equals, "") |
1070 | } |
1071 | |
1072 | func (*environSuite) TestNewHostedServiceRetriesIfNotUnique(c *gc.C) { |
1073 | @@ -606,17 +647,22 @@ |
1074 | responses := []gwacl.DispatcherResponse{ |
1075 | gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil), |
1076 | gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil), |
1077 | - gwacl.NewDispatcherResponse(okBody, http.StatusOK, nil), |
1078 | - gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), |
1079 | + gwacl.NewDispatcherResponse(okBody, http.StatusOK, nil), // name is unique |
1080 | + gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), // create service |
1081 | + getAzureServiceResponse(c, gwacl.HostedService{ |
1082 | + HostedServiceDescriptor: gwacl.HostedServiceDescriptor{ |
1083 | + ServiceName: "anything", |
1084 | + }, |
1085 | + }), |
1086 | } |
1087 | requests := gwacl.PatchManagementAPIResponses(responses) |
1088 | azure, err := gwacl.NewManagementAPI("subscription", "", "West US") |
1089 | c.Assert(err, gc.IsNil) |
1090 | |
1091 | - service, err := newHostedService(azure, "service", "affinity-group", "location") |
1092 | + service, err := newHostedService(azure, "service", "affinity-group", "") |
1093 | c.Check(err, gc.IsNil) |
1094 | |
1095 | - c.Assert(*requests, gc.HasLen, 4) |
1096 | + c.Assert(*requests, gc.HasLen, 5) |
1097 | // How many names have been attempted, and how often? |
1098 | // There is a minute chance that this tries the same name twice, and |
1099 | // then this test will fail. If that happens, try seeding the |
1100 | @@ -637,11 +683,8 @@ |
1101 | c.Check(attemptedNames, gc.HasLen, 3) |
1102 | |
1103 | // Once newHostedService succeeds, we get a hosted service with the |
1104 | - // last requested name. |
1105 | - c.Check( |
1106 | - service.ServiceName, |
1107 | - gc.Equals, |
1108 | - parseCreateServiceRequest(c, (*requests)[3]).ServiceName) |
1109 | + // name returned from GetHostedServiceProperties. |
1110 | + c.Check(service.ServiceName, gc.Equals, "anything") |
1111 | } |
1112 | |
1113 | func (*environSuite) TestNewHostedServiceFailsIfUnableToFindUniqueName(c *gc.C) { |
1114 | @@ -654,128 +697,142 @@ |
1115 | azure, err := gwacl.NewManagementAPI("subscription", "", "West US") |
1116 | c.Assert(err, gc.IsNil) |
1117 | |
1118 | - _, err = newHostedService(azure, "service", "affinity-group", "location") |
1119 | + _, err = newHostedService(azure, "service", "affinity-group", "") |
1120 | c.Assert(err, gc.NotNil) |
1121 | c.Check(err, gc.ErrorMatches, "could not come up with a unique hosted service name.*") |
1122 | } |
1123 | |
1124 | -// buildDestroyAzureServiceResponses returns a slice containing the responses that a fake Azure server |
1125 | -// can use to simulate the deletion of the given list of services. |
1126 | -func buildDestroyAzureServiceResponses(c *gc.C, services []*gwacl.HostedService) []gwacl.DispatcherResponse { |
1127 | - responses := []gwacl.DispatcherResponse{} |
1128 | - for _, service := range services { |
1129 | - // When destroying a hosted service, gwacl first issues a Get request |
1130 | - // to fetch the properties of the services. Then it destroys all the |
1131 | - // deployments found in this service (none in this case, we make sure |
1132 | - // the service does not contain deployments to keep the testing simple) |
1133 | - // And it finally deletes the service itself. |
1134 | - if len(service.Deployments) != 0 { |
1135 | - panic("buildDestroyAzureServiceResponses does not support services with deployments!") |
1136 | - } |
1137 | +func buildGetServicePropertiesResponses(c *gc.C, services ...*gwacl.HostedService) []gwacl.DispatcherResponse { |
1138 | + responses := make([]gwacl.DispatcherResponse, len(services)) |
1139 | + for i, service := range services { |
1140 | serviceXML, err := service.Serialize() |
1141 | c.Assert(err, gc.IsNil) |
1142 | - serviceGetResponse := gwacl.NewDispatcherResponse( |
1143 | - []byte(serviceXML), |
1144 | - http.StatusOK, |
1145 | - nil, |
1146 | - ) |
1147 | - responses = append(responses, serviceGetResponse) |
1148 | - serviceDeleteResponse := gwacl.NewDispatcherResponse( |
1149 | - nil, |
1150 | - http.StatusOK, |
1151 | - nil, |
1152 | - ) |
1153 | - responses = append(responses, serviceDeleteResponse) |
1154 | - } |
1155 | - return responses |
1156 | -} |
1157 | - |
1158 | -func makeAzureService(name string) (*gwacl.HostedService, *gwacl.HostedServiceDescriptor) { |
1159 | - service1Desc := &gwacl.HostedServiceDescriptor{ServiceName: name} |
1160 | - service1 := &gwacl.HostedService{HostedServiceDescriptor: *service1Desc} |
1161 | - return service1, service1Desc |
1162 | -} |
1163 | - |
1164 | -func (s *environSuite) setServiceDeletionConcurrency(nbGoroutines int) { |
1165 | - s.PatchValue(&maxConcurrentDeletes, nbGoroutines) |
1166 | + responses[i] = gwacl.NewDispatcherResponse([]byte(serviceXML), http.StatusOK, nil) |
1167 | + } |
1168 | + return responses |
1169 | +} |
1170 | + |
1171 | +func buildStatusOKResponses(c *gc.C, n int) []gwacl.DispatcherResponse { |
1172 | + responses := make([]gwacl.DispatcherResponse, n) |
1173 | + for i := range responses { |
1174 | + responses[i] = gwacl.NewDispatcherResponse(nil, http.StatusOK, nil) |
1175 | + } |
1176 | + return responses |
1177 | +} |
1178 | + |
1179 | +func makeAzureService(name string) *gwacl.HostedService { |
1180 | + return &gwacl.HostedService{ |
1181 | + HostedServiceDescriptor: gwacl.HostedServiceDescriptor{ServiceName: name}, |
1182 | + } |
1183 | +} |
1184 | + |
1185 | +func makeRole(env *azureEnviron) *gwacl.Role { |
1186 | + size := "Large" |
1187 | + vhd := env.newOSDisk("source-image-name") |
1188 | + userData := "example-user-data" |
1189 | + return env.newRole(size, vhd, userData, false) |
1190 | +} |
1191 | + |
1192 | +func makeLegacyDeployment(env *azureEnviron, serviceName string) *gwacl.HostedService { |
1193 | + service := makeAzureService(serviceName) |
1194 | + service.Deployments = []gwacl.Deployment{{ |
1195 | + Name: serviceName, |
1196 | + RoleList: []gwacl.Role{*makeRole(env)}, |
1197 | + }} |
1198 | + return service |
1199 | +} |
1200 | + |
1201 | +func makeDeployment(env *azureEnviron, serviceName string) *gwacl.HostedService { |
1202 | + service := makeAzureService(serviceName) |
1203 | + service.Deployments = []gwacl.Deployment{{ |
1204 | + Name: serviceName + "-v2", |
1205 | + RoleList: []gwacl.Role{*makeRole(env), *makeRole(env)}, |
1206 | + }} |
1207 | + return service |
1208 | } |
1209 | |
1210 | func (s *environSuite) TestStopInstancesDestroysMachines(c *gc.C) { |
1211 | - s.setServiceDeletionConcurrency(3) |
1212 | + env := makeEnviron(c) |
1213 | service1Name := "service1" |
1214 | - service1, service1Desc := makeAzureService(service1Name) |
1215 | + service1 := makeLegacyDeployment(env, service1Name) |
1216 | service2Name := "service2" |
1217 | - service2, service2Desc := makeAzureService(service2Name) |
1218 | - services := []*gwacl.HostedService{service1, service2} |
1219 | - responses := buildDestroyAzureServiceResponses(c, services) |
1220 | + service2 := makeDeployment(env, service2Name) |
1221 | + |
1222 | + inst1, err := env.getInstance(service1, "") |
1223 | + c.Assert(err, gc.IsNil) |
1224 | + role2Name := service2.Deployments[0].RoleList[0].RoleName |
1225 | + inst2, err := env.getInstance(service2, role2Name) |
1226 | + c.Assert(err, gc.IsNil) |
1227 | + role3Name := service2.Deployments[0].RoleList[1].RoleName |
1228 | + inst3, err := env.getInstance(service2, role3Name) |
1229 | + c.Assert(err, gc.IsNil) |
1230 | + |
1231 | + responses := buildGetServicePropertiesResponses(c, service1) |
1232 | + responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteHostedService |
1233 | + responses = append(responses, buildGetServicePropertiesResponses(c, service2)...) |
1234 | + responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteHostedService |
1235 | requests := gwacl.PatchManagementAPIResponses(responses) |
1236 | - env := makeEnviron(c) |
1237 | - instances := convertToInstances( |
1238 | - []gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc}, |
1239 | - env) |
1240 | - |
1241 | - err := env.StopInstances(instances) |
1242 | + err = env.StopInstances([]instance.Instance{inst1, inst2, inst3}) |
1243 | c.Check(err, gc.IsNil) |
1244 | |
1245 | - // It takes 2 API calls to delete each service: |
1246 | - // - one GET request to fetch the service's properties; |
1247 | - // - one DELETE request to delete the service. |
1248 | - c.Check(len(*requests), gc.Equals, len(services)*2) |
1249 | - assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".*") |
1250 | + // One GET and DELETE per service |
1251 | + // (GetHostedServiceProperties and DeleteHostedService). |
1252 | + c.Check(len(*requests), gc.Equals, len(responses)) |
1253 | + assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".") |
1254 | + assertOneRequestMatches(c, *requests, "GET", ".*"+service2Name+".*") |
1255 | assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1Name+".*") |
1256 | - assertOneRequestMatches(c, *requests, "GET", ".*"+service2Name+".") |
1257 | assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2Name+".*") |
1258 | } |
1259 | |
1260 | +func (s *environSuite) TestStopInstancesServiceSubset(c *gc.C) { |
1261 | + env := makeEnviron(c) |
1262 | + service := makeDeployment(env, "service") |
1263 | + |
1264 | + role1Name := service.Deployments[0].RoleList[0].RoleName |
1265 | + inst1, err := env.getInstance(service, role1Name) |
1266 | + c.Assert(err, gc.IsNil) |
1267 | + |
1268 | + responses := buildGetServicePropertiesResponses(c, service) |
1269 | + responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteRole |
1270 | + requests := gwacl.PatchManagementAPIResponses(responses) |
1271 | + err = env.StopInstances([]instance.Instance{inst1}) |
1272 | + c.Check(err, gc.IsNil) |
1273 | + |
1274 | + // One GET for the service, and one DELETE for the role. |
1275 | + // The service isn't deleted because it has two roles, |
1276 | + // and only one is being deleted. |
1277 | + c.Check(len(*requests), gc.Equals, len(responses)) |
1278 | + assertOneRequestMatches(c, *requests, "GET", ".*"+service.ServiceName+".") |
1279 | + assertOneRequestMatches(c, *requests, "DELETE", ".*"+role1Name+".*") |
1280 | +} |
1281 | + |
1282 | func (s *environSuite) TestStopInstancesWhenStoppingMachinesFails(c *gc.C) { |
1283 | - s.setServiceDeletionConcurrency(3) |
1284 | - responses := []gwacl.DispatcherResponse{ |
1285 | - gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil), |
1286 | - } |
1287 | - service1Name := "service1" |
1288 | - _, service1Desc := makeAzureService(service1Name) |
1289 | - service2Name := "service2" |
1290 | - service2, service2Desc := makeAzureService(service2Name) |
1291 | - services := []*gwacl.HostedService{service2} |
1292 | - destroyResponses := buildDestroyAzureServiceResponses(c, services) |
1293 | - responses = append(responses, destroyResponses...) |
1294 | + env := makeEnviron(c) |
1295 | + service1 := makeDeployment(env, "service1") |
1296 | + service2 := makeDeployment(env, "service2") |
1297 | + service1Role1Name := service1.Deployments[0].RoleList[0].RoleName |
1298 | + inst1, err := env.getInstance(service1, service1Role1Name) |
1299 | + c.Assert(err, gc.IsNil) |
1300 | + service2Role1Name := service2.Deployments[0].RoleList[0].RoleName |
1301 | + inst2, err := env.getInstance(service2, service2Role1Name) |
1302 | + c.Assert(err, gc.IsNil) |
1303 | + |
1304 | + responses := buildGetServicePropertiesResponses(c, service1, service2) |
1305 | + // Failed to delete one of the services. |
1306 | + responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil)) |
1307 | requests := gwacl.PatchManagementAPIResponses(responses) |
1308 | - env := makeEnviron(c) |
1309 | - instances := convertToInstances( |
1310 | - []gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc}, env) |
1311 | |
1312 | - err := env.StopInstances(instances) |
1313 | + instances := []instance.Instance{inst1, inst2} |
1314 | + err = env.StopInstances(instances) |
1315 | c.Check(err, gc.ErrorMatches, ".*Conflict.*") |
1316 | |
1317 | - c.Check(len(*requests), gc.Equals, 3) |
1318 | - assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".") |
1319 | - assertOneRequestMatches(c, *requests, "GET", ".*"+service2Name+".") |
1320 | - // Only one of the services was deleted. |
1321 | - assertOneRequestMatches(c, *requests, "DELETE", ".*") |
1322 | -} |
1323 | - |
1324 | -func (s *environSuite) TestStopInstancesWithLimitedConcurrency(c *gc.C) { |
1325 | - s.setServiceDeletionConcurrency(3) |
1326 | - services := []*gwacl.HostedService{} |
1327 | - serviceDescs := []gwacl.HostedServiceDescriptor{} |
1328 | - for i := 0; i < 10; i++ { |
1329 | - serviceName := fmt.Sprintf("service%d", i) |
1330 | - service, serviceDesc := makeAzureService(serviceName) |
1331 | - services = append(services, service) |
1332 | - serviceDescs = append(serviceDescs, *serviceDesc) |
1333 | - } |
1334 | - responses := buildDestroyAzureServiceResponses(c, services) |
1335 | - requests := gwacl.PatchManagementAPIResponses(responses) |
1336 | - env := makeEnviron(c) |
1337 | - instances := convertToInstances(serviceDescs, env) |
1338 | - |
1339 | - err := env.StopInstances(instances) |
1340 | - c.Check(err, gc.IsNil) |
1341 | - c.Check(len(*requests), gc.Equals, len(services)*2) |
1342 | + c.Check(len(*requests), gc.Equals, len(responses)) |
1343 | + assertOneRequestMatches(c, *requests, "GET", ".*"+service1.ServiceName+".*") |
1344 | + assertOneRequestMatches(c, *requests, "GET", ".*"+service2.ServiceName+".*") |
1345 | + assertOneRequestMatches(c, *requests, "DELETE", ".*("+service1.ServiceName+"|"+service2.ServiceName+").") |
1346 | } |
1347 | |
1348 | func (s *environSuite) TestStopInstancesWithZeroInstance(c *gc.C) { |
1349 | - s.setServiceDeletionConcurrency(3) |
1350 | env := makeEnviron(c) |
1351 | instances := []instance.Instance{} |
1352 | |
1353 | @@ -832,8 +889,7 @@ |
1354 | env.Storage(), |
1355 | &bootstrap.BootstrapState{StateInstances: []instance.Id{instance.Id("test-id")}}) |
1356 | c.Assert(err, gc.IsNil) |
1357 | - services := []gwacl.HostedServiceDescriptor{} |
1358 | - responses := getAzureServiceListResponse(c, services) |
1359 | + responses := getAzureServiceListResponse(c) |
1360 | cleanupResponses := getVnetAndAffinityGroupCleanupResponses(c) |
1361 | responses = append(responses, cleanupResponses...) |
1362 | gwacl.PatchManagementAPIResponses(responses) |
1363 | @@ -849,8 +905,7 @@ |
1364 | func (s *environSuite) TestDestroyDeletesVirtualNetworkAndAffinityGroup(c *gc.C) { |
1365 | env := makeEnviron(c) |
1366 | s.setDummyStorage(c, env) |
1367 | - services := []gwacl.HostedServiceDescriptor{} |
1368 | - responses := getAzureServiceListResponse(c, services) |
1369 | + responses := getAzureServiceListResponse(c) |
1370 | // Prepare a configuration with a single virtual network. |
1371 | existingConfig := &gwacl.NetworkConfiguration{ |
1372 | XMLNS: gwacl.XMLNS_NC, |
1373 | @@ -914,52 +969,98 @@ |
1374 | } |
1375 | |
1376 | func (s *environSuite) TestDestroyStopsAllInstances(c *gc.C) { |
1377 | - s.setServiceDeletionConcurrency(3) |
1378 | env := makeEnviron(c) |
1379 | s.setDummyStorage(c, env) |
1380 | + prefix := env.getEnvPrefix() |
1381 | + service1 := makeDeployment(env, prefix+"service1") |
1382 | + service2 := makeDeployment(env, prefix+"service2") |
1383 | |
1384 | - // Simulate 2 instances corresponding to two Azure services. |
1385 | - prefix := env.getEnvPrefix() |
1386 | - service1Name := prefix + "service1" |
1387 | - service1, service1Desc := makeAzureService(service1Name) |
1388 | - services := []*gwacl.HostedService{service1} |
1389 | // The call to AllInstances() will return only one service (service1). |
1390 | - listInstancesResponses := getAzureServiceListResponse(c, []gwacl.HostedServiceDescriptor{*service1Desc}) |
1391 | - destroyResponses := buildDestroyAzureServiceResponses(c, services) |
1392 | - responses := append(listInstancesResponses, destroyResponses...) |
1393 | - cleanupResponses := getVnetAndAffinityGroupCleanupResponses(c) |
1394 | - responses = append(responses, cleanupResponses...) |
1395 | + responses := getAzureServiceListResponse(c, service1.HostedServiceDescriptor, service2.HostedServiceDescriptor) |
1396 | + responses = append(responses, buildStatusOKResponses(c, 2)...) // DeleteHostedService |
1397 | + responses = append(responses, getVnetAndAffinityGroupCleanupResponses(c)...) |
1398 | requests := gwacl.PatchManagementAPIResponses(responses) |
1399 | |
1400 | err := env.Destroy() |
1401 | c.Check(err, gc.IsNil) |
1402 | |
1403 | // One request to get the list of all the environment's instances. |
1404 | - // Then two requests per destroyed machine (one to fetch the |
1405 | - // service's information, one to delete it) and two requests to delete |
1406 | - // the Virtual Network and the Affinity Group. |
1407 | - c.Check((*requests), gc.HasLen, 1+len(services)*2+2) |
1408 | + // One delete request per destroyed service, and two additional |
1409 | + // requests to delete the Virtual Network and the Affinity Group. |
1410 | + c.Check((*requests), gc.HasLen, 5) |
1411 | c.Check((*requests)[0].Method, gc.Equals, "GET") |
1412 | - assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".*") |
1413 | - assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1Name+".*") |
1414 | -} |
1415 | - |
1416 | -func (*environSuite) TestGetInstance(c *gc.C) { |
1417 | - env := makeEnviron(c) |
1418 | - prefix := env.getEnvPrefix() |
1419 | - serviceName := prefix + "instance-name" |
1420 | - serviceDesc := gwacl.HostedServiceDescriptor{ServiceName: serviceName} |
1421 | - service := gwacl.HostedService{HostedServiceDescriptor: serviceDesc} |
1422 | - responses := getAzureServiceResponses(c, service) |
1423 | - gwacl.PatchManagementAPIResponses(responses) |
1424 | - |
1425 | - instance, err := env.getInstance("serviceName") |
1426 | - c.Check(err, gc.IsNil) |
1427 | - |
1428 | - c.Check(string(instance.Id()), gc.Equals, serviceName) |
1429 | - c.Check(instance, gc.FitsTypeOf, &azureInstance{}) |
1430 | - azInstance := instance.(*azureInstance) |
1431 | - c.Check(azInstance.environ, gc.Equals, env) |
1432 | + assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1.ServiceName+".*") |
1433 | + assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2.ServiceName+".*") |
1434 | +} |
1435 | + |
1436 | +func (s *environSuite) TestGetInstance(c *gc.C) { |
1437 | + env := makeEnviron(c) |
1438 | + service1 := makeLegacyDeployment(env, "service1") |
1439 | + service2 := makeDeployment(env, "service1") |
1440 | + |
1441 | + // azureEnviron.Instances will call getInstance with roleName=="" |
1442 | + // for legacy instances. This will cause getInstance to get the |
1443 | + // one and only role (or error if there is more than one). |
1444 | + inst1, err := env.getInstance(service1, "") |
1445 | + c.Assert(err, gc.IsNil) |
1446 | + c.Check(inst1.Id(), gc.Equals, instance.Id("service1")) |
1447 | + c.Assert(inst1, gc.FitsTypeOf, &azureInstance{}) |
1448 | + c.Check(inst1.(*azureInstance).environ, gc.Equals, env) |
1449 | + c.Check(inst1.(*azureInstance).roleName, gc.Equals, service1.Deployments[0].RoleList[0].RoleName) |
1450 | + service1.Deployments[0].RoleList = service2.Deployments[0].RoleList |
1451 | + inst1, err = env.getInstance(service1, "") |
1452 | + c.Check(err, gc.ErrorMatches, `expected one role for "service1", got 2`) |
1453 | + |
1454 | + inst2, err := env.getInstance(service2, service2.Deployments[0].RoleList[0].RoleName) |
1455 | + c.Assert(err, gc.IsNil) |
1456 | + c.Check(inst2.Id(), gc.Equals, instance.Id("service1-"+service2.Deployments[0].RoleList[0].RoleName)) |
1457 | +} |
1458 | + |
1459 | +func (s *environSuite) TestInitialPorts(c *gc.C) { |
1460 | + env := makeEnviron(c) |
1461 | + service1 := makeLegacyDeployment(env, "service1") |
1462 | + service2 := makeDeployment(env, "service2") |
1463 | + service3 := makeDeployment(env, "service3") |
1464 | + service3.Label = base64.StdEncoding.EncodeToString([]byte(stateServerLabel)) |
1465 | + |
1466 | + role1 := &service1.Deployments[0].RoleList[0] |
1467 | + inst1, err := env.getInstance(service1, role1.RoleName) |
1468 | + c.Assert(err, gc.IsNil) |
1469 | + c.Assert(inst1.(*azureInstance).maskStateServerPorts, jc.IsTrue) |
1470 | + role2 := &service2.Deployments[0].RoleList[0] |
1471 | + inst2, err := env.getInstance(service2, role2.RoleName) |
1472 | + c.Assert(err, gc.IsNil) |
1473 | + role3 := &service3.Deployments[0].RoleList[0] |
1474 | + inst3, err := env.getInstance(service3, role3.RoleName) |
1475 | + c.Assert(err, gc.IsNil) |
1476 | + |
1477 | + // Only role2 should report opened state server ports via the Ports method. |
1478 | + dummyRole := *role1 |
1479 | + configSetNetwork(&dummyRole).InputEndpoints = &[]gwacl.InputEndpoint{{ |
1480 | + LocalPort: env.Config().StatePort(), |
1481 | + Protocol: "tcp", |
1482 | + Name: "stateserver", |
1483 | + Port: env.Config().StatePort(), |
1484 | + }, { |
1485 | + LocalPort: env.Config().APIPort(), |
1486 | + Protocol: "tcp", |
1487 | + Name: "apiserver", |
1488 | + Port: env.Config().APIPort(), |
1489 | + }} |
1490 | + reportsStateServerPorts := func(inst instance.Instance) bool { |
1491 | + responses := preparePortChangeConversation(c, &dummyRole) |
1492 | + gwacl.PatchManagementAPIResponses(responses) |
1493 | + ports, err := inst.Ports("") |
1494 | + c.Assert(err, gc.IsNil) |
1495 | + portmap := make(map[int]bool) |
1496 | + for _, port := range ports { |
1497 | + portmap[port.Number] = true |
1498 | + } |
1499 | + return portmap[env.Config().StatePort()] && portmap[env.Config().APIPort()] |
1500 | + } |
1501 | + c.Check(inst1, gc.Not(jc.Satisfies), reportsStateServerPorts) |
1502 | + c.Check(inst2, jc.Satisfies, reportsStateServerPorts) |
1503 | + c.Check(inst3, gc.Not(jc.Satisfies), reportsStateServerPorts) |
1504 | } |
1505 | |
1506 | func (*environSuite) TestNewOSVirtualDisk(c *gc.C) { |
1507 | @@ -989,25 +1090,32 @@ |
1508 | return mapping |
1509 | } |
1510 | |
1511 | -func (*environSuite) TestNewRole(c *gc.C) { |
1512 | +func (s *environSuite) TestNewRole(c *gc.C) { |
1513 | + s.testNewRole(c, false) |
1514 | +} |
1515 | + |
1516 | +func (s *environSuite) TestNewRoleStateServer(c *gc.C) { |
1517 | + s.testNewRole(c, true) |
1518 | +} |
1519 | + |
1520 | +func (*environSuite) testNewRole(c *gc.C, stateServer bool) { |
1521 | env := makeEnviron(c) |
1522 | size := "Large" |
1523 | vhd := env.newOSDisk("source-image-name") |
1524 | userData := "example-user-data" |
1525 | - hostname := "hostname" |
1526 | |
1527 | - role := env.newRole(size, vhd, userData, hostname) |
1528 | + role := env.newRole(size, vhd, userData, stateServer) |
1529 | |
1530 | configs := role.ConfigurationSets |
1531 | linuxConfig := configs[0] |
1532 | networkConfig := configs[1] |
1533 | c.Check(linuxConfig.CustomData, gc.Equals, userData) |
1534 | - c.Check(linuxConfig.Hostname, gc.Equals, hostname) |
1535 | + c.Check(linuxConfig.Hostname, gc.Equals, role.RoleName) |
1536 | c.Check(linuxConfig.Username, gc.Not(gc.Equals), "") |
1537 | c.Check(linuxConfig.Password, gc.Not(gc.Equals), "") |
1538 | c.Check(linuxConfig.DisableSSHPasswordAuthentication, gc.Equals, "true") |
1539 | c.Check(role.RoleSize, gc.Equals, size) |
1540 | - c.Check(role.OSVirtualHardDisk[0], gc.Equals, *vhd) |
1541 | + c.Check(role.OSVirtualHardDisk, gc.DeepEquals, vhd) |
1542 | |
1543 | endpoints := mapInputEndpointsByPort(c, *networkConfig.InputEndpoints) |
1544 | |
1545 | @@ -1017,35 +1125,19 @@ |
1546 | c.Check(sshEndpoint.LocalPort, gc.Equals, 22) |
1547 | c.Check(sshEndpoint.Protocol, gc.Equals, "tcp") |
1548 | |
1549 | - // There's also an endpoint for the state (mongodb) port. |
1550 | - // TODO: Ought to have this only for state servers. |
1551 | - stateEndpoint, ok := endpoints[env.Config().StatePort()] |
1552 | - c.Assert(ok, gc.Equals, true) |
1553 | - c.Check(stateEndpoint.LocalPort, gc.Equals, env.Config().StatePort()) |
1554 | - c.Check(stateEndpoint.Protocol, gc.Equals, "tcp") |
1555 | - |
1556 | - // And one for the API port. |
1557 | - // TODO: Ought to have this only for API servers. |
1558 | - apiEndpoint, ok := endpoints[env.Config().APIPort()] |
1559 | - c.Assert(ok, gc.Equals, true) |
1560 | - c.Check(apiEndpoint.LocalPort, gc.Equals, env.Config().APIPort()) |
1561 | - c.Check(apiEndpoint.Protocol, gc.Equals, "tcp") |
1562 | -} |
1563 | - |
1564 | -func (*environSuite) TestNewDeployment(c *gc.C) { |
1565 | - env := makeEnviron(c) |
1566 | - deploymentName := "deployment-name" |
1567 | - deploymentLabel := "deployment-label" |
1568 | - virtualNetworkName := "virtual-network-name" |
1569 | - vhd := env.newOSDisk("source-image-name") |
1570 | - role := env.newRole("Small", vhd, "user-data", "hostname") |
1571 | - |
1572 | - deployment := env.newDeployment(role, deploymentName, deploymentLabel, virtualNetworkName) |
1573 | - |
1574 | - base64Label := base64.StdEncoding.EncodeToString([]byte(deploymentLabel)) |
1575 | - c.Check(deployment.Label, gc.Equals, base64Label) |
1576 | - c.Check(deployment.Name, gc.Equals, deploymentName) |
1577 | - c.Check(deployment.RoleList, gc.HasLen, 1) |
1578 | + if stateServer { |
1579 | + // There's also an endpoint for the state (mongodb) port. |
1580 | + stateEndpoint, ok := endpoints[env.Config().StatePort()] |
1581 | + c.Assert(ok, gc.Equals, true) |
1582 | + c.Check(stateEndpoint.LocalPort, gc.Equals, env.Config().StatePort()) |
1583 | + c.Check(stateEndpoint.Protocol, gc.Equals, "tcp") |
1584 | + |
1585 | + // And one for the API port. |
1586 | + apiEndpoint, ok := endpoints[env.Config().APIPort()] |
1587 | + c.Assert(ok, gc.Equals, true) |
1588 | + c.Check(apiEndpoint.LocalPort, gc.Equals, env.Config().APIPort()) |
1589 | + c.Check(apiEndpoint.Protocol, gc.Equals, "tcp") |
1590 | + } |
1591 | } |
1592 | |
1593 | func (*environSuite) TestProviderReturnsAzureEnvironProvider(c *gc.C) { |
1594 | @@ -1235,18 +1327,6 @@ |
1595 | c.Assert(image, gc.Equals, "image-id") |
1596 | } |
1597 | |
1598 | -func (*environSuite) TestConvertToInstances(c *gc.C) { |
1599 | - services := []gwacl.HostedServiceDescriptor{ |
1600 | - {ServiceName: "foo"}, {ServiceName: "bar"}, |
1601 | - } |
1602 | - env := makeEnviron(c) |
1603 | - instances := convertToInstances(services, env) |
1604 | - c.Check(instances, gc.DeepEquals, []instance.Instance{ |
1605 | - &azureInstance{services[0], env}, |
1606 | - &azureInstance{services[1], env}, |
1607 | - }) |
1608 | -} |
1609 | - |
1610 | func (*environSuite) TestExtractStorageKeyPicksPrimaryKeyIfSet(c *gc.C) { |
1611 | keys := gwacl.StorageAccountKeys{ |
1612 | Primary: "mainkey", |
1613 | |
1614 | === modified file 'provider/azure/instance.go' |
1615 | --- provider/azure/instance.go 2014-01-20 17:18:13 +0000 |
1616 | +++ provider/azure/instance.go 2014-04-03 07:35:44 +0000 |
1617 | @@ -6,18 +6,28 @@ |
1618 | import ( |
1619 | "fmt" |
1620 | "strings" |
1621 | + "sync" |
1622 | |
1623 | "launchpad.net/gwacl" |
1624 | |
1625 | + "launchpad.net/juju-core/errors" |
1626 | "launchpad.net/juju-core/instance" |
1627 | "launchpad.net/juju-core/provider/common" |
1628 | "launchpad.net/juju-core/worker/firewaller" |
1629 | ) |
1630 | |
1631 | +const AZURE_DOMAIN_NAME = "cloudapp.net" |
1632 | + |
1633 | type azureInstance struct { |
1634 | - // An instance contains an Azure Service (instance==service). |
1635 | - gwacl.HostedServiceDescriptor |
1636 | - environ *azureEnviron |
1637 | + environ *azureEnviron |
1638 | + hostedService *gwacl.HostedServiceDescriptor |
1639 | + instanceId instance.Id |
1640 | + deploymentName string |
1641 | + roleName string |
1642 | + maskStateServerPorts bool |
1643 | + |
1644 | + mu sync.Mutex |
1645 | + roleInstance *gwacl.RoleInstance |
1646 | } |
1647 | |
1648 | // azureInstance implements Instance. |
1649 | @@ -25,38 +35,71 @@ |
1650 | |
1651 | // Id is specified in the Instance interface. |
1652 | func (azInstance *azureInstance) Id() instance.Id { |
1653 | - return instance.Id(azInstance.ServiceName) |
1654 | + return azInstance.instanceId |
1655 | +} |
1656 | + |
1657 | +// supportsLoadBalancing returns true iff the instance is |
1658 | +// not a legacy instance where endpoints may have been |
1659 | +// created without load balancing set names associated. |
1660 | +func (azInstance *azureInstance) supportsLoadBalancing() bool { |
1661 | + v1Name := deploymentNameV1(azInstance.hostedService.ServiceName) |
1662 | + return azInstance.deploymentName != v1Name |
1663 | } |
1664 | |
1665 | // Status is specified in the Instance interface. |
1666 | func (azInstance *azureInstance) Status() string { |
1667 | - return azInstance.HostedServiceDescriptor.Status |
1668 | + azInstance.mu.Lock() |
1669 | + defer azInstance.mu.Unlock() |
1670 | + if azInstance.roleInstance == nil { |
1671 | + return "" |
1672 | + } |
1673 | + return azInstance.roleInstance.InstanceStatus |
1674 | } |
1675 | |
1676 | -var AZURE_DOMAIN_NAME = "cloudapp.net" |
1677 | +func (azInstance *azureInstance) serviceName() string { |
1678 | + return azInstance.hostedService.ServiceName |
1679 | +} |
1680 | |
1681 | // Refresh is specified in the Instance interface. |
1682 | func (azInstance *azureInstance) Refresh() error { |
1683 | - // TODO(axw) 2013-12-16 #1261324 |
1684 | - // Cache Addresses/netInfo, refresh here. |
1685 | - return nil |
1686 | + return azInstance.apiCall(false, func(c *azureManagementContext) error { |
1687 | + d, err := c.GetDeployment(&gwacl.GetDeploymentRequest{ |
1688 | + ServiceName: azInstance.serviceName(), |
1689 | + DeploymentName: azInstance.deploymentName, |
1690 | + }) |
1691 | + if err != nil { |
1692 | + return err |
1693 | + } |
1694 | + // Look for the role instance. |
1695 | + for _, role := range d.RoleInstanceList { |
1696 | + if role.RoleName == azInstance.roleName { |
1697 | + azInstance.mu.Lock() |
1698 | + azInstance.roleInstance = &role |
1699 | + azInstance.mu.Unlock() |
1700 | + return nil |
1701 | + } |
1702 | + } |
1703 | + return errors.NotFoundf("role instance %q", azInstance.roleName) |
1704 | + }) |
1705 | } |
1706 | |
1707 | // Addresses is specified in the Instance interface. |
1708 | func (azInstance *azureInstance) Addresses() ([]instance.Address, error) { |
1709 | - addrs := []instance.Address{} |
1710 | - ip, netname, err := azInstance.netInfo() |
1711 | - if err != nil { |
1712 | - return nil, err |
1713 | - } |
1714 | - if ip != "" { |
1715 | - addrs = append(addrs, instance.Address{ |
1716 | - ip, |
1717 | - instance.Ipv4Address, |
1718 | - netname, |
1719 | - instance.NetworkCloudLocal}) |
1720 | - } |
1721 | - |
1722 | + var addrs []instance.Address |
1723 | + for i := 0; i < 2; i++ { |
1724 | + if ip := azInstance.ipAddress(); ip != "" { |
1725 | + addrs = append(addrs, instance.Address{ |
1726 | + Value: ip, |
1727 | + Type: instance.Ipv4Address, |
1728 | + NetworkName: azInstance.environ.getVirtualNetworkName(), |
1729 | + NetworkScope: instance.NetworkCloudLocal, |
1730 | + }) |
1731 | + break |
1732 | + } |
1733 | + if err := azInstance.Refresh(); err != nil { |
1734 | + return nil, err |
1735 | + } |
1736 | + } |
1737 | name, err := azInstance.DNSName() |
1738 | if err != nil { |
1739 | return nil, err |
1740 | @@ -66,32 +109,14 @@ |
1741 | return addrs, nil |
1742 | } |
1743 | |
1744 | -func (azInstance *azureInstance) netInfo() (ip, netname string, err error) { |
1745 | - err = azInstance.apiCall(false, func(c *azureManagementContext) error { |
1746 | - d, err := c.GetDeployment(&gwacl.GetDeploymentRequest{ |
1747 | - ServiceName: azInstance.ServiceName, |
1748 | - DeploymentName: azInstance.ServiceName, |
1749 | - }) |
1750 | - if err != nil { |
1751 | - return err |
1752 | - } |
1753 | - switch len(d.RoleInstanceList) { |
1754 | - case 0: |
1755 | - // nothing to do, this can happen if the instances aren't finished deploying |
1756 | - return nil |
1757 | - case 1: |
1758 | - // success |
1759 | - ip = d.RoleInstanceList[0].IPAddress |
1760 | - netname = d.VirtualNetworkName |
1761 | - return nil |
1762 | - default: |
1763 | - return fmt.Errorf("Too many instances, expected one, got %d", len(d.RoleInstanceList)) |
1764 | - } |
1765 | - }) |
1766 | - if err != nil { |
1767 | - return "", "", err |
1768 | +func (azInstance *azureInstance) ipAddress() string { |
1769 | + azInstance.mu.Lock() |
1770 | + defer azInstance.mu.Unlock() |
1771 | + if azInstance.roleInstance == nil { |
1772 | + // RoleInstance hasn't finished deploying. |
1773 | + return "" |
1774 | } |
1775 | - return ip, netname, nil |
1776 | + return azInstance.roleInstance.IPAddress |
1777 | } |
1778 | |
1779 | // DNSName is specified in the Instance interface. |
1780 | @@ -101,7 +126,7 @@ |
1781 | // (For Staging deployments it's all much weirder: they get random |
1782 | // names assigned, which somehow don't seem to resolve from the |
1783 | // outside.) |
1784 | - name := fmt.Sprintf("%s.%s", azInstance.ServiceName, AZURE_DOMAIN_NAME) |
1785 | + name := fmt.Sprintf("%s.%s", azInstance.serviceName(), AZURE_DOMAIN_NAME) |
1786 | return name, nil |
1787 | } |
1788 | |
1789 | @@ -121,13 +146,11 @@ |
1790 | // the environment |
1791 | func (azInstance *azureInstance) apiCall(lock bool, f func(*azureManagementContext) error) error { |
1792 | env := azInstance.environ |
1793 | - |
1794 | context, err := env.getManagementAPI() |
1795 | if err != nil { |
1796 | return err |
1797 | } |
1798 | defer env.releaseManagementAPI(context) |
1799 | - |
1800 | if lock { |
1801 | env.Lock() |
1802 | defer env.Unlock() |
1803 | @@ -139,37 +162,40 @@ |
1804 | // responsible for locking and unlocking the environ and releasing the |
1805 | // management context. |
1806 | func (azInstance *azureInstance) openEndpoints(context *azureManagementContext, ports []instance.Port) error { |
1807 | - deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ |
1808 | - ServiceName: azInstance.ServiceName, |
1809 | - }) |
1810 | - if err != nil { |
1811 | - return err |
1812 | - } |
1813 | - |
1814 | - for _, deployment := range deployments { |
1815 | - for _, role := range deployment.RoleList { |
1816 | - request := &gwacl.AddRoleEndpointsRequest{ |
1817 | - ServiceName: azInstance.ServiceName, |
1818 | - DeploymentName: deployment.Name, |
1819 | - RoleName: role.RoleName, |
1820 | - } |
1821 | - for _, port := range ports { |
1822 | - request.InputEndpoints = append( |
1823 | - request.InputEndpoints, gwacl.InputEndpoint{ |
1824 | - LocalPort: port.Number, |
1825 | - Name: fmt.Sprintf("%s%d", port.Protocol, port.Number), |
1826 | - Port: port.Number, |
1827 | - Protocol: port.Protocol, |
1828 | - }) |
1829 | - } |
1830 | - err := context.AddRoleEndpoints(request) |
1831 | - if err != nil { |
1832 | - return err |
1833 | - } |
1834 | - } |
1835 | - } |
1836 | - |
1837 | - return nil |
1838 | + request := &gwacl.AddRoleEndpointsRequest{ |
1839 | + ServiceName: azInstance.serviceName(), |
1840 | + DeploymentName: azInstance.deploymentName, |
1841 | + RoleName: azInstance.roleName, |
1842 | + } |
1843 | + for _, port := range ports { |
1844 | + name := fmt.Sprintf("%s%d", port.Protocol, port.Number) |
1845 | + endpoint := gwacl.InputEndpoint{ |
1846 | + LocalPort: port.Number, |
1847 | + Name: name, |
1848 | + Port: port.Number, |
1849 | + Protocol: port.Protocol, |
1850 | + } |
1851 | + if azInstance.supportsLoadBalancing() { |
1852 | + probePort := port.Number |
1853 | + if strings.ToUpper(endpoint.Protocol) == "UDP" { |
1854 | + // Load balancing needs a TCP port to probe, or an HTTP |
1855 | + // server port & path to query. For UDP, we just use the |
1856 | + // machine's SSH agent port to test machine liveness. |
1857 | + // |
1858 | + // It probably doesn't make sense to load balance most UDP |
1859 | + // protocols transparently, but that's an application level |
1860 | + // concern. |
1861 | + probePort = 22 |
1862 | + } |
1863 | + endpoint.LoadBalancedEndpointSetName = name |
1864 | + endpoint.LoadBalancerProbe = &gwacl.LoadBalancerProbe{ |
1865 | + Port: probePort, |
1866 | + Protocol: "TCP", |
1867 | + } |
1868 | + } |
1869 | + request.InputEndpoints = append(request.InputEndpoints, endpoint) |
1870 | + } |
1871 | + return context.AddRoleEndpoints(request) |
1872 | } |
1873 | |
1874 | // ClosePorts is specified in the Instance interface. |
1875 | @@ -183,40 +209,25 @@ |
1876 | // responsible for locking and unlocking the environ and releasing the |
1877 | // management context. |
1878 | func (azInstance *azureInstance) closeEndpoints(context *azureManagementContext, ports []instance.Port) error { |
1879 | - deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ |
1880 | - ServiceName: azInstance.ServiceName, |
1881 | - }) |
1882 | - if err != nil { |
1883 | - return err |
1884 | - } |
1885 | - |
1886 | - for _, deployment := range deployments { |
1887 | - for _, role := range deployment.RoleList { |
1888 | - request := &gwacl.RemoveRoleEndpointsRequest{ |
1889 | - ServiceName: azInstance.ServiceName, |
1890 | - DeploymentName: deployment.Name, |
1891 | - RoleName: role.RoleName, |
1892 | - } |
1893 | - for _, port := range ports { |
1894 | - request.InputEndpoints = append( |
1895 | - request.InputEndpoints, gwacl.InputEndpoint{ |
1896 | - LocalPort: port.Number, |
1897 | - Name: fmt.Sprintf("%s%d", port.Protocol, port.Number), |
1898 | - Port: port.Number, |
1899 | - Protocol: port.Protocol, |
1900 | - }) |
1901 | - } |
1902 | - err := context.RemoveRoleEndpoints(request) |
1903 | - if err != nil { |
1904 | - return err |
1905 | - } |
1906 | - } |
1907 | - } |
1908 | - |
1909 | - return nil |
1910 | + request := &gwacl.RemoveRoleEndpointsRequest{ |
1911 | + ServiceName: azInstance.serviceName(), |
1912 | + DeploymentName: azInstance.deploymentName, |
1913 | + RoleName: azInstance.roleName, |
1914 | + } |
1915 | + for _, port := range ports { |
1916 | + name := fmt.Sprintf("%s%d", port.Protocol, port.Number) |
1917 | + request.InputEndpoints = append(request.InputEndpoints, gwacl.InputEndpoint{ |
1918 | + LocalPort: port.Number, |
1919 | + Name: name, |
1920 | + Port: port.Number, |
1921 | + Protocol: port.Protocol, |
1922 | + LoadBalancedEndpointSetName: name, |
1923 | + }) |
1924 | + } |
1925 | + return context.RemoveRoleEndpoints(request) |
1926 | } |
1927 | |
1928 | -// convertAndFilterEndpoints converts a slice of gwacl.InputEndpoint into a slice of instance.Port. |
1929 | +// convertEndpointsToPorts converts a slice of gwacl.InputEndpoint into a slice of instance.Port. |
1930 | func convertEndpointsToPorts(endpoints []gwacl.InputEndpoint) []instance.Port { |
1931 | ports := []instance.Port{} |
1932 | for _, endpoint := range endpoints { |
1933 | @@ -230,10 +241,11 @@ |
1934 | |
1935 | // convertAndFilterEndpoints converts a slice of gwacl.InputEndpoint into a slice of instance.Port |
1936 | // and filters out the initial endpoints that every instance should have opened (ssh port, etc.). |
1937 | -func convertAndFilterEndpoints(endpoints []gwacl.InputEndpoint, env *azureEnviron) []instance.Port { |
1938 | +func convertAndFilterEndpoints(endpoints []gwacl.InputEndpoint, env *azureEnviron, stateServer bool) []instance.Port { |
1939 | return firewaller.Diff( |
1940 | convertEndpointsToPorts(endpoints), |
1941 | - convertEndpointsToPorts(env.getInitialEndpoints())) |
1942 | + convertEndpointsToPorts(env.getInitialEndpoints(stateServer)), |
1943 | + ) |
1944 | } |
1945 | |
1946 | // Ports is specified in the Instance interface. |
1947 | @@ -254,39 +266,14 @@ |
1948 | // responsible for locking and unlocking the environ and releasing the |
1949 | // management context. |
1950 | func (azInstance *azureInstance) listPorts(context *azureManagementContext) ([]instance.Port, error) { |
1951 | - deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ |
1952 | - ServiceName: azInstance.ServiceName, |
1953 | + endpoints, err := context.ListRoleEndpoints(&gwacl.ListRoleEndpointsRequest{ |
1954 | + ServiceName: azInstance.serviceName(), |
1955 | + DeploymentName: azInstance.deploymentName, |
1956 | + RoleName: azInstance.roleName, |
1957 | }) |
1958 | if err != nil { |
1959 | return nil, err |
1960 | } |
1961 | - |
1962 | - env := azInstance.environ |
1963 | - switch { |
1964 | - // Only zero or one deployment is a valid state (instance==service). |
1965 | - case len(deployments) > 1: |
1966 | - return nil, fmt.Errorf("more than one Azure deployment inside the service named %q", azInstance.ServiceName) |
1967 | - case len(deployments) == 1: |
1968 | - deployment := deployments[0] |
1969 | - switch { |
1970 | - // Only zero or one role is a valid state (instance==service). |
1971 | - case len(deployment.RoleList) > 1: |
1972 | - return nil, fmt.Errorf("more than one Azure role inside the deployment named %q", deployment.Name) |
1973 | - case len(deployment.RoleList) == 1: |
1974 | - role := deployment.RoleList[0] |
1975 | - |
1976 | - endpoints, err := context.ListRoleEndpoints(&gwacl.ListRoleEndpointsRequest{ |
1977 | - ServiceName: azInstance.ServiceName, |
1978 | - DeploymentName: deployment.Name, |
1979 | - RoleName: role.RoleName, |
1980 | - }) |
1981 | - if err != nil { |
1982 | - return nil, err |
1983 | - } |
1984 | - ports := convertAndFilterEndpoints(endpoints, env) |
1985 | - return ports, nil |
1986 | - } |
1987 | - return nil, nil |
1988 | - } |
1989 | - return nil, nil |
1990 | + ports := convertAndFilterEndpoints(endpoints, azInstance.environ, azInstance.maskStateServerPorts) |
1991 | + return ports, nil |
1992 | } |
1993 | |
1994 | === modified file 'provider/azure/instance_test.go' |
1995 | --- provider/azure/instance_test.go 2014-03-13 07:54:56 +0000 |
1996 | +++ provider/azure/instance_test.go 2014-04-03 07:35:44 +0000 |
1997 | @@ -13,12 +13,42 @@ |
1998 | "launchpad.net/gwacl" |
1999 | |
2000 | "launchpad.net/juju-core/instance" |
2001 | + "launchpad.net/juju-core/testing/testbase" |
2002 | ) |
2003 | |
2004 | -type instanceSuite struct{} |
2005 | +type instanceSuite struct { |
2006 | + testbase.LoggingSuite |
2007 | + env *azureEnviron |
2008 | + service *gwacl.HostedService |
2009 | + deployment *gwacl.Deployment |
2010 | + role *gwacl.Role |
2011 | + instance *azureInstance |
2012 | +} |
2013 | |
2014 | var _ = gc.Suite(&instanceSuite{}) |
2015 | |
2016 | +func (s *instanceSuite) SetUpTest(c *gc.C) { |
2017 | + s.env = makeEnviron(c) |
2018 | + s.service = makeDeployment(s.env, "service-name") |
2019 | + s.deployment = &s.service.Deployments[0] |
2020 | + s.deployment.Name = "deployment-one" |
2021 | + s.role = &s.deployment.RoleList[0] |
2022 | + s.role.RoleName = "role-one" |
2023 | + inst, err := s.env.getInstance(s.service, s.role.RoleName) |
2024 | + c.Assert(err, gc.IsNil) |
2025 | + c.Assert(inst, gc.FitsTypeOf, &azureInstance{}) |
2026 | + s.instance = inst.(*azureInstance) |
2027 | +} |
2028 | + |
2029 | +func configSetNetwork(role *gwacl.Role) *gwacl.ConfigurationSet { |
2030 | + for i, configSet := range role.ConfigurationSets { |
2031 | + if configSet.ConfigurationSetType == gwacl.CONFIG_SET_NETWORK { |
2032 | + return &role.ConfigurationSets[i] |
2033 | + } |
2034 | + } |
2035 | + return nil |
2036 | +} |
2037 | + |
2038 | // makeHostedServiceDescriptor creates a HostedServiceDescriptor with the |
2039 | // given service name. |
2040 | func makeHostedServiceDescriptor(name string) *gwacl.HostedServiceDescriptor { |
2041 | @@ -27,66 +57,50 @@ |
2042 | } |
2043 | |
2044 | func (*instanceSuite) TestId(c *gc.C) { |
2045 | - serviceName := "test-name" |
2046 | - testService := makeHostedServiceDescriptor(serviceName) |
2047 | - azInstance := azureInstance{*testService, nil} |
2048 | - c.Check(azInstance.Id(), gc.Equals, instance.Id(serviceName)) |
2049 | + azInstance := azureInstance{instanceId: "whatever"} |
2050 | + c.Check(azInstance.Id(), gc.Equals, instance.Id("whatever")) |
2051 | } |
2052 | |
2053 | func (*instanceSuite) TestStatus(c *gc.C) { |
2054 | - serviceName := "test-name" |
2055 | - testService := makeHostedServiceDescriptor(serviceName) |
2056 | - testService.Status = "something" |
2057 | - azInstance := azureInstance{*testService, nil} |
2058 | - c.Check(azInstance.Status(), gc.Equals, testService.Status) |
2059 | + var inst azureInstance |
2060 | + c.Check(inst.Status(), gc.Equals, "") |
2061 | + inst.roleInstance = &gwacl.RoleInstance{InstanceStatus: "anyoldthing"} |
2062 | + c.Check(inst.Status(), gc.Equals, "anyoldthing") |
2063 | } |
2064 | |
2065 | func (*instanceSuite) TestDNSName(c *gc.C) { |
2066 | - // An instance's DNS name is computed from its hosted-service name. |
2067 | - host := "hostname" |
2068 | - testService := makeHostedServiceDescriptor(host) |
2069 | - azInstance := azureInstance{*testService, nil} |
2070 | + testService := makeHostedServiceDescriptor("cloud-service-name") |
2071 | + azInstance := azureInstance{hostedService: testService} |
2072 | dnsName, err := azInstance.DNSName() |
2073 | c.Assert(err, gc.IsNil) |
2074 | - c.Check(dnsName, gc.Equals, host+"."+AZURE_DOMAIN_NAME) |
2075 | + c.Check(dnsName, gc.Equals, "cloud-service-name.cloudapp.net") |
2076 | } |
2077 | |
2078 | func (*instanceSuite) TestWaitDNSName(c *gc.C) { |
2079 | // An Azure instance gets its DNS name immediately, so there's no |
2080 | // waiting involved. |
2081 | - host := "hostname" |
2082 | - testService := makeHostedServiceDescriptor(host) |
2083 | - azInstance := azureInstance{*testService, nil} |
2084 | + testService := makeHostedServiceDescriptor("cloud-service-name") |
2085 | + azInstance := azureInstance{hostedService: testService} |
2086 | dnsName, err := azInstance.WaitDNSName() |
2087 | c.Assert(err, gc.IsNil) |
2088 | - c.Check(dnsName, gc.Equals, host+"."+AZURE_DOMAIN_NAME) |
2089 | -} |
2090 | - |
2091 | -func makeRole(name string, endpoints ...gwacl.InputEndpoint) gwacl.Role { |
2092 | - return gwacl.Role{ |
2093 | - RoleName: name, |
2094 | - ConfigurationSets: []gwacl.ConfigurationSet{ |
2095 | - { |
2096 | - ConfigurationSetType: gwacl.CONFIG_SET_NETWORK, |
2097 | - InputEndpoints: &endpoints, |
2098 | - }, |
2099 | - }, |
2100 | - } |
2101 | -} |
2102 | - |
2103 | -func makeDeployment(name string, roles ...gwacl.Role) gwacl.Deployment { |
2104 | - return gwacl.Deployment{ |
2105 | - Name: name, |
2106 | - RoleList: roles, |
2107 | - } |
2108 | + c.Check(dnsName, gc.Equals, "cloud-service-name.cloudapp.net") |
2109 | } |
2110 | |
2111 | func makeInputEndpoint(port int, protocol string) gwacl.InputEndpoint { |
2112 | + name := fmt.Sprintf("%s%d", protocol, port) |
2113 | + probe := &gwacl.LoadBalancerProbe{Port: port, Protocol: "TCP"} |
2114 | + if protocol == "udp" { |
2115 | + // We just use port 22 (SSH) for the |
2116 | + // probe when a UDP port is exposed. |
2117 | + probe.Port = 22 |
2118 | + } |
2119 | return gwacl.InputEndpoint{ |
2120 | LocalPort: port, |
2121 | - Name: fmt.Sprintf("%s%d", protocol, port), |
2122 | - Port: port, |
2123 | - Protocol: protocol, |
2124 | + Name: name, |
2125 | + LoadBalancedEndpointSetName: name, |
2126 | + LoadBalancerProbe: probe, |
2127 | + Port: port, |
2128 | + Protocol: protocol, |
2129 | } |
2130 | } |
2131 | |
2132 | @@ -104,36 +118,18 @@ |
2133 | } |
2134 | } |
2135 | |
2136 | -func preparePortChangeConversation( |
2137 | - c *gc.C, service *gwacl.HostedServiceDescriptor, |
2138 | - deployments ...gwacl.Deployment) []gwacl.DispatcherResponse { |
2139 | - // Construct the series of responses to expected requests. |
2140 | - responses := []gwacl.DispatcherResponse{ |
2141 | - // First, GetHostedServiceProperties |
2142 | - gwacl.NewDispatcherResponse( |
2143 | - serialize(c, &gwacl.HostedService{ |
2144 | - Deployments: deployments, |
2145 | - HostedServiceDescriptor: *service, |
2146 | - XMLNS: gwacl.XMLNS, |
2147 | - }), |
2148 | - http.StatusOK, nil), |
2149 | - } |
2150 | - for _, deployment := range deployments { |
2151 | - for _, role := range deployment.RoleList { |
2152 | - // GetRole returns a PersistentVMRole. |
2153 | - persistentRole := &gwacl.PersistentVMRole{ |
2154 | - XMLNS: gwacl.XMLNS, |
2155 | - RoleName: role.RoleName, |
2156 | - ConfigurationSets: role.ConfigurationSets, |
2157 | - } |
2158 | - responses = append(responses, gwacl.NewDispatcherResponse( |
2159 | - serialize(c, persistentRole), http.StatusOK, nil)) |
2160 | - // UpdateRole expects a 200 response, that's all. |
2161 | - responses = append(responses, |
2162 | - gwacl.NewDispatcherResponse(nil, http.StatusOK, nil)) |
2163 | - } |
2164 | - } |
2165 | - return responses |
2166 | +func preparePortChangeConversation(c *gc.C, role *gwacl.Role) []gwacl.DispatcherResponse { |
2167 | + persistentRole := &gwacl.PersistentVMRole{ |
2168 | + XMLNS: gwacl.XMLNS, |
2169 | + RoleName: role.RoleName, |
2170 | + ConfigurationSets: role.ConfigurationSets, |
2171 | + } |
2172 | + return []gwacl.DispatcherResponse{ |
2173 | + // GetRole returns a PersistentVMRole. |
2174 | + gwacl.NewDispatcherResponse(serialize(c, persistentRole), http.StatusOK, nil), |
2175 | + // UpdateRole expects a 200 response, that's all. |
2176 | + gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), |
2177 | + } |
2178 | } |
2179 | |
2180 | // point is 1-indexed; it represents which request should fail. |
2181 | @@ -155,20 +151,16 @@ |
2182 | } |
2183 | } |
2184 | |
2185 | -func (*instanceSuite) TestAddresses(c *gc.C) { |
2186 | - name := "service-name" |
2187 | - vnn := "Virt Net Name" |
2188 | - service := makeHostedServiceDescriptor(name) |
2189 | - responses := prepareDeploymentInfoResponse(c, |
2190 | - gwacl.Deployment{ |
2191 | - RoleInstanceList: []gwacl.RoleInstance{ |
2192 | - gwacl.RoleInstance{IPAddress: "1.2.3.4"}, |
2193 | - }, |
2194 | - VirtualNetworkName: vnn, |
2195 | - }) |
2196 | - |
2197 | +func (s *instanceSuite) TestAddresses(c *gc.C) { |
2198 | + vnn := s.env.getVirtualNetworkName() |
2199 | + responses := prepareDeploymentInfoResponse(c, gwacl.Deployment{ |
2200 | + RoleInstanceList: []gwacl.RoleInstance{{ |
2201 | + RoleName: s.role.RoleName, |
2202 | + IPAddress: "1.2.3.4", |
2203 | + }}, |
2204 | + VirtualNetworkName: vnn, |
2205 | + }) |
2206 | gwacl.PatchManagementAPIResponses(responses) |
2207 | - inst := azureInstance{*service, makeEnviron(c)} |
2208 | |
2209 | expected := []instance.Address{ |
2210 | instance.Address{ |
2211 | @@ -178,217 +170,153 @@ |
2212 | instance.NetworkCloudLocal, |
2213 | }, |
2214 | instance.Address{ |
2215 | - name + "." + AZURE_DOMAIN_NAME, |
2216 | + s.service.ServiceName + "." + AZURE_DOMAIN_NAME, |
2217 | instance.HostName, |
2218 | "", |
2219 | instance.NetworkPublic, |
2220 | }, |
2221 | } |
2222 | |
2223 | - addrs, err := inst.Addresses() |
2224 | + addrs, err := s.instance.Addresses() |
2225 | c.Check(err, gc.IsNil) |
2226 | - |
2227 | c.Check(addrs, jc.SameContents, expected) |
2228 | } |
2229 | |
2230 | -func (*instanceSuite) TestOpenPorts(c *gc.C) { |
2231 | - service := makeHostedServiceDescriptor("service-name") |
2232 | - responses := preparePortChangeConversation(c, service, |
2233 | - makeDeployment("deployment-one", |
2234 | - makeRole("role-one"), makeRole("role-two")), |
2235 | - makeDeployment("deployment-two", |
2236 | - makeRole("role-three"))) |
2237 | +func (s *instanceSuite) TestOpenPorts(c *gc.C) { |
2238 | + // Close the default ports. |
2239 | + configSetNetwork((*gwacl.Role)(s.role)).InputEndpoints = nil |
2240 | + |
2241 | + responses := preparePortChangeConversation(c, s.role) |
2242 | record := gwacl.PatchManagementAPIResponses(responses) |
2243 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2244 | - |
2245 | - err := azInstance.OpenPorts("machine-id", []instance.Port{ |
2246 | + err := s.instance.OpenPorts("machine-id", []instance.Port{ |
2247 | {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2248 | }) |
2249 | - |
2250 | c.Assert(err, gc.IsNil) |
2251 | + |
2252 | assertPortChangeConversation(c, *record, []expectedRequest{ |
2253 | - {"GET", ".*/services/hostedservices/service-name[?].*"}, // GetHostedServiceProperties |
2254 | - {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole |
2255 | - {"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole |
2256 | - {"GET", ".*/deployments/deployment-one/roles/role-two"}, // GetRole |
2257 | - {"PUT", ".*/deployments/deployment-one/roles/role-two"}, // UpdateRole |
2258 | - {"GET", ".*/deployments/deployment-two/roles/role-three"}, // GetRole |
2259 | - {"PUT", ".*/deployments/deployment-two/roles/role-three"}, // UpdateRole |
2260 | + {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole |
2261 | + {"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole |
2262 | }) |
2263 | |
2264 | // A representative UpdateRole payload includes configuration for the |
2265 | // ports requested. |
2266 | role := &gwacl.PersistentVMRole{} |
2267 | - err = role.Deserialize((*record)[2].Payload) |
2268 | + err = role.Deserialize((*record)[1].Payload) |
2269 | c.Assert(err, gc.IsNil) |
2270 | c.Check( |
2271 | - *(role.ConfigurationSets[0].InputEndpoints), |
2272 | - gc.DeepEquals, []gwacl.InputEndpoint{ |
2273 | + *configSetNetwork((*gwacl.Role)(role)).InputEndpoints, |
2274 | + gc.DeepEquals, |
2275 | + []gwacl.InputEndpoint{ |
2276 | makeInputEndpoint(79, "tcp"), |
2277 | makeInputEndpoint(587, "tcp"), |
2278 | makeInputEndpoint(9, "udp"), |
2279 | + }, |
2280 | + ) |
2281 | +} |
2282 | + |
2283 | +func (s *instanceSuite) TestOpenPortsFailsWhenUnableToGetRole(c *gc.C) { |
2284 | + responses := preparePortChangeConversation(c, s.role) |
2285 | + failPortChangeConversationAt(1, responses) // 1st request, GetRole |
2286 | + record := gwacl.PatchManagementAPIResponses(responses) |
2287 | + err := s.instance.OpenPorts("machine-id", []instance.Port{ |
2288 | + {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2289 | + }) |
2290 | + c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") |
2291 | + c.Check(*record, gc.HasLen, 1) |
2292 | +} |
2293 | + |
2294 | +func (s *instanceSuite) TestOpenPortsFailsWhenUnableToUpdateRole(c *gc.C) { |
2295 | + responses := preparePortChangeConversation(c, s.role) |
2296 | + failPortChangeConversationAt(2, responses) // 2nd request, UpdateRole |
2297 | + record := gwacl.PatchManagementAPIResponses(responses) |
2298 | + err := s.instance.OpenPorts("machine-id", []instance.Port{ |
2299 | + {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2300 | + }) |
2301 | + c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]") |
2302 | + c.Check(*record, gc.HasLen, 2) |
2303 | +} |
2304 | + |
2305 | +func (s *instanceSuite) TestClosePorts(c *gc.C) { |
2306 | + type test struct { |
2307 | + inputPorts []instance.Port |
2308 | + removePorts []instance.Port |
2309 | + outputPorts []instance.Port |
2310 | + } |
2311 | + |
2312 | + tests := []test{{ |
2313 | + inputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, |
2314 | + removePorts: nil, |
2315 | + outputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, |
2316 | + }, { |
2317 | + inputPorts: []instance.Port{{"tcp", 1}}, |
2318 | + removePorts: []instance.Port{{"udp", 1}}, |
2319 | + outputPorts: []instance.Port{{"tcp", 1}}, |
2320 | + }, { |
2321 | + inputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, |
2322 | + removePorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, |
2323 | + outputPorts: []instance.Port{}, |
2324 | + }, { |
2325 | + inputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, |
2326 | + removePorts: []instance.Port{{"tcp", 99}}, |
2327 | + outputPorts: []instance.Port{{"tcp", 1}, {"tcp", 2}, {"udp", 3}}, |
2328 | + }} |
2329 | + |
2330 | + for i, test := range tests { |
2331 | + c.Logf("test %d: %#v", i, test) |
2332 | + |
2333 | + inputEndpoints := make([]gwacl.InputEndpoint, len(test.inputPorts)) |
2334 | + for i, port := range test.inputPorts { |
2335 | + inputEndpoints[i] = makeInputEndpoint(port.Number, port.Protocol) |
2336 | + } |
2337 | + configSetNetwork(s.role).InputEndpoints = &inputEndpoints |
2338 | + responses := preparePortChangeConversation(c, s.role) |
2339 | + record := gwacl.PatchManagementAPIResponses(responses) |
2340 | + |
2341 | + err := s.instance.ClosePorts("machine-id", test.removePorts) |
2342 | + c.Assert(err, gc.IsNil) |
2343 | + assertPortChangeConversation(c, *record, []expectedRequest{ |
2344 | + {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole |
2345 | + {"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole |
2346 | }) |
2347 | -} |
2348 | - |
2349 | -func (*instanceSuite) TestOpenPortsFailsWhenUnableToGetServiceProperties(c *gc.C) { |
2350 | - service := makeHostedServiceDescriptor("service-name") |
2351 | - responses := []gwacl.DispatcherResponse{ |
2352 | - // GetHostedServiceProperties breaks. |
2353 | - gwacl.NewDispatcherResponse(nil, http.StatusInternalServerError, nil), |
2354 | - } |
2355 | - record := gwacl.PatchManagementAPIResponses(responses) |
2356 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2357 | - |
2358 | - err := azInstance.OpenPorts("machine-id", []instance.Port{ |
2359 | - {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2360 | - }) |
2361 | - |
2362 | - c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") |
2363 | - c.Check(*record, gc.HasLen, 1) |
2364 | -} |
2365 | - |
2366 | -func (*instanceSuite) TestOpenPortsFailsWhenUnableToGetRole(c *gc.C) { |
2367 | - service := makeHostedServiceDescriptor("service-name") |
2368 | - responses := preparePortChangeConversation(c, service, |
2369 | - makeDeployment("deployment-one", makeRole("role-one"))) |
2370 | - failPortChangeConversationAt(2, responses) // 2nd request, GetRole |
2371 | - record := gwacl.PatchManagementAPIResponses(responses) |
2372 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2373 | - |
2374 | - err := azInstance.OpenPorts("machine-id", []instance.Port{ |
2375 | - {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2376 | - }) |
2377 | - |
2378 | - c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") |
2379 | - c.Check(*record, gc.HasLen, 2) |
2380 | -} |
2381 | - |
2382 | -func (*instanceSuite) TestOpenPortsFailsWhenUnableToUpdateRole(c *gc.C) { |
2383 | - service := makeHostedServiceDescriptor("service-name") |
2384 | - responses := preparePortChangeConversation(c, service, |
2385 | - makeDeployment("deployment-one", makeRole("role-one"))) |
2386 | - failPortChangeConversationAt(3, responses) // 3rd request, UpdateRole |
2387 | - record := gwacl.PatchManagementAPIResponses(responses) |
2388 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2389 | - |
2390 | - err := azInstance.OpenPorts("machine-id", []instance.Port{ |
2391 | - {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2392 | - }) |
2393 | - |
2394 | - c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]") |
2395 | - c.Check(*record, gc.HasLen, 3) |
2396 | -} |
2397 | - |
2398 | -func (*instanceSuite) TestClosePorts(c *gc.C) { |
2399 | - service := makeHostedServiceDescriptor("service-name") |
2400 | - responses := preparePortChangeConversation(c, service, |
2401 | - makeDeployment("deployment-one", |
2402 | - makeRole("role-one", |
2403 | - makeInputEndpoint(587, "tcp"), |
2404 | - ), |
2405 | - makeRole("role-two", |
2406 | - makeInputEndpoint(79, "tcp"), |
2407 | - makeInputEndpoint(9, "udp"), |
2408 | - )), |
2409 | - makeDeployment("deployment-two", |
2410 | - makeRole("role-three", |
2411 | - makeInputEndpoint(9, "tcp"), |
2412 | - makeInputEndpoint(9, "udp"), |
2413 | - ))) |
2414 | - record := gwacl.PatchManagementAPIResponses(responses) |
2415 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2416 | - |
2417 | - err := azInstance.ClosePorts("machine-id", |
2418 | - []instance.Port{{"tcp", 587}, {"udp", 9}}) |
2419 | - |
2420 | - c.Assert(err, gc.IsNil) |
2421 | - assertPortChangeConversation(c, *record, []expectedRequest{ |
2422 | - {"GET", ".*/services/hostedservices/service-name[?].*"}, // GetHostedServiceProperties |
2423 | - {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole |
2424 | - {"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole |
2425 | - {"GET", ".*/deployments/deployment-one/roles/role-two"}, // GetRole |
2426 | - {"PUT", ".*/deployments/deployment-one/roles/role-two"}, // UpdateRole |
2427 | - {"GET", ".*/deployments/deployment-two/roles/role-three"}, // GetRole |
2428 | - {"PUT", ".*/deployments/deployment-two/roles/role-three"}, // UpdateRole |
2429 | - }) |
2430 | - |
2431 | - // The first UpdateRole removes all endpoints from the role's |
2432 | - // configuration. |
2433 | - roleOne := &gwacl.PersistentVMRole{} |
2434 | - err = roleOne.Deserialize((*record)[2].Payload) |
2435 | - c.Assert(err, gc.IsNil) |
2436 | - c.Check(roleOne.ConfigurationSets[0].InputEndpoints, gc.IsNil) |
2437 | - |
2438 | - // The second UpdateRole removes all but 79/TCP. |
2439 | - roleTwo := &gwacl.PersistentVMRole{} |
2440 | - err = roleTwo.Deserialize((*record)[4].Payload) |
2441 | - c.Assert(err, gc.IsNil) |
2442 | - c.Check( |
2443 | - roleTwo.ConfigurationSets[0].InputEndpoints, |
2444 | - gc.DeepEquals, |
2445 | - &[]gwacl.InputEndpoint{makeInputEndpoint(79, "tcp")}) |
2446 | - |
2447 | - // The third UpdateRole removes all but 9/TCP. |
2448 | - roleThree := &gwacl.PersistentVMRole{} |
2449 | - err = roleThree.Deserialize((*record)[6].Payload) |
2450 | - c.Assert(err, gc.IsNil) |
2451 | - c.Check( |
2452 | - roleThree.ConfigurationSets[0].InputEndpoints, |
2453 | - gc.DeepEquals, |
2454 | - &[]gwacl.InputEndpoint{makeInputEndpoint(9, "tcp")}) |
2455 | -} |
2456 | - |
2457 | -func (*instanceSuite) TestClosePortsFailsWhenUnableToGetServiceProperties(c *gc.C) { |
2458 | - service := makeHostedServiceDescriptor("service-name") |
2459 | - responses := []gwacl.DispatcherResponse{ |
2460 | - // GetHostedServiceProperties breaks. |
2461 | - gwacl.NewDispatcherResponse(nil, http.StatusInternalServerError, nil), |
2462 | - } |
2463 | - record := gwacl.PatchManagementAPIResponses(responses) |
2464 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2465 | - |
2466 | - err := azInstance.ClosePorts("machine-id", []instance.Port{ |
2467 | - {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2468 | - }) |
2469 | - |
2470 | - c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") |
2471 | - c.Check(*record, gc.HasLen, 1) |
2472 | -} |
2473 | - |
2474 | -func (*instanceSuite) TestClosePortsFailsWhenUnableToGetRole(c *gc.C) { |
2475 | - service := makeHostedServiceDescriptor("service-name") |
2476 | - responses := preparePortChangeConversation(c, service, |
2477 | - makeDeployment("deployment-one", makeRole("role-one"))) |
2478 | - failPortChangeConversationAt(2, responses) // 2nd request, GetRole |
2479 | - record := gwacl.PatchManagementAPIResponses(responses) |
2480 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2481 | - |
2482 | - err := azInstance.ClosePorts("machine-id", []instance.Port{ |
2483 | - {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2484 | - }) |
2485 | - |
2486 | - c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") |
2487 | - c.Check(*record, gc.HasLen, 2) |
2488 | -} |
2489 | - |
2490 | -func (*instanceSuite) TestClosePortsFailsWhenUnableToUpdateRole(c *gc.C) { |
2491 | - service := makeHostedServiceDescriptor("service-name") |
2492 | - responses := preparePortChangeConversation(c, service, |
2493 | - makeDeployment("deployment-one", makeRole("role-one"))) |
2494 | - failPortChangeConversationAt(3, responses) // 3rd request, UpdateRole |
2495 | - record := gwacl.PatchManagementAPIResponses(responses) |
2496 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2497 | - |
2498 | - err := azInstance.ClosePorts("machine-id", []instance.Port{ |
2499 | - {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2500 | - }) |
2501 | - |
2502 | - c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]") |
2503 | - c.Check(*record, gc.HasLen, 3) |
2504 | -} |
2505 | - |
2506 | -func (*instanceSuite) TestConvertAndFilterEndpoints(c *gc.C) { |
2507 | - env := makeEnviron(c) |
2508 | + |
2509 | + // The first UpdateRole removes all endpoints from the role's |
2510 | + // configuration. |
2511 | + roleOne := &gwacl.PersistentVMRole{} |
2512 | + err = roleOne.Deserialize((*record)[1].Payload) |
2513 | + c.Assert(err, gc.IsNil) |
2514 | + endpoints := configSetNetwork((*gwacl.Role)(roleOne)).InputEndpoints |
2515 | + if len(test.outputPorts) == 0 { |
2516 | + c.Check(endpoints, gc.IsNil) |
2517 | + } else { |
2518 | + c.Check(endpoints, gc.NotNil) |
2519 | + c.Check(convertAndFilterEndpoints(*endpoints, s.env, false), gc.DeepEquals, test.outputPorts) |
2520 | + } |
2521 | + } |
2522 | +} |
2523 | + |
2524 | +func (s *instanceSuite) TestClosePortsFailsWhenUnableToGetRole(c *gc.C) { |
2525 | + responses := preparePortChangeConversation(c, s.role) |
2526 | + failPortChangeConversationAt(1, responses) // 1st request, GetRole |
2527 | + record := gwacl.PatchManagementAPIResponses(responses) |
2528 | + err := s.instance.ClosePorts("machine-id", []instance.Port{ |
2529 | + {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2530 | + }) |
2531 | + c.Check(err, gc.ErrorMatches, "GET request failed [(]500: Internal Server Error[)]") |
2532 | + c.Check(*record, gc.HasLen, 1) |
2533 | +} |
2534 | + |
2535 | +func (s *instanceSuite) TestClosePortsFailsWhenUnableToUpdateRole(c *gc.C) { |
2536 | + responses := preparePortChangeConversation(c, s.role) |
2537 | + failPortChangeConversationAt(2, responses) // 2nd request, UpdateRole |
2538 | + record := gwacl.PatchManagementAPIResponses(responses) |
2539 | + err := s.instance.ClosePorts("machine-id", []instance.Port{ |
2540 | + {"tcp", 79}, {"tcp", 587}, {"udp", 9}, |
2541 | + }) |
2542 | + c.Check(err, gc.ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]") |
2543 | + c.Check(*record, gc.HasLen, 2) |
2544 | +} |
2545 | + |
2546 | +func (s *instanceSuite) TestConvertAndFilterEndpoints(c *gc.C) { |
2547 | endpoints := []gwacl.InputEndpoint{ |
2548 | { |
2549 | LocalPort: 123, |
2550 | @@ -402,7 +330,7 @@ |
2551 | Name: "test456", |
2552 | Port: 44, |
2553 | }} |
2554 | - endpoints = append(endpoints, env.getInitialEndpoints()...) |
2555 | + endpoints = append(endpoints, s.env.getInitialEndpoints(true)...) |
2556 | expectedPorts := []instance.Port{ |
2557 | { |
2558 | Number: 1123, |
2559 | @@ -412,99 +340,66 @@ |
2560 | Number: 44, |
2561 | Protocol: "tcp", |
2562 | }} |
2563 | - c.Check(convertAndFilterEndpoints(endpoints, env), gc.DeepEquals, expectedPorts) |
2564 | + c.Check(convertAndFilterEndpoints(endpoints, s.env, true), gc.DeepEquals, expectedPorts) |
2565 | } |
2566 | |
2567 | -func (*instanceSuite) TestConvertAndFilterEndpointsEmptySlice(c *gc.C) { |
2568 | - env := makeEnviron(c) |
2569 | - ports := convertAndFilterEndpoints([]gwacl.InputEndpoint{}, env) |
2570 | +func (s *instanceSuite) TestConvertAndFilterEndpointsEmptySlice(c *gc.C) { |
2571 | + ports := convertAndFilterEndpoints([]gwacl.InputEndpoint{}, s.env, true) |
2572 | c.Check(ports, gc.HasLen, 0) |
2573 | } |
2574 | |
2575 | -func (*instanceSuite) TestPorts(c *gc.C) { |
2576 | - service := makeHostedServiceDescriptor("service-name") |
2577 | - endpoints := []gwacl.InputEndpoint{ |
2578 | - { |
2579 | - LocalPort: 223, |
2580 | - Protocol: "udp", |
2581 | - Name: "test223", |
2582 | - Port: 2123, |
2583 | - }, |
2584 | - { |
2585 | - LocalPort: 123, |
2586 | - Protocol: "udp", |
2587 | - Name: "test123", |
2588 | - Port: 1123, |
2589 | - }, |
2590 | - { |
2591 | - LocalPort: 456, |
2592 | - Protocol: "tcp", |
2593 | - Name: "test456", |
2594 | - Port: 4456, |
2595 | - }} |
2596 | - |
2597 | - responses := preparePortChangeConversation(c, service, |
2598 | - makeDeployment("deployment-one", |
2599 | - makeRole("role-one", endpoints...))) |
2600 | +func (s *instanceSuite) TestPorts(c *gc.C) { |
2601 | + s.testPorts(c, false) |
2602 | + s.testPorts(c, true) |
2603 | +} |
2604 | + |
2605 | +func (s *instanceSuite) testPorts(c *gc.C, maskStateServerPorts bool) { |
2606 | + // Update the role's endpoints by hand. |
2607 | + configSetNetwork(s.role).InputEndpoints = &[]gwacl.InputEndpoint{{ |
2608 | + LocalPort: 223, |
2609 | + Protocol: "udp", |
2610 | + Name: "test223", |
2611 | + Port: 2123, |
2612 | + }, { |
2613 | + LocalPort: 123, |
2614 | + Protocol: "udp", |
2615 | + Name: "test123", |
2616 | + Port: 1123, |
2617 | + }, { |
2618 | + LocalPort: 456, |
2619 | + Protocol: "tcp", |
2620 | + Name: "test456", |
2621 | + Port: 4456, |
2622 | + }, { |
2623 | + LocalPort: s.env.Config().StatePort(), |
2624 | + Protocol: "tcp", |
2625 | + Name: "stateserver", |
2626 | + Port: s.env.Config().StatePort(), |
2627 | + }, { |
2628 | + LocalPort: s.env.Config().APIPort(), |
2629 | + Protocol: "tcp", |
2630 | + Name: "apiserver", |
2631 | + Port: s.env.Config().APIPort(), |
2632 | + }} |
2633 | + |
2634 | + responses := preparePortChangeConversation(c, s.role) |
2635 | record := gwacl.PatchManagementAPIResponses(responses) |
2636 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2637 | - |
2638 | - ports, err := azInstance.Ports("machine-id") |
2639 | - |
2640 | + s.instance.maskStateServerPorts = maskStateServerPorts |
2641 | + ports, err := s.instance.Ports("machine-id") |
2642 | c.Assert(err, gc.IsNil) |
2643 | assertPortChangeConversation(c, *record, []expectedRequest{ |
2644 | - {"GET", ".*/services/hostedservices/service-name[?].*"}, // GetHostedServiceProperties |
2645 | {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole |
2646 | }) |
2647 | |
2648 | - c.Check( |
2649 | - ports, |
2650 | - gc.DeepEquals, |
2651 | - // The result is sorted using instance.SortPorts() (i.e. first by protocol, |
2652 | - // then by number). |
2653 | - []instance.Port{ |
2654 | - {Number: 4456, Protocol: "tcp"}, |
2655 | - {Number: 1123, Protocol: "udp"}, |
2656 | - {Number: 2123, Protocol: "udp"}, |
2657 | - }) |
2658 | -} |
2659 | - |
2660 | -func (*instanceSuite) TestPortsErrorsIfMoreThanOneRole(c *gc.C) { |
2661 | - service := makeHostedServiceDescriptor("service-name") |
2662 | - responses := preparePortChangeConversation(c, service, |
2663 | - makeDeployment("deployment-one", |
2664 | - makeRole("role-one"), makeRole("role-two"))) |
2665 | - gwacl.PatchManagementAPIResponses(responses) |
2666 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2667 | - |
2668 | - _, err := azInstance.Ports("machine-id") |
2669 | - |
2670 | - c.Check(err, gc.ErrorMatches, ".*more than one Azure role inside the deployment.*") |
2671 | -} |
2672 | - |
2673 | -func (*instanceSuite) TestPortsErrorsIfMoreThanOneDeployment(c *gc.C) { |
2674 | - service := makeHostedServiceDescriptor("service-name") |
2675 | - responses := preparePortChangeConversation(c, service, |
2676 | - makeDeployment("deployment-one", |
2677 | - makeRole("role-one")), |
2678 | - makeDeployment("deployment-two", |
2679 | - makeRole("role-two"))) |
2680 | - gwacl.PatchManagementAPIResponses(responses) |
2681 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2682 | - |
2683 | - _, err := azInstance.Ports("machine-id") |
2684 | - |
2685 | - c.Check(err, gc.ErrorMatches, ".*more than one Azure deployment inside the service.*") |
2686 | -} |
2687 | - |
2688 | -func (*instanceSuite) TestPortsReturnsEmptySliceIfNoDeployment(c *gc.C) { |
2689 | - service := makeHostedServiceDescriptor("service-name") |
2690 | - responses := preparePortChangeConversation(c, service) |
2691 | - gwacl.PatchManagementAPIResponses(responses) |
2692 | - azInstance := azureInstance{*service, makeEnviron(c)} |
2693 | - |
2694 | - ports, err := azInstance.Ports("machine-id") |
2695 | - |
2696 | - c.Assert(err, gc.IsNil) |
2697 | - c.Check(ports, gc.HasLen, 0) |
2698 | + expected := []instance.Port{ |
2699 | + {Number: 4456, Protocol: "tcp"}, |
2700 | + {Number: 1123, Protocol: "udp"}, |
2701 | + {Number: 2123, Protocol: "udp"}, |
2702 | + } |
2703 | + if !maskStateServerPorts { |
2704 | + expected = append(expected, instance.Port{Number: s.env.Config().StatePort(), Protocol: "tcp"}) |
2705 | + expected = append(expected, instance.Port{Number: s.env.Config().APIPort(), Protocol: "tcp"}) |
2706 | + instance.SortPorts(expected) |
2707 | + } |
2708 | + c.Check(ports, gc.DeepEquals, expected) |
2709 | } |
Reviewers: mp+210353_ code.launchpad. net,
Message:
Please take a look.
Description: availability
provider/azure: enable load-balancing/
This change is required to enable Availability Set
support for Azure. The existing implementation was
fundamentally incompatible with Availability Sets,
so the implementation has been significantly altered,
while maintaining backwards compatibility.
In the old implementation, instances are associated
one-to-one with Cloud Services. In the new
implementation, instances are associated with Roles
within a Cloud Service; there may be multiple Roles
within a single Cloud Service.
If a service is exposed, then its ports will be load-
balanced across each instance within the cloud service
which opens that port. We currently open ports 22 for
all instances, and the Mongo and API server ports for
state server instances. State server instances are
allocated to a single Cloud Service.
There are a number of incidental improvements, e.g.
using an updated version of DeleteHostedService which
destroys associated disks/blobs server side.
All instances are now allocated to an Availability Set
called "juju", which is scoped to the Cloud Service.
We do not currently group units into Cloud Services
automatically, however the ground work is laid to do
this. A follow-up will implement this work.
When we enable grouping, the same time we will have to
make changes to how we connect to machines via SSH,
either by proxying through an arbitrary machine in the
Cloud Service, or by not load balancing port 22 and
connecting to the machine's auto-allocated public port.
https:/ /code.launchpad .net/~axwalk/ juju-core/ azure-provider- roleisinstance/ +merge/ 210353
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/73910043/
Affected files (+1090, -888 lines): azure/environ. go azure/environ_ test.go azure/instance. go azure/instance_ test.go
A [revision details]
M dependencies.tsv
M provider/
M provider/
M provider/
M provider/