~juju-qa/ubuntu/xenial/juju/2.0-rc2

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/provider/azure/environ_test.go

  • Committer: Nicholas Skaggs
  • Date: 2016-09-30 14:39:30 UTC
  • mfrom: (1.8.1)
  • Revision ID: nicholas.skaggs@canonical.com-20160930143930-vwwhrefh6ftckccy
import upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
        jc "github.com/juju/testing/checkers"
25
25
        "github.com/juju/utils"
26
26
        "github.com/juju/utils/arch"
27
 
        "github.com/juju/utils/series"
28
27
        gc "gopkg.in/check.v1"
29
28
        "gopkg.in/juju/names.v2"
30
29
 
39
38
        envtools "github.com/juju/juju/environs/tools"
40
39
        "github.com/juju/juju/instance"
41
40
        "github.com/juju/juju/provider/azure"
 
41
        "github.com/juju/juju/provider/azure/internal/armtemplates"
 
42
        "github.com/juju/juju/provider/azure/internal/azureauth"
42
43
        "github.com/juju/juju/provider/azure/internal/azuretesting"
43
44
        "github.com/juju/juju/testing"
44
45
        "github.com/juju/juju/tools"
45
46
        "github.com/juju/version"
46
47
)
47
48
 
 
49
const storageAccountName = "juju400d80004b1d0d06f00d"
 
50
 
 
51
var (
 
52
        quantalImageReference = compute.ImageReference{
 
53
                Publisher: to.StringPtr("Canonical"),
 
54
                Offer:     to.StringPtr("UbuntuServer"),
 
55
                Sku:       to.StringPtr("12.10"),
 
56
                Version:   to.StringPtr("latest"),
 
57
        }
 
58
        win2012ImageReference = compute.ImageReference{
 
59
                Publisher: to.StringPtr("MicrosoftWindowsServer"),
 
60
                Offer:     to.StringPtr("WindowsServer"),
 
61
                Sku:       to.StringPtr("2012-Datacenter"),
 
62
                Version:   to.StringPtr("latest"),
 
63
        }
 
64
        centos7ImageReference = compute.ImageReference{
 
65
                Publisher: to.StringPtr("OpenLogic"),
 
66
                Offer:     to.StringPtr("CentOS"),
 
67
                Sku:       to.StringPtr("7.1"),
 
68
                Version:   to.StringPtr("latest"),
 
69
        }
 
70
 
 
71
        sshPublicKeys = []compute.SSHPublicKey{{
 
72
                Path:    to.StringPtr("/home/ubuntu/.ssh/authorized_keys"),
 
73
                KeyData: to.StringPtr(testing.FakeAuthKeys),
 
74
        }}
 
75
        linuxOsProfile = compute.OSProfile{
 
76
                ComputerName:  to.StringPtr("machine-0"),
 
77
                CustomData:    to.StringPtr("<juju-goes-here>"),
 
78
                AdminUsername: to.StringPtr("ubuntu"),
 
79
                LinuxConfiguration: &compute.LinuxConfiguration{
 
80
                        DisablePasswordAuthentication: to.BoolPtr(true),
 
81
                        SSH: &compute.SSHConfiguration{
 
82
                                PublicKeys: &sshPublicKeys,
 
83
                        },
 
84
                },
 
85
        }
 
86
        windowsOsProfile = compute.OSProfile{
 
87
                ComputerName:  to.StringPtr("machine-0"),
 
88
                CustomData:    to.StringPtr("<juju-goes-here>"),
 
89
                AdminUsername: to.StringPtr("JujuAdministrator"),
 
90
                AdminPassword: to.StringPtr("sorandom"),
 
91
                WindowsConfiguration: &compute.WindowsConfiguration{
 
92
                        ProvisionVMAgent:       to.BoolPtr(true),
 
93
                        EnableAutomaticUpdates: to.BoolPtr(true),
 
94
                },
 
95
        }
 
96
)
 
97
 
48
98
type environSuite struct {
49
99
        testing.BaseSuite
50
100
 
54
104
        sender        azuretesting.Senders
55
105
        retryClock    mockClock
56
106
 
57
 
        controllerUUID                string
58
 
        envTags                       map[string]*string
59
 
        vmTags                        map[string]*string
60
 
        group                         *resources.ResourceGroup
61
 
        vmSizes                       *compute.VirtualMachineSizeListResult
62
 
        storageAccounts               []storage.Account
63
 
        storageNameAvailabilityResult *storage.CheckNameAvailabilityResult
64
 
        storageAccount                *storage.Account
65
 
        storageAccountKeys            *storage.AccountListKeysResult
66
 
        vnet                          *network.VirtualNetwork
67
 
        nsg                           *network.SecurityGroup
68
 
        internalSubnet                *network.Subnet
69
 
        controllerSubnet              *network.Subnet
70
 
        ubuntuServerSKUs              []compute.VirtualMachineImageResource
71
 
        publicIPAddress               *network.PublicIPAddress
72
 
        oldNetworkInterfaces          *network.InterfaceListResult
73
 
        newNetworkInterface           *network.Interface
74
 
        jujuAvailabilitySet           *compute.AvailabilitySet
75
 
        sshPublicKeys                 []compute.SSHPublicKey
76
 
        networkInterfaceReferences    []compute.NetworkInterfaceReference
77
 
        virtualMachine                *compute.VirtualMachine
78
 
        vmExtension                   *compute.VirtualMachineExtension
 
107
        controllerUUID     string
 
108
        envTags            map[string]*string
 
109
        vmTags             map[string]*string
 
110
        group              *resources.ResourceGroup
 
111
        vmSizes            *compute.VirtualMachineSizeListResult
 
112
        storageAccounts    []storage.Account
 
113
        storageAccount     *storage.Account
 
114
        storageAccountKeys *storage.AccountListKeysResult
 
115
        ubuntuServerSKUs   []compute.VirtualMachineImageResource
 
116
        deployment         *resources.Deployment
79
117
}
80
118
 
81
119
var _ = gc.Suite(&environSuite{})
89
127
 
90
128
        s.provider = newProvider(c, azure.ProviderConfig{
91
129
                Sender:           azuretesting.NewSerialSender(&s.sender),
92
 
                RequestInspector: requestRecorder(&s.requests),
 
130
                RequestInspector: azuretesting.RequestRecorder(&s.requests),
93
131
                NewStorageClient: s.storageClient.NewClient,
94
132
                RetryClock: &gitjujutesting.AutoAdvancingClock{
95
133
                        &s.retryClock, s.retryClock.Advance,
96
134
                },
 
135
                RandomWindowsAdminPassword:        func() string { return "sorandom" },
 
136
                InteractiveCreateServicePrincipal: azureauth.InteractiveCreateServicePrincipal,
97
137
        })
98
138
 
99
139
        s.controllerUUID = testing.ControllerTag.Id()
102
142
                "juju-controller-uuid": to.StringPtr(s.controllerUUID),
103
143
        }
104
144
        s.vmTags = map[string]*string{
105
 
                "juju-machine-name": to.StringPtr("machine-0"),
 
145
                "juju-model-uuid":      to.StringPtr(testing.ModelTag.Id()),
 
146
                "juju-controller-uuid": to.StringPtr(s.controllerUUID),
 
147
                "juju-machine-name":    to.StringPtr("machine-0"),
106
148
        }
107
149
 
108
150
        s.group = &resources.ResourceGroup{
123
165
        }}
124
166
        s.vmSizes = &compute.VirtualMachineSizeListResult{Value: &vmSizes}
125
167
 
126
 
        s.storageNameAvailabilityResult = &storage.CheckNameAvailabilityResult{
127
 
                NameAvailable: to.BoolPtr(true),
128
 
        }
129
 
 
130
168
        s.storageAccount = &storage.Account{
131
169
                Name: to.StringPtr("my-storage-account"),
132
170
                Type: to.StringPtr("Standard_LRS"),
133
171
                Tags: &s.envTags,
134
172
                Properties: &storage.AccountProperties{
135
173
                        PrimaryEndpoints: &storage.Endpoints{
136
 
                                Blob: to.StringPtr(fmt.Sprintf("https://%s.blob.storage.azurestack.local/", fakeStorageAccount)),
 
174
                                Blob: to.StringPtr(fmt.Sprintf("https://%s.blob.storage.azurestack.local/", storageAccountName)),
137
175
                        },
138
176
                        ProvisioningState: "Succeeded",
139
177
                },
148
186
                Keys: &keys,
149
187
        }
150
188
 
151
 
        addressPrefixes := []string{"192.168.0.0/20", "192.168.16.0/20"}
152
 
        s.vnet = &network.VirtualNetwork{
153
 
                ID:       to.StringPtr("juju-internal-network"),
154
 
                Name:     to.StringPtr("juju-internal-network"),
155
 
                Location: to.StringPtr("westus"),
156
 
                Tags:     &s.envTags,
157
 
                Properties: &network.VirtualNetworkPropertiesFormat{
158
 
                        AddressSpace:      &network.AddressSpace{&addressPrefixes},
159
 
                        ProvisioningState: to.StringPtr("Succeeded"),
160
 
                },
161
 
        }
162
 
 
163
 
        s.nsg = &network.SecurityGroup{
164
 
                ID: to.StringPtr(path.Join(
165
 
                        "/subscriptions", fakeSubscriptionId,
166
 
                        "resourceGroups", "juju-testenv-model-"+testing.ModelTag.Id(),
167
 
                        "providers/Microsoft.Network/networkSecurityGroups/juju-internal-nsg",
168
 
                )),
169
 
                Tags: &s.envTags,
170
 
                Properties: &network.SecurityGroupPropertiesFormat{
171
 
                        ProvisioningState: to.StringPtr("Succeeded"),
172
 
                },
173
 
        }
174
 
 
175
 
        s.internalSubnet = &network.Subnet{
176
 
                ID:   to.StringPtr("/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d/providers/Microsoft.Network/virtualNetworks/juju-internal-network/subnets/juju-internal-subnet"),
177
 
                Name: to.StringPtr("juju-internal-subnet"),
178
 
                Properties: &network.SubnetPropertiesFormat{
179
 
                        AddressPrefix: to.StringPtr("192.168.0.0/20"),
180
 
                        NetworkSecurityGroup: &network.SecurityGroup{
181
 
                                ID: to.StringPtr("/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d/providers/Microsoft.Network/networkSecurityGroups/juju-internal-nsg"),
182
 
                        },
183
 
                        ProvisioningState: to.StringPtr("Succeeded"),
184
 
                },
185
 
        }
186
 
 
187
 
        s.controllerSubnet = &network.Subnet{
188
 
                ID:   to.StringPtr("/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d/providers/Microsoft.Network/virtualNetworks/juju-internal-network/subnets/juju-controller-subnet"),
189
 
                Name: to.StringPtr("juju-controller-subnet"),
190
 
                Properties: &network.SubnetPropertiesFormat{
191
 
                        AddressPrefix: to.StringPtr("192.168.16.0/20"),
192
 
                        NetworkSecurityGroup: &network.SecurityGroup{
193
 
                                ID: to.StringPtr("/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d/providers/Microsoft.Network/networkSecurityGroups/juju-internal-nsg"),
194
 
                        },
195
 
                        ProvisioningState: to.StringPtr("Succeeded"),
196
 
                },
197
 
        }
198
 
 
199
189
        s.ubuntuServerSKUs = []compute.VirtualMachineImageResource{
200
190
                {Name: to.StringPtr("12.04-LTS")},
201
191
                {Name: to.StringPtr("12.10")},
205
195
                {Name: to.StringPtr("16.04-LTS")},
206
196
        }
207
197
 
208
 
        s.publicIPAddress = &network.PublicIPAddress{
209
 
                ID:       to.StringPtr("/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d/providers/Microsoft.Network/publicIPAddresses/machine-0-public-ip"),
210
 
                Name:     to.StringPtr("machine-0-public-ip"),
211
 
                Location: to.StringPtr("westus"),
212
 
                Tags:     &s.vmTags,
213
 
                Properties: &network.PublicIPAddressPropertiesFormat{
214
 
                        PublicIPAllocationMethod: network.Dynamic,
215
 
                        IPAddress:                to.StringPtr("1.2.3.4"),
216
 
                        ProvisioningState:        to.StringPtr("Succeeded"),
217
 
                },
218
 
        }
219
 
 
220
 
        // Existing IPs/NICs. These are the results of querying NICs so we
221
 
        // can tell which IP to allocate.
222
 
        oldIPConfigurations := []network.InterfaceIPConfiguration{{
223
 
                ID:   to.StringPtr("ip-configuration-0-id"),
224
 
                Name: to.StringPtr("ip-configuration-0"),
225
 
                Properties: &network.InterfaceIPConfigurationPropertiesFormat{
226
 
                        PrivateIPAddress:          to.StringPtr("192.168.0.4"),
227
 
                        PrivateIPAllocationMethod: network.Static,
228
 
                        Subnet:            s.internalSubnet,
229
 
                        ProvisioningState: to.StringPtr("Succeeded"),
230
 
                },
231
 
        }}
232
 
        oldNetworkInterfaces := []network.Interface{{
233
 
                ID:   to.StringPtr("network-interface-0-id"),
234
 
                Name: to.StringPtr("network-interface-0"),
235
 
                Properties: &network.InterfacePropertiesFormat{
236
 
                        IPConfigurations:  &oldIPConfigurations,
237
 
                        Primary:           to.BoolPtr(true),
238
 
                        ProvisioningState: to.StringPtr("Succeeded"),
239
 
                },
240
 
        }}
241
 
        s.oldNetworkInterfaces = &network.InterfaceListResult{
242
 
                Value: &oldNetworkInterfaces,
243
 
        }
244
 
 
245
 
        // The newly created IP/NIC.
246
 
        newIPConfigurations := []network.InterfaceIPConfiguration{{
247
 
                ID:   to.StringPtr("ip-configuration-1-id"),
248
 
                Name: to.StringPtr("primary"),
249
 
                Properties: &network.InterfaceIPConfigurationPropertiesFormat{
250
 
                        PrivateIPAddress:          to.StringPtr("192.168.0.5"),
251
 
                        PrivateIPAllocationMethod: network.Static,
252
 
                        Subnet:            s.internalSubnet,
253
 
                        PublicIPAddress:   s.publicIPAddress,
254
 
                        ProvisioningState: to.StringPtr("Succeeded"),
255
 
                },
256
 
        }}
257
 
        s.newNetworkInterface = &network.Interface{
258
 
                ID:       to.StringPtr("/subscriptions/22222222-2222-2222-2222-222222222222/resourceGroups/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d/providers/Microsoft.Network/networkInterfaces/machine-0-primary"),
259
 
                Name:     to.StringPtr("network-interface-1"),
260
 
                Location: to.StringPtr("westus"),
261
 
                Tags:     &s.vmTags,
262
 
                Properties: &network.InterfacePropertiesFormat{
263
 
                        IPConfigurations:  &newIPConfigurations,
264
 
                        ProvisioningState: to.StringPtr("Succeeded"),
265
 
                },
266
 
        }
267
 
 
268
 
        s.jujuAvailabilitySet = &compute.AvailabilitySet{
269
 
                ID:       to.StringPtr("juju-availability-set-id"),
270
 
                Name:     to.StringPtr("juju"),
271
 
                Location: to.StringPtr("westus"),
272
 
                Tags:     &s.envTags,
273
 
        }
274
 
 
275
 
        s.sshPublicKeys = []compute.SSHPublicKey{{
276
 
                Path:    to.StringPtr("/home/ubuntu/.ssh/authorized_keys"),
277
 
                KeyData: to.StringPtr(testing.FakeAuthKeys),
278
 
        }}
279
 
        s.networkInterfaceReferences = []compute.NetworkInterfaceReference{{
280
 
                ID: s.newNetworkInterface.ID,
281
 
                Properties: &compute.NetworkInterfaceReferenceProperties{
282
 
                        Primary: to.BoolPtr(true),
283
 
                },
284
 
        }}
285
 
        s.virtualMachine = &compute.VirtualMachine{
286
 
                ID:       to.StringPtr("machine-0-id"),
287
 
                Name:     to.StringPtr("machine-0"),
288
 
                Location: to.StringPtr("westus"),
289
 
                Tags:     &s.vmTags,
290
 
                Properties: &compute.VirtualMachineProperties{
291
 
                        HardwareProfile: &compute.HardwareProfile{
292
 
                                VMSize: "Standard_D1",
293
 
                        },
294
 
                        StorageProfile: &compute.StorageProfile{
295
 
                                ImageReference: &compute.ImageReference{
296
 
                                        Publisher: to.StringPtr("Canonical"),
297
 
                                        Offer:     to.StringPtr("UbuntuServer"),
298
 
                                        Sku:       to.StringPtr("12.10"),
299
 
                                        Version:   to.StringPtr("latest"),
300
 
                                },
301
 
                                OsDisk: &compute.OSDisk{
302
 
                                        Name:         to.StringPtr("machine-0"),
303
 
                                        CreateOption: compute.FromImage,
304
 
                                        Caching:      compute.ReadWrite,
305
 
                                        Vhd: &compute.VirtualHardDisk{
306
 
                                                URI: to.StringPtr(fmt.Sprintf(
307
 
                                                        "https://%s.blob.storage.azurestack.local/osvhds/machine-0.vhd",
308
 
                                                        fakeStorageAccount,
309
 
                                                )),
310
 
                                        },
311
 
                                        // 30 GiB is roughly 32 GB.
312
 
                                        DiskSizeGB: to.Int32Ptr(32),
313
 
                                },
314
 
                        },
315
 
                        OsProfile: &compute.OSProfile{
316
 
                                ComputerName:  to.StringPtr("machine-0"),
317
 
                                CustomData:    to.StringPtr("<juju-goes-here>"),
318
 
                                AdminUsername: to.StringPtr("ubuntu"),
319
 
                                LinuxConfiguration: &compute.LinuxConfiguration{
320
 
                                        DisablePasswordAuthentication: to.BoolPtr(true),
321
 
                                        SSH: &compute.SSHConfiguration{
322
 
                                                PublicKeys: &s.sshPublicKeys,
323
 
                                        },
324
 
                                },
325
 
                        },
326
 
                        NetworkProfile: &compute.NetworkProfile{
327
 
                                NetworkInterfaces: &s.networkInterfaceReferences,
328
 
                        },
329
 
                        AvailabilitySet:   &compute.SubResource{ID: s.jujuAvailabilitySet.ID},
330
 
                        ProvisioningState: to.StringPtr("Succeeded"),
331
 
                },
332
 
        }
333
 
 
334
 
        s.vmExtension = nil
 
198
        s.deployment = nil
335
199
}
336
200
 
337
201
func (s *environSuite) openEnviron(c *gc.C, attrs ...testing.Attrs) environs.Environ {
355
219
 
356
220
        // Force an explicit refresh of the access token, so it isn't done
357
221
        // implicitly during the tests.
358
 
        *sender = azuretesting.Senders{tokenRefreshSender()}
 
222
        *sender = azuretesting.Senders{
 
223
                discoverAuthSender(),
 
224
                tokenRefreshSender(),
 
225
        }
359
226
        err = azure.ForceTokenRefresh(env)
360
227
        c.Assert(err, jc.ErrorIsNil)
361
228
        return env
382
249
        })
383
250
        c.Assert(err, jc.ErrorIsNil)
384
251
 
385
 
        *sender = azuretesting.Senders{tokenRefreshSender()}
 
252
        *sender = azuretesting.Senders{
 
253
                discoverAuthSender(),
 
254
                tokenRefreshSender(),
 
255
        }
386
256
        err = env.PrepareForBootstrap(ctx)
387
257
        c.Assert(err, jc.ErrorIsNil)
388
258
        return env
396
266
                Endpoint:         "https://api.azurestack.local",
397
267
                IdentityEndpoint: "https://login.microsoftonline.com",
398
268
                StorageEndpoint:  "https://storage.azurestack.local",
399
 
                Credential:       fakeUserPassCredential(),
 
269
                Credential:       fakeServicePrincipalCredential(),
400
270
        }
401
271
}
402
272
 
410
280
        return tokenRefreshSender
411
281
}
412
282
 
413
 
func (s *environSuite) initResourceGroupSenders(controller bool) azuretesting.Senders {
 
283
func discoverAuthSender() *azuretesting.MockSender {
 
284
        const fakeTenantId = "11111111-1111-1111-1111-111111111111"
 
285
        sender := mocks.NewSender()
 
286
        resp := mocks.NewResponseWithStatus("", http.StatusUnauthorized)
 
287
        mocks.SetResponseHeaderValues(resp, "WWW-Authenticate", []string{
 
288
                fmt.Sprintf(
 
289
                        `authorization_uri="https://testing.invalid/%s"`,
 
290
                        fakeTenantId,
 
291
                ),
 
292
        })
 
293
        sender.AppendResponse(resp)
 
294
        return &azuretesting.MockSender{
 
295
                Sender:      sender,
 
296
                PathPattern: ".*/subscriptions/" + fakeSubscriptionId,
 
297
        }
 
298
}
 
299
 
 
300
func (s *environSuite) initResourceGroupSenders() azuretesting.Senders {
414
301
        resourceGroupName := "juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d"
415
 
        senders := azuretesting.Senders{
416
 
                s.makeSender(".*/resourcegroups/"+resourceGroupName, s.group),
417
 
                s.makeSender(".*/virtualNetworks/juju-internal-network", s.vnet),
418
 
                s.makeSender(".*/networkSecurityGroups/juju-internal-nsg", s.nsg),
419
 
                s.makeSender(".*/virtualNetworks/juju-internal-network/subnets/juju-internal-subnet", s.internalSubnet),
420
 
        }
421
 
        if controller {
422
 
                senders = append(senders,
423
 
                        s.makeSender(".*/virtualNetworks/juju-internal-network/subnets/juju-controller-subnet", s.controllerSubnet),
424
 
                )
425
 
        }
426
 
        senders = append(senders,
427
 
                s.makeSender(".*/checkNameAvailability", s.storageNameAvailabilityResult),
428
 
                s.makeSender(".*/storageAccounts/.*", s.storageAccount),
429
 
        )
 
302
        senders := azuretesting.Senders{s.makeSender(".*/resourcegroups/"+resourceGroupName, s.group)}
430
303
        return senders
431
304
}
432
305
 
433
306
func (s *environSuite) startInstanceSenders(controller bool) azuretesting.Senders {
434
 
        senders := azuretesting.Senders{
435
 
                s.vmSizesSender(),
436
 
                s.storageAccountsSender(),
437
 
        }
 
307
        senders := azuretesting.Senders{s.vmSizesSender()}
438
308
        if s.ubuntuServerSKUs != nil {
439
309
                senders = append(senders, s.makeSender(".*/Canonical/.*/UbuntuServer/skus", s.ubuntuServerSKUs))
440
310
        }
441
 
        senders = append(senders,
442
 
                s.makeSender(".*/publicIPAddresses/machine-0-public-ip", s.publicIPAddress),
443
 
                s.makeSender(".*/networkInterfaces", s.oldNetworkInterfaces),
444
 
                s.makeSender(".*/networkInterfaces/machine-0-primary", s.newNetworkInterface),
445
 
                s.makeSender(".*/availabilitySets/.*", s.jujuAvailabilitySet),
446
 
                s.makeSender(".*/virtualMachines/machine-0", s.virtualMachine),
447
 
        )
448
 
        if s.vmExtension != nil {
449
 
                senders = append(senders, s.makeSender(
450
 
                        ".*/virtualMachines/machine-0/extensions/JujuCustomScriptExtension", s.vmExtension),
451
 
                )
452
 
        }
 
311
        senders = append(senders, s.makeSender("/deployments/machine-0", s.deployment))
453
312
        return senders
454
313
}
455
314
 
469
328
        return s.makeSender(".*/vmSizes", s.vmSizes)
470
329
}
471
330
 
472
 
func (s *environSuite) storageAccountsSender() *azuretesting.MockSender {
473
 
        accounts := []storage.Account{*s.storageAccount}
474
 
        return s.makeSender(".*/storageAccounts", storage.AccountListResult{Value: &accounts})
 
331
func (s *environSuite) storageAccountSender() *azuretesting.MockSender {
 
332
        return s.makeSender(".*/storageAccounts/"+storageAccountName, s.storageAccount)
475
333
}
476
334
 
477
335
func (s *environSuite) storageAccountKeysSender() *azuretesting.MockSender {
487
345
func makeStartInstanceParams(c *gc.C, controllerUUID, series string) environs.StartInstanceParams {
488
346
        machineTag := names.NewMachineTag("0")
489
347
        apiInfo := &api.Info{
490
 
                Addrs:    []string{"localhost:246"},
 
348
                Addrs:    []string{"localhost:17777"},
491
349
                CACert:   testing.CACert,
492
350
                Password: "admin",
493
351
                Tag:      machineTag,
500
358
                series, apiInfo,
501
359
        )
502
360
        c.Assert(err, jc.ErrorIsNil)
 
361
        icfg.Tags = map[string]string{
 
362
                tags.JujuModel:      testing.ModelTag.Id(),
 
363
                tags.JujuController: controllerUUID,
 
364
        }
503
365
 
504
366
        return environs.StartInstanceParams{
505
367
                ControllerUUID: controllerUUID,
585
447
                RootDisk: &rootDisk,
586
448
                CpuCores: &cpuCores,
587
449
        })
588
 
        requests := s.assertStartInstanceRequests(c, s.requests)
589
 
        availabilitySetName := path.Base(requests.availabilitySet.URL.Path)
590
 
        c.Assert(availabilitySetName, gc.Equals, "juju")
 
450
        s.assertStartInstanceRequests(c, s.requests, assertStartInstanceRequestsParams{
 
451
                imageReference: &quantalImageReference,
 
452
                diskSizeGB:     32,
 
453
                osProfile:      &linuxOsProfile,
 
454
        })
591
455
}
592
456
 
593
457
func (s *environSuite) TestStartInstanceWindowsMinRootDisk(c *gc.C) {
594
458
        // The minimum OS disk size for Windows machines is 127GiB.
595
459
        cons := constraints.MustParse("root-disk=44G")
596
 
        s.testStartInstanceWindowsRootDisk(c, cons, 127*1024)
 
460
        s.testStartInstanceWindows(c, cons, 127*1024, 136)
597
461
}
598
462
 
599
463
func (s *environSuite) TestStartInstanceWindowsGrowableRootDisk(c *gc.C) {
600
464
        // The OS disk size may be grown larger than 127GiB.
601
465
        cons := constraints.MustParse("root-disk=200G")
602
 
        s.testStartInstanceWindowsRootDisk(c, cons, 200*1024)
 
466
        s.testStartInstanceWindows(c, cons, 200*1024, 214)
603
467
}
604
468
 
605
 
func (s *environSuite) testStartInstanceWindowsRootDisk(c *gc.C, cons constraints.Value, expect uint64) {
 
469
func (s *environSuite) testStartInstanceWindows(
 
470
        c *gc.C, cons constraints.Value,
 
471
        expect uint64, requestValue int,
 
472
) {
606
473
        // Starting a Windows VM, we should not expect an image query.
607
474
        s.PatchValue(&s.ubuntuServerSKUs, nil)
608
 
        s.PatchValue(&s.vmExtension, &compute.VirtualMachineExtension{})
609
475
 
610
476
        env := s.openEnviron(c)
611
477
        s.sender = s.startInstanceSenders(false)
616
482
        c.Assert(err, jc.ErrorIsNil)
617
483
        c.Assert(result, gc.NotNil)
618
484
        c.Assert(result.Hardware.RootDisk, jc.DeepEquals, &expect)
 
485
 
 
486
        vmExtensionSettings := map[string]interface{}{
 
487
                "commandToExecute": `` +
 
488
                        `move C:\AzureData\CustomData.bin C:\AzureData\CustomData.ps1 && ` +
 
489
                        `powershell.exe -ExecutionPolicy Unrestricted -File C:\AzureData\CustomData.ps1 && ` +
 
490
                        `del /q C:\AzureData\CustomData.ps1`,
 
491
        }
 
492
        s.assertStartInstanceRequests(c, s.requests, assertStartInstanceRequestsParams{
 
493
                imageReference: &win2012ImageReference,
 
494
                diskSizeGB:     requestValue,
 
495
                vmExtension: &compute.VirtualMachineExtensionProperties{
 
496
                        Publisher:               to.StringPtr("Microsoft.Compute"),
 
497
                        Type:                    to.StringPtr("CustomScriptExtension"),
 
498
                        TypeHandlerVersion:      to.StringPtr("1.4"),
 
499
                        AutoUpgradeMinorVersion: to.BoolPtr(true),
 
500
                        Settings:                &vmExtensionSettings,
 
501
                },
 
502
                osProfile: &windowsOsProfile,
 
503
        })
 
504
}
 
505
 
 
506
func (s *environSuite) TestStartInstanceCentOS(c *gc.C) {
 
507
        // Starting a CentOS VM, we should not expect an image query.
 
508
        s.PatchValue(&s.ubuntuServerSKUs, nil)
 
509
 
 
510
        env := s.openEnviron(c)
 
511
        s.sender = s.startInstanceSenders(false)
 
512
        s.requests = nil
 
513
        args := makeStartInstanceParams(c, s.controllerUUID, "centos7")
 
514
        _, err := env.StartInstance(args)
 
515
        c.Assert(err, jc.ErrorIsNil)
 
516
 
 
517
        vmExtensionSettings := map[string]interface{}{
 
518
                "commandToExecute": `bash -c 'base64 -d /var/lib/waagent/CustomData | bash'`,
 
519
        }
 
520
        s.assertStartInstanceRequests(c, s.requests, assertStartInstanceRequestsParams{
 
521
                imageReference: &centos7ImageReference,
 
522
                diskSizeGB:     32,
 
523
                vmExtension: &compute.VirtualMachineExtensionProperties{
 
524
                        Publisher:               to.StringPtr("Microsoft.OSTCExtensions"),
 
525
                        Type:                    to.StringPtr("CustomScriptForLinux"),
 
526
                        TypeHandlerVersion:      to.StringPtr("1.4"),
 
527
                        AutoUpgradeMinorVersion: to.BoolPtr(true),
 
528
                        Settings:                &vmExtensionSettings,
 
529
                },
 
530
                osProfile: &linuxOsProfile,
 
531
        })
619
532
}
620
533
 
621
534
func (s *environSuite) TestStartInstanceTooManyRequests(c *gc.C) {
646
559
        c.Assert(err, jc.ErrorIsNil)
647
560
 
648
561
        c.Assert(s.requests, gc.HasLen, numExpectedStartInstanceRequests+failures)
649
 
        s.assertStartInstanceRequests(c, s.requests[:numExpectedStartInstanceRequests])
 
562
        s.assertStartInstanceRequests(c, s.requests[:numExpectedStartInstanceRequests], assertStartInstanceRequestsParams{
 
563
                imageReference: &quantalImageReference,
 
564
                diskSizeGB:     32,
 
565
                osProfile:      &linuxOsProfile,
 
566
        })
650
567
 
651
568
        // The final requests should all be identical.
652
569
        for i := numExpectedStartInstanceRequests; i < numExpectedStartInstanceRequests+failures; i++ {
689
606
        s.sender = senders
690
607
 
691
608
        _, err := env.StartInstance(makeStartInstanceParams(c, s.controllerUUID, "quantal"))
692
 
        c.Assert(err, gc.ErrorMatches, `creating virtual machine "machine-0": creating virtual machine: max duration exceeded: .*`)
 
609
        c.Assert(err, gc.ErrorMatches, `creating virtual machine "machine-0": creating deployment "machine-0": max duration exceeded: .*`)
693
610
 
694
611
        s.retryClock.CheckCalls(c, []gitjujutesting.StubCall{
695
612
                {"After", []interface{}{5 * time.Second}},  // t0 + 5s
719
636
 
720
637
        _, err := env.StartInstance(params)
721
638
        c.Assert(err, jc.ErrorIsNil)
722
 
        requests := s.assertStartInstanceRequests(c, s.requests)
723
 
        availabilitySetName := path.Base(requests.availabilitySet.URL.Path)
724
 
        c.Assert(availabilitySetName, gc.Equals, "mysql")
725
 
}
726
 
 
727
 
const numExpectedStartInstanceRequests = 8
728
 
 
729
 
func (s *environSuite) assertStartInstanceRequests(c *gc.C, requests []*http.Request) startInstanceRequests {
730
 
        // The values defined here are the *request* values. They lack IDs,
731
 
        // Names (in most places), and ProvisioningStates. The values defined
732
 
        // on the suite are the *response* values; they are supersets of the
733
 
        // request values.
734
 
 
735
 
        publicIPAddress := &network.PublicIPAddress{
736
 
                Location: to.StringPtr("westus"),
737
 
                Tags:     &s.vmTags,
738
 
                Properties: &network.PublicIPAddressPropertiesFormat{
739
 
                        PublicIPAllocationMethod: network.Dynamic,
740
 
                },
 
639
        s.assertStartInstanceRequests(c, s.requests, assertStartInstanceRequestsParams{
 
640
                availabilitySetName: "mysql",
 
641
                imageReference:      &quantalImageReference,
 
642
                diskSizeGB:          32,
 
643
                osProfile:           &linuxOsProfile,
 
644
        })
 
645
}
 
646
 
 
647
const numExpectedStartInstanceRequests = 3
 
648
 
 
649
type assertStartInstanceRequestsParams struct {
 
650
        availabilitySetName string
 
651
        imageReference      *compute.ImageReference
 
652
        vmExtension         *compute.VirtualMachineExtensionProperties
 
653
        diskSizeGB          int
 
654
        osProfile           *compute.OSProfile
 
655
}
 
656
 
 
657
func (s *environSuite) assertStartInstanceRequests(
 
658
        c *gc.C,
 
659
        requests []*http.Request,
 
660
        args assertStartInstanceRequestsParams,
 
661
) startInstanceRequests {
 
662
        nsgId := `[resourceId('Microsoft.Network/networkSecurityGroups', 'juju-internal-nsg')]`
 
663
        securityRules := []network.SecurityRule{{
 
664
                Name: to.StringPtr("SSHInbound"),
 
665
                Properties: &network.SecurityRulePropertiesFormat{
 
666
                        Description:              to.StringPtr("Allow SSH access to all machines"),
 
667
                        Protocol:                 network.TCP,
 
668
                        SourceAddressPrefix:      to.StringPtr("*"),
 
669
                        SourcePortRange:          to.StringPtr("*"),
 
670
                        DestinationAddressPrefix: to.StringPtr("*"),
 
671
                        DestinationPortRange:     to.StringPtr("22"),
 
672
                        Access:                   network.Allow,
 
673
                        Priority:                 to.Int32Ptr(100),
 
674
                        Direction:                network.Inbound,
 
675
                },
 
676
        }, {
 
677
                Name: to.StringPtr("JujuAPIInbound"),
 
678
                Properties: &network.SecurityRulePropertiesFormat{
 
679
                        Description:              to.StringPtr("Allow API connections to controller machines"),
 
680
                        Protocol:                 network.TCP,
 
681
                        SourceAddressPrefix:      to.StringPtr("*"),
 
682
                        SourcePortRange:          to.StringPtr("*"),
 
683
                        DestinationAddressPrefix: to.StringPtr("192.168.16.0/20"),
 
684
                        DestinationPortRange:     to.StringPtr("17777"),
 
685
                        Access:                   network.Allow,
 
686
                        Priority:                 to.Int32Ptr(101),
 
687
                        Direction:                network.Inbound,
 
688
                },
 
689
        }}
 
690
        subnets := []network.Subnet{{
 
691
                Name: to.StringPtr("juju-internal-subnet"),
 
692
                Properties: &network.SubnetPropertiesFormat{
 
693
                        AddressPrefix: to.StringPtr("192.168.0.0/20"),
 
694
                        NetworkSecurityGroup: &network.SecurityGroup{
 
695
                                ID: to.StringPtr(nsgId),
 
696
                        },
 
697
                },
 
698
        }, {
 
699
                Name: to.StringPtr("juju-controller-subnet"),
 
700
                Properties: &network.SubnetPropertiesFormat{
 
701
                        AddressPrefix: to.StringPtr("192.168.16.0/20"),
 
702
                        NetworkSecurityGroup: &network.SecurityGroup{
 
703
                                ID: to.StringPtr(nsgId),
 
704
                        },
 
705
                },
 
706
        }}
 
707
 
 
708
        subnetName := "juju-internal-subnet"
 
709
        privateIPAddress := "192.168.0.4"
 
710
        if args.availabilitySetName == "juju-controller" {
 
711
                subnetName = "juju-controller-subnet"
 
712
                privateIPAddress = "192.168.16.4"
741
713
        }
742
 
        newIPConfigurations := []network.InterfaceIPConfiguration{{
 
714
        subnetId := fmt.Sprintf(
 
715
                `[concat(resourceId('Microsoft.Network/virtualNetworks', 'juju-internal-network'), '/subnets/%s')]`,
 
716
                subnetName,
 
717
        )
 
718
 
 
719
        publicIPAddressId := `[resourceId('Microsoft.Network/publicIPAddresses', 'machine-0-public-ip')]`
 
720
 
 
721
        ipConfigurations := []network.InterfaceIPConfiguration{{
743
722
                Name: to.StringPtr("primary"),
744
723
                Properties: &network.InterfaceIPConfigurationPropertiesFormat{
745
724
                        Primary:                   to.BoolPtr(true),
746
 
                        PrivateIPAddress:          to.StringPtr("192.168.0.5"),
 
725
                        PrivateIPAddress:          to.StringPtr(privateIPAddress),
747
726
                        PrivateIPAllocationMethod: network.Static,
748
 
                        Subnet: &network.Subnet{
749
 
                                ID: s.internalSubnet.ID,
750
 
                        },
 
727
                        Subnet: &network.Subnet{ID: to.StringPtr(subnetId)},
751
728
                        PublicIPAddress: &network.PublicIPAddress{
752
 
                                ID: s.publicIPAddress.ID,
 
729
                                ID: to.StringPtr(publicIPAddressId),
753
730
                        },
754
731
                },
755
732
        }}
756
 
        newNetworkInterface := &network.Interface{
757
 
                Location: to.StringPtr("westus"),
758
 
                Tags:     &s.vmTags,
 
733
 
 
734
        nicId := `[resourceId('Microsoft.Network/networkInterfaces', 'machine-0-primary')]`
 
735
        nics := []compute.NetworkInterfaceReference{{
 
736
                ID: to.StringPtr(nicId),
 
737
                Properties: &compute.NetworkInterfaceReferenceProperties{
 
738
                        Primary: to.BoolPtr(true),
 
739
                },
 
740
        }}
 
741
        vmDependsOn := []string{
 
742
                nicId,
 
743
                `[resourceId('Microsoft.Storage/storageAccounts', '` + storageAccountName + `')]`,
 
744
        }
 
745
 
 
746
        addressPrefixes := []string{"192.168.0.0/20", "192.168.16.0/20"}
 
747
        templateResources := []armtemplates.Resource{{
 
748
                APIVersion: network.APIVersion,
 
749
                Type:       "Microsoft.Network/networkSecurityGroups",
 
750
                Name:       "juju-internal-nsg",
 
751
                Location:   "westus",
 
752
                Tags:       to.StringMap(s.envTags),
 
753
                Properties: &network.SecurityGroupPropertiesFormat{
 
754
                        SecurityRules: &securityRules,
 
755
                },
 
756
        }, {
 
757
                APIVersion: network.APIVersion,
 
758
                Type:       "Microsoft.Network/virtualNetworks",
 
759
                Name:       "juju-internal-network",
 
760
                Location:   "westus",
 
761
                Tags:       to.StringMap(s.envTags),
 
762
                Properties: &network.VirtualNetworkPropertiesFormat{
 
763
                        AddressSpace: &network.AddressSpace{&addressPrefixes},
 
764
                        Subnets:      &subnets,
 
765
                },
 
766
                DependsOn: []string{nsgId},
 
767
        }, {
 
768
                APIVersion: storage.APIVersion,
 
769
                Type:       "Microsoft.Storage/storageAccounts",
 
770
                Name:       storageAccountName,
 
771
                Location:   "westus",
 
772
                Tags:       to.StringMap(s.envTags),
 
773
                StorageSku: &storage.Sku{
 
774
                        Name: storage.SkuName("Standard_LRS"),
 
775
                },
 
776
        }}
 
777
 
 
778
        var availabilitySetSubResource *compute.SubResource
 
779
        if args.availabilitySetName != "" {
 
780
                availabilitySetId := fmt.Sprintf(
 
781
                        `[resourceId('Microsoft.Compute/availabilitySets','%s')]`,
 
782
                        args.availabilitySetName,
 
783
                )
 
784
                templateResources = append(templateResources, armtemplates.Resource{
 
785
                        APIVersion: compute.APIVersion,
 
786
                        Type:       "Microsoft.Compute/availabilitySets",
 
787
                        Name:       args.availabilitySetName,
 
788
                        Location:   "westus",
 
789
                        Tags:       to.StringMap(s.envTags),
 
790
                })
 
791
                availabilitySetSubResource = &compute.SubResource{
 
792
                        ID: to.StringPtr(availabilitySetId),
 
793
                }
 
794
                vmDependsOn = append([]string{availabilitySetId}, vmDependsOn...)
 
795
        }
 
796
 
 
797
        templateResources = append(templateResources, []armtemplates.Resource{{
 
798
                APIVersion: network.APIVersion,
 
799
                Type:       "Microsoft.Network/publicIPAddresses",
 
800
                Name:       "machine-0-public-ip",
 
801
                Location:   "westus",
 
802
                Tags:       to.StringMap(s.vmTags),
 
803
                Properties: &network.PublicIPAddressPropertiesFormat{
 
804
                        PublicIPAllocationMethod: network.Dynamic,
 
805
                },
 
806
        }, {
 
807
                APIVersion: network.APIVersion,
 
808
                Type:       "Microsoft.Network/networkInterfaces",
 
809
                Name:       "machine-0-primary",
 
810
                Location:   "westus",
 
811
                Tags:       to.StringMap(s.vmTags),
759
812
                Properties: &network.InterfacePropertiesFormat{
760
 
                        IPConfigurations: &newIPConfigurations,
761
 
                },
762
 
        }
763
 
        jujuAvailabilitySet := &compute.AvailabilitySet{
764
 
                Location: to.StringPtr("westus"),
765
 
                Tags:     &s.envTags,
766
 
        }
767
 
        virtualMachine := &compute.VirtualMachine{
768
 
                Name:     to.StringPtr("machine-0"),
769
 
                Location: to.StringPtr("westus"),
770
 
                Tags:     &s.vmTags,
 
813
                        IPConfigurations: &ipConfigurations,
 
814
                },
 
815
                DependsOn: []string{
 
816
                        publicIPAddressId,
 
817
                        `[resourceId('Microsoft.Network/virtualNetworks', 'juju-internal-network')]`,
 
818
                },
 
819
        }, {
 
820
                APIVersion: compute.APIVersion,
 
821
                Type:       "Microsoft.Compute/virtualMachines",
 
822
                Name:       "machine-0",
 
823
                Location:   "westus",
 
824
                Tags:       to.StringMap(s.vmTags),
771
825
                Properties: &compute.VirtualMachineProperties{
772
826
                        HardwareProfile: &compute.HardwareProfile{
773
827
                                VMSize: "Standard_D1",
774
828
                        },
775
829
                        StorageProfile: &compute.StorageProfile{
776
 
                                ImageReference: &compute.ImageReference{
777
 
                                        Publisher: to.StringPtr("Canonical"),
778
 
                                        Offer:     to.StringPtr("UbuntuServer"),
779
 
                                        Sku:       to.StringPtr("12.10"),
780
 
                                        Version:   to.StringPtr("latest"),
781
 
                                },
 
830
                                ImageReference: args.imageReference,
782
831
                                OsDisk: &compute.OSDisk{
783
832
                                        Name:         to.StringPtr("machine-0"),
784
833
                                        CreateOption: compute.FromImage,
785
834
                                        Caching:      compute.ReadWrite,
786
835
                                        Vhd: &compute.VirtualHardDisk{
787
836
                                                URI: to.StringPtr(fmt.Sprintf(
788
 
                                                        "https://%s.blob.storage.azurestack.local/osvhds/machine-0.vhd",
789
 
                                                        fakeStorageAccount,
 
837
                                                        `[concat(reference(resourceId('Microsoft.Storage/storageAccounts', '%s'), '%s').primaryEndpoints.blob, 'osvhds/machine-0.vhd')]`,
 
838
                                                        storageAccountName, storage.APIVersion,
790
839
                                                )),
791
840
                                        },
792
 
                                        // 30 GiB is roughly 32 GB.
793
 
                                        DiskSizeGB: to.Int32Ptr(32),
794
 
                                },
795
 
                        },
796
 
                        OsProfile: &compute.OSProfile{
797
 
                                ComputerName:  to.StringPtr("machine-0"),
798
 
                                CustomData:    to.StringPtr("<juju-goes-here>"),
799
 
                                AdminUsername: to.StringPtr("ubuntu"),
800
 
                                LinuxConfiguration: &compute.LinuxConfiguration{
801
 
                                        DisablePasswordAuthentication: to.BoolPtr(true),
802
 
                                        SSH: &compute.SSHConfiguration{
803
 
                                                PublicKeys: &s.sshPublicKeys,
804
 
                                        },
805
 
                                },
806
 
                        },
807
 
                        NetworkProfile: &compute.NetworkProfile{
808
 
                                NetworkInterfaces: &s.networkInterfaceReferences,
809
 
                        },
810
 
                        AvailabilitySet: &compute.SubResource{ID: s.jujuAvailabilitySet.ID},
 
841
                                        DiskSizeGB: to.Int32Ptr(int32(args.diskSizeGB)),
 
842
                                },
 
843
                        },
 
844
                        OsProfile:       args.osProfile,
 
845
                        NetworkProfile:  &compute.NetworkProfile{&nics},
 
846
                        AvailabilitySet: availabilitySetSubResource,
 
847
                },
 
848
                DependsOn: vmDependsOn,
 
849
        }}...)
 
850
        if args.vmExtension != nil {
 
851
                templateResources = append(templateResources, armtemplates.Resource{
 
852
                        APIVersion: compute.APIVersion,
 
853
                        Type:       "Microsoft.Compute/virtualMachines/extensions",
 
854
                        Name:       "machine-0/JujuCustomScriptExtension",
 
855
                        Location:   "westus",
 
856
                        Tags:       to.StringMap(s.vmTags),
 
857
                        Properties: args.vmExtension,
 
858
                        DependsOn:  []string{"Microsoft.Compute/virtualMachines/machine-0"},
 
859
                })
 
860
        }
 
861
        templateMap := map[string]interface{}{
 
862
                "$schema":        "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
 
863
                "contentVersion": "1.0.0.0",
 
864
                "resources":      templateResources,
 
865
        }
 
866
        deployment := &resources.Deployment{
 
867
                &resources.DeploymentProperties{
 
868
                        Template: &templateMap,
 
869
                        Mode:     resources.Incremental,
811
870
                },
812
871
        }
813
872
 
814
873
        // Validate HTTP request bodies.
815
 
        c.Assert(requests, gc.HasLen, numExpectedStartInstanceRequests)
816
 
        c.Assert(requests[0].Method, gc.Equals, "GET") // vmSizes
817
 
        c.Assert(requests[1].Method, gc.Equals, "GET") // storage accounts
818
 
        c.Assert(requests[2].Method, gc.Equals, "GET") // skus
819
 
        c.Assert(requests[3].Method, gc.Equals, "PUT")
820
 
        assertRequestBody(c, requests[3], publicIPAddress)
821
 
        c.Assert(requests[4].Method, gc.Equals, "GET") // list NICs (to choose private IP address)
822
 
        c.Assert(requests[5].Method, gc.Equals, "PUT") // create NIC
823
 
        assertRequestBody(c, requests[5], newNetworkInterface)
824
 
        c.Assert(requests[6].Method, gc.Equals, "PUT") // create availability set
825
 
        assertRequestBody(c, requests[6], jujuAvailabilitySet)
826
 
        c.Assert(requests[7].Method, gc.Equals, "PUT") // create VM
827
 
        assertCreateVirtualMachineRequestBody(c, requests[7], virtualMachine)
828
 
 
829
 
        return startInstanceRequests{
830
 
                vmSizes:          requests[0],
831
 
                storageAccounts:  requests[1],
832
 
                skus:             requests[2],
833
 
                publicIPAddress:  requests[3],
834
 
                nics:             requests[4],
835
 
                networkInterface: requests[5],
836
 
                availabilitySet:  requests[6],
837
 
                virtualMachine:   requests[7],
 
874
        var startInstanceRequests startInstanceRequests
 
875
        if args.vmExtension != nil {
 
876
                // It must be Windows or CentOS, so
 
877
                // there should be no image query.
 
878
                c.Assert(requests, gc.HasLen, numExpectedStartInstanceRequests-1)
 
879
                c.Assert(requests[0].Method, gc.Equals, "GET") // vmSizes
 
880
                c.Assert(requests[1].Method, gc.Equals, "PUT") // create deployment
 
881
                startInstanceRequests.vmSizes = requests[0]
 
882
                startInstanceRequests.deployment = requests[1]
 
883
        } else {
 
884
                c.Assert(requests, gc.HasLen, numExpectedStartInstanceRequests)
 
885
                c.Assert(requests[0].Method, gc.Equals, "GET") // vmSizes
 
886
                c.Assert(requests[1].Method, gc.Equals, "GET") // skus
 
887
                c.Assert(requests[2].Method, gc.Equals, "PUT") // create deployment
 
888
                startInstanceRequests.vmSizes = requests[0]
 
889
                startInstanceRequests.skus = requests[1]
 
890
                startInstanceRequests.deployment = requests[2]
838
891
        }
839
 
}
840
 
 
841
 
func assertCreateVirtualMachineRequestBody(c *gc.C, req *http.Request, expect *compute.VirtualMachine) {
842
 
        // CustomData is non-deterministic, so don't compare it.
 
892
 
 
893
        // Marshal/unmarshal the deployment we expect, so it's in map form.
 
894
        var expected resources.Deployment
 
895
        data, err := json.Marshal(&deployment)
 
896
        c.Assert(err, jc.ErrorIsNil)
 
897
        err = json.Unmarshal(data, &expected)
 
898
        c.Assert(err, jc.ErrorIsNil)
 
899
 
 
900
        // Check that we send what we expect. CustomData is non-deterministic,
 
901
        // so don't compare it.
843
902
        // TODO(axw) shouldn't CustomData be deterministic? Look into this.
844
 
        var virtualMachine compute.VirtualMachine
845
 
        unmarshalRequestBody(c, req, &virtualMachine)
846
 
        c.Assert(to.String(virtualMachine.Properties.OsProfile.CustomData), gc.Not(gc.HasLen), 0)
847
 
        virtualMachine.Properties.OsProfile.CustomData = to.StringPtr("<juju-goes-here>")
848
 
        c.Assert(&virtualMachine, jc.DeepEquals, expect)
 
903
        var actual resources.Deployment
 
904
        unmarshalRequestBody(c, startInstanceRequests.deployment, &actual)
 
905
        c.Assert(actual.Properties, gc.NotNil)
 
906
        c.Assert(actual.Properties.Template, gc.NotNil)
 
907
        resources := (*actual.Properties.Template)["resources"].([]interface{})
 
908
        c.Assert(resources, gc.HasLen, len(templateResources))
 
909
 
 
910
        vmResourceIndex := len(resources) - 1
 
911
        if args.vmExtension != nil {
 
912
                vmResourceIndex--
 
913
        }
 
914
        vmResource := resources[vmResourceIndex].(map[string]interface{})
 
915
        vmResourceProperties := vmResource["properties"].(map[string]interface{})
 
916
        osProfile := vmResourceProperties["osProfile"].(map[string]interface{})
 
917
        osProfile["customData"] = "<juju-goes-here>"
 
918
        c.Assert(actual, jc.DeepEquals, expected)
 
919
 
 
920
        return startInstanceRequests
849
921
}
850
922
 
851
923
type startInstanceRequests struct {
852
 
        vmSizes          *http.Request
853
 
        storageAccounts  *http.Request
854
 
        subnet           *http.Request
855
 
        skus             *http.Request
856
 
        publicIPAddress  *http.Request
857
 
        nics             *http.Request
858
 
        networkInterface *http.Request
859
 
        availabilitySet  *http.Request
860
 
        virtualMachine   *http.Request
 
924
        vmSizes    *http.Request
 
925
        skus       *http.Request
 
926
        deployment *http.Request
861
927
}
862
928
 
863
929
func (s *environSuite) TestBootstrap(c *gc.C) {
866
932
        ctx := envtesting.BootstrapContext(c)
867
933
        env := prepareForBootstrap(c, ctx, s.provider, &s.sender)
868
934
 
869
 
        s.sender = s.initResourceGroupSenders(true)
 
935
        s.sender = s.initResourceGroupSenders()
870
936
        s.sender = append(s.sender, s.startInstanceSenders(true)...)
871
937
        s.requests = nil
872
938
        result, err := env.Bootstrap(
873
939
                ctx, environs.BootstrapParams{
874
940
                        ControllerConfig: testing.FakeControllerConfig(),
875
 
                        AvailableTools:   makeToolsList(series.LatestLts()),
 
941
                        AvailableTools:   makeToolsList("quantal"),
 
942
                        BootstrapSeries:  "quantal",
876
943
                },
877
944
        )
878
945
        c.Assert(err, jc.ErrorIsNil)
879
946
        c.Assert(result.Arch, gc.Equals, "amd64")
880
 
        c.Assert(result.Series, gc.Equals, series.LatestLts())
881
 
 
882
 
        c.Assert(len(s.requests), gc.Equals, 15)
883
 
 
884
 
        c.Assert(s.requests[0].Method, gc.Equals, "PUT")  // resource group
885
 
        c.Assert(s.requests[1].Method, gc.Equals, "PUT")  // create vnet
886
 
        c.Assert(s.requests[2].Method, gc.Equals, "PUT")  // create network security group
887
 
        c.Assert(s.requests[3].Method, gc.Equals, "PUT")  // create subnet (internal)
888
 
        c.Assert(s.requests[4].Method, gc.Equals, "PUT")  // create subnet (controller)
889
 
        c.Assert(s.requests[5].Method, gc.Equals, "POST") // check storage account name
890
 
        c.Assert(s.requests[6].Method, gc.Equals, "PUT")  // create storage account
891
 
 
892
 
        s.group.Properties = nil
893
 
        assertRequestBody(c, s.requests[0], &s.group)
894
 
 
895
 
        s.vnet.ID = nil
896
 
        s.vnet.Name = nil
897
 
        s.vnet.Properties.ProvisioningState = nil
898
 
        assertRequestBody(c, s.requests[1], s.vnet)
899
 
 
900
 
        securityRules := []network.SecurityRule{{
901
 
                Name: to.StringPtr("SSHInbound"),
902
 
                Properties: &network.SecurityRulePropertiesFormat{
903
 
                        Description:              to.StringPtr("Allow SSH access to all machines"),
904
 
                        Protocol:                 network.TCP,
905
 
                        SourceAddressPrefix:      to.StringPtr("*"),
906
 
                        SourcePortRange:          to.StringPtr("*"),
907
 
                        DestinationAddressPrefix: to.StringPtr("*"),
908
 
                        DestinationPortRange:     to.StringPtr("22"),
909
 
                        Access:                   network.Allow,
910
 
                        Priority:                 to.Int32Ptr(100),
911
 
                        Direction:                network.Inbound,
912
 
                },
913
 
        }, {
914
 
                Name: to.StringPtr("JujuAPIInbound"),
915
 
                Properties: &network.SecurityRulePropertiesFormat{
916
 
                        Description:              to.StringPtr("Allow API connections to controller machines"),
917
 
                        Protocol:                 network.TCP,
918
 
                        SourceAddressPrefix:      to.StringPtr("*"),
919
 
                        SourcePortRange:          to.StringPtr("*"),
920
 
                        DestinationAddressPrefix: to.StringPtr("192.168.16.0/20"),
921
 
                        DestinationPortRange:     to.StringPtr("17777"),
922
 
                        Access:                   network.Allow,
923
 
                        Priority:                 to.Int32Ptr(101),
924
 
                        Direction:                network.Inbound,
925
 
                },
926
 
        }}
927
 
        assertRequestBody(c, s.requests[2], &network.SecurityGroup{
928
 
                Location: to.StringPtr("westus"),
929
 
                Tags:     s.nsg.Tags,
930
 
                Properties: &network.SecurityGroupPropertiesFormat{
931
 
                        SecurityRules: &securityRules,
932
 
                },
933
 
        })
934
 
 
935
 
        s.internalSubnet.ID = nil
936
 
        s.internalSubnet.Name = nil
937
 
        s.internalSubnet.Properties.ProvisioningState = nil
938
 
        assertRequestBody(c, s.requests[3], s.internalSubnet)
939
 
        s.controllerSubnet.ID = nil
940
 
        s.controllerSubnet.Name = nil
941
 
        s.controllerSubnet.Properties.ProvisioningState = nil
942
 
        assertRequestBody(c, s.requests[4], s.controllerSubnet)
943
 
 
944
 
        assertRequestBody(c, s.requests[5], &storage.AccountCheckNameAvailabilityParameters{
945
 
                Name: to.StringPtr(fakeStorageAccount),
946
 
                Type: to.StringPtr("Microsoft.Storage/storageAccounts"),
947
 
        })
948
 
 
949
 
        assertRequestBody(c, s.requests[6], &storage.AccountCreateParameters{
950
 
                Location: to.StringPtr("westus"),
951
 
                Tags:     s.storageAccount.Tags,
952
 
                Sku: &storage.Sku{
953
 
                        Name: storage.StandardLRS,
954
 
                },
 
947
        c.Assert(result.Series, gc.Equals, "quantal")
 
948
 
 
949
        c.Assert(len(s.requests), gc.Equals, numExpectedStartInstanceRequests+1)
 
950
        s.vmTags[tags.JujuIsController] = to.StringPtr("true")
 
951
        s.assertStartInstanceRequests(c, s.requests[1:], assertStartInstanceRequestsParams{
 
952
                availabilitySetName: "juju-controller",
 
953
                imageReference:      &quantalImageReference,
 
954
                diskSizeGB:          32,
 
955
                osProfile:           &linuxOsProfile,
955
956
        })
956
957
}
957
958
 
968
969
 
969
970
func (s *environSuite) TestStopInstancesNotFound(c *gc.C) {
970
971
        env := s.openEnviron(c)
971
 
        sender := mocks.NewSender()
972
 
        sender.AppendResponse(mocks.NewResponseWithStatus(
973
 
                "vm not found", http.StatusNotFound,
974
 
        ))
975
 
        s.sender = azuretesting.Senders{sender, sender, sender}
 
972
        sender0 := mocks.NewSender()
 
973
        sender0.AppendResponse(mocks.NewResponseWithStatus(
 
974
                "vm not found", http.StatusNotFound,
 
975
        ))
 
976
        sender1 := mocks.NewSender()
 
977
        sender1.AppendResponse(mocks.NewResponseWithStatus(
 
978
                "vm not found", http.StatusNotFound,
 
979
        ))
 
980
        s.sender = azuretesting.Senders{sender0, sender1}
976
981
        err := env.StopInstances("a", "b")
977
982
        c.Assert(err, jc.ErrorIsNil)
978
983
}
980
985
func (s *environSuite) TestStopInstances(c *gc.C) {
981
986
        env := s.openEnviron(c)
982
987
 
983
 
        // Security group has rules for machine-0 but not machine-1, and
984
 
        // has a rule that doesn't match either.
 
988
        // Security group has rules for machine-0, as well as a rule that doesn't match.
985
989
        nsg := makeSecurityGroup(
986
990
                makeSecurityRule("machine-0-80", "192.168.0.4", "80"),
987
991
                makeSecurityRule("machine-0-1000-2000", "192.168.0.4", "1000-2000"),
995
999
        nic0 := makeNetworkInterface("nic-0", "machine-0", nic0IPConfiguration)
996
1000
 
997
1001
        s.sender = azuretesting.Senders{
998
 
                s.networkInterfacesSender(
999
 
                        nic0,
1000
 
                        makeNetworkInterface("nic-1", "machine-1"),
1001
 
                        makeNetworkInterface("nic-2", "machine-1"),
1002
 
                ),
1003
 
                s.virtualMachinesSender(makeVirtualMachine("machine-0")),
1004
 
                s.publicIPAddressesSender(
1005
 
                        makePublicIPAddress("pip-0", "machine-0", "1.2.3.4"),
1006
 
                ),
1007
 
                s.storageAccountsSender(),
 
1002
                s.makeSender(".*/deployments/machine-0/cancel", nil), // POST
 
1003
                s.storageAccountSender(),
1008
1004
                s.storageAccountKeysSender(),
 
1005
                s.networkInterfacesSender(nic0),
 
1006
                s.publicIPAddressesSender(makePublicIPAddress("pip-0", "machine-0", "1.2.3.4")),
1009
1007
                s.makeSender(".*/virtualMachines/machine-0", nil),                                                 // DELETE
1010
1008
                s.makeSender(".*/networkSecurityGroups/juju-internal-nsg", nsg),                                   // GET
1011
1009
                s.makeSender(".*/networkSecurityGroups/juju-internal-nsg/securityRules/machine-0-80", nil),        // DELETE
1012
1010
                s.makeSender(".*/networkSecurityGroups/juju-internal-nsg/securityRules/machine-0-1000-2000", nil), // DELETE
1013
 
                s.makeSender(".*/networkInterfaces/nic-0", nic0),                                                  // PUT
 
1011
                s.makeSender(".*/networkInterfaces/nic-0", nil),                                                   // DELETE
1014
1012
                s.makeSender(".*/publicIPAddresses/pip-0", nil),                                                   // DELETE
1015
 
                s.makeSender(".*/networkInterfaces/nic-0", nil),                                                   // DELETE
1016
 
                s.makeSender(".*/virtualMachines/machine-1", nil),                                                 // DELETE
1017
 
                s.makeSender(".*/networkSecurityGroups/juju-internal-nsg", nsg),                                   // GET
1018
 
                s.makeSender(".*/networkInterfaces/nic-1", nil),                                                   // DELETE
1019
 
                s.makeSender(".*/networkInterfaces/nic-2", nil),                                                   // DELETE
 
1013
                s.makeSender(".*/deployments/machine-0", nil),                                                     // DELETE
1020
1014
        }
1021
 
        err := env.StopInstances("machine-0", "machine-1", "machine-2")
 
1015
        err := env.StopInstances("machine-0")
1022
1016
        c.Assert(err, jc.ErrorIsNil)
1023
1017
 
1024
1018
        s.storageClient.CheckCallNames(c,
1025
 
                "NewClient", "DeleteBlobIfExists", "DeleteBlobIfExists",
 
1019
                "NewClient", "DeleteBlobIfExists",
1026
1020
        )
1027
1021
        s.storageClient.CheckCall(c, 1, "DeleteBlobIfExists", "osvhds", "machine-0")
1028
 
        s.storageClient.CheckCall(c, 2, "DeleteBlobIfExists", "osvhds", "machine-1")
 
1022
}
 
1023
 
 
1024
func (s *environSuite) TestStopInstancesMultiple(c *gc.C) {
 
1025
        env := s.openEnviron(c)
 
1026
 
 
1027
        vmDeleteSender0 := s.makeSender(".*/virtualMachines/machine-[01]", nil)
 
1028
        vmDeleteSender1 := s.makeSender(".*/virtualMachines/machine-[01]", nil)
 
1029
        vmDeleteSender0.SetError(errors.New("blargh"))
 
1030
        vmDeleteSender1.SetError(errors.New("blargh"))
 
1031
 
 
1032
        s.sender = azuretesting.Senders{
 
1033
                s.makeSender(".*/deployments/machine-[01]/cancel", nil), // POST
 
1034
                s.makeSender(".*/deployments/machine-[01]/cancel", nil), // POST
 
1035
 
 
1036
                // We should only query the NICs, public IPs, and storage
 
1037
                // account/keys, regardless of how many instances are deleted.
 
1038
                s.storageAccountSender(),
 
1039
                s.storageAccountKeysSender(),
 
1040
                s.networkInterfacesSender(),
 
1041
                s.publicIPAddressesSender(),
 
1042
 
 
1043
                vmDeleteSender0,
 
1044
                vmDeleteSender1,
 
1045
        }
 
1046
        err := env.StopInstances("machine-0", "machine-1")
 
1047
        c.Assert(err, gc.ErrorMatches, `deleting instance "machine-[01]":.*blargh`)
 
1048
}
 
1049
 
 
1050
func (s *environSuite) TestStopInstancesDeploymentNotFound(c *gc.C) {
 
1051
        env := s.openEnviron(c)
 
1052
 
 
1053
        cancelSender := mocks.NewSender()
 
1054
        cancelSender.AppendResponse(mocks.NewResponseWithStatus(
 
1055
                "deployment not found", http.StatusNotFound,
 
1056
        ))
 
1057
        s.sender = azuretesting.Senders{cancelSender}
 
1058
        err := env.StopInstances("machine-0")
 
1059
        c.Assert(err, jc.ErrorIsNil)
1029
1060
}
1030
1061
 
1031
1062
func (s *environSuite) TestStopInstancesStorageAccountNoKeys(c *gc.C) {
1032
1063
        s.PatchValue(&s.storageAccountKeys.Keys, nil)
1033
 
        s.testStopInstancesStorageKeysError(c, "getting storage account key: storage account keys not found")
 
1064
        s.testStopInstancesStorageAccountNotFound(c)
1034
1065
}
1035
1066
 
1036
1067
func (s *environSuite) TestStopInstancesStorageAccountNoFullKey(c *gc.C) {
1037
1068
        keys := *s.storageAccountKeys.Keys
1038
1069
        s.PatchValue(&keys[0].Permissions, storage.READ)
1039
 
        s.testStopInstancesStorageKeysError(c, `getting storage account key: storage account key with "FULL" permission not found`)
 
1070
        s.testStopInstancesStorageAccountNotFound(c)
1040
1071
}
1041
1072
 
1042
 
func (s *environSuite) testStopInstancesStorageKeysError(c *gc.C, expect string) {
 
1073
func (s *environSuite) testStopInstancesStorageAccountNotFound(c *gc.C) {
1043
1074
        env := s.openEnviron(c)
1044
 
 
1045
 
        nic0IPConfiguration := makeIPConfiguration("192.168.0.4")
1046
 
        nic0IPConfiguration.Properties.PublicIPAddress = &network.PublicIPAddress{}
1047
 
        nic0 := makeNetworkInterface("nic-0", "machine-0", nic0IPConfiguration)
1048
1075
        s.sender = azuretesting.Senders{
1049
 
                s.networkInterfacesSender(nic0),
1050
 
                s.virtualMachinesSender(makeVirtualMachine("machine-0")),
1051
 
                s.publicIPAddressesSender(makePublicIPAddress("pip-0", "machine-0", "1.2.3.4")),
1052
 
                s.storageAccountsSender(),
 
1076
                s.makeSender("/deployments/machine-0", s.deployment), // Cancel
 
1077
                s.storageAccountSender(),
1053
1078
                s.storageAccountKeysSender(),
1054
 
        }
1055
 
 
1056
 
        err := env.StopInstances("machine-0")
1057
 
        c.Assert(err, gc.ErrorMatches, expect)
 
1079
                s.networkInterfacesSender(),                                                     // GET: no NICs
 
1080
                s.publicIPAddressesSender(),                                                     // GET: no public IPs
 
1081
                s.makeSender(".*/virtualMachines/machine-0", nil),                               // DELETE
 
1082
                s.makeSender(".*/networkSecurityGroups/juju-internal-nsg", makeSecurityGroup()), // GET: no rules
 
1083
                s.makeSender(".*/deployments/machine-0", nil),                                   // DELETE
 
1084
        }
 
1085
        err := env.StopInstances("machine-0")
 
1086
        c.Assert(err, jc.ErrorIsNil)
 
1087
}
 
1088
 
 
1089
func (s *environSuite) TestStopInstancesStorageAccountError(c *gc.C) {
 
1090
        env := s.openEnviron(c)
 
1091
        errorSender := s.storageAccountSender()
 
1092
        errorSender.SetError(errors.New("blargh"))
 
1093
        s.sender = azuretesting.Senders{
 
1094
                s.makeSender("/deployments/machine-0", s.deployment), // Cancel
 
1095
                errorSender,
 
1096
        }
 
1097
        err := env.StopInstances("machine-0")
 
1098
        c.Assert(err, gc.ErrorMatches, "getting storage account:.*blargh")
 
1099
}
 
1100
 
 
1101
func (s *environSuite) TestStopInstancesStorageAccountKeysError(c *gc.C) {
 
1102
        env := s.openEnviron(c)
 
1103
        errorSender := s.storageAccountKeysSender()
 
1104
        errorSender.SetError(errors.New("blargh"))
 
1105
        s.sender = azuretesting.Senders{
 
1106
                s.makeSender("/deployments/machine-0", s.deployment), // Cancel
 
1107
                s.storageAccountSender(),
 
1108
                errorSender,
 
1109
        }
 
1110
        err := env.StopInstances("machine-0")
 
1111
        c.Assert(err, gc.ErrorMatches, "getting storage account key:.*blargh")
1058
1112
}
1059
1113
 
1060
1114
func (s *environSuite) TestConstraintsValidatorUnsupported(c *gc.C) {