~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/networking.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:
6
6
import (
7
7
        "fmt"
8
8
        "net"
9
 
        "path"
 
9
        "strconv"
10
10
 
11
 
        "github.com/Azure/azure-sdk-for-go/arm/compute"
12
11
        "github.com/Azure/azure-sdk-for-go/arm/network"
13
 
        "github.com/Azure/go-autorest/autorest"
14
12
        "github.com/Azure/go-autorest/autorest/to"
15
13
        "github.com/juju/errors"
16
14
 
 
15
        "github.com/juju/juju/provider/azure/internal/armtemplates"
17
16
        "github.com/juju/juju/provider/azure/internal/iputils"
18
17
)
19
18
 
106
105
        }
107
106
)
108
107
 
109
 
func createInternalVirtualNetwork(
110
 
        callAPI callAPIFunc,
111
 
        client network.ManagementClient,
112
 
        subscriptionId, resourceGroup string,
113
 
        location string,
114
 
        tags map[string]string,
115
 
) (*network.VirtualNetwork, error) {
116
 
        addressPrefixes := []string{internalSubnetPrefix, controllerSubnetPrefix}
117
 
        vnet := network.VirtualNetwork{
118
 
                Location: to.StringPtr(location),
119
 
                Tags:     to.StringMapPtr(tags),
120
 
                Properties: &network.VirtualNetworkPropertiesFormat{
121
 
                        AddressSpace: &network.AddressSpace{&addressPrefixes},
122
 
                },
123
 
        }
124
 
        logger.Debugf("creating virtual network %q", internalNetworkName)
125
 
        vnetClient := network.VirtualNetworksClient{client}
126
 
        if err := callAPI(func() (autorest.Response, error) {
127
 
                return vnetClient.CreateOrUpdate(
128
 
                        resourceGroup, internalNetworkName, vnet,
129
 
                        nil, // abort channel
130
 
                )
131
 
        }); err != nil {
132
 
                return nil, errors.Annotatef(err, "creating virtual network %q", internalNetworkName)
133
 
        }
134
 
        vnet.ID = to.StringPtr(internalNetworkId(subscriptionId, resourceGroup))
135
 
        return &vnet, nil
136
 
}
137
 
 
138
 
func createInternalNetworkSecurityGroup(
139
 
        callAPI callAPIFunc,
140
 
        client network.ManagementClient,
141
 
        subscriptionId, resourceGroup string,
142
 
        location string,
143
 
        tags map[string]string,
144
 
        apiPort *int,
145
 
) (*network.SecurityGroup, error) {
 
108
// networkTemplateResources returns resource definitions for creating network
 
109
// resources shared by all machines in a model.
 
110
func networkTemplateResources(
 
111
        location string,
 
112
        envTags map[string]string,
 
113
        apiPort int,
 
114
) []armtemplates.Resource {
146
115
        // Create a network security group for the environment. There is only
147
116
        // one NSG per environment (there's a limit of 100 per subscription),
148
117
        // in which we manage rules for each exposed machine.
149
 
        securityRules := []network.SecurityRule{sshSecurityRule}
150
 
        if apiPort != nil {
151
 
                rule := apiSecurityRule
152
 
                properties := *rule.Properties
153
 
                properties.DestinationPortRange = to.StringPtr(fmt.Sprint(*apiPort))
154
 
                rule.Properties = &properties
155
 
                securityRules = append(securityRules, rule)
156
 
        }
157
 
        nsg := network.SecurityGroup{
158
 
                Location: to.StringPtr(location),
159
 
                Tags:     to.StringMapPtr(tags),
 
118
        apiSecurityRule := apiSecurityRule
 
119
        properties := *apiSecurityRule.Properties
 
120
        properties.DestinationPortRange = to.StringPtr(fmt.Sprint(apiPort))
 
121
        apiSecurityRule.Properties = &properties
 
122
        securityRules := []network.SecurityRule{sshSecurityRule, apiSecurityRule}
 
123
 
 
124
        // NOTE(axw) we create the API rule for all models to avoid having to
 
125
        // make queries when creating resources, making deployment faster and
 
126
        // more robust. The controller subnet is never used in non-controller
 
127
        // models, so there are no security implications.
 
128
        nsgId := fmt.Sprintf(
 
129
                `[resourceId('Microsoft.Network/networkSecurityGroups', '%s')]`,
 
130
                internalSecurityGroupName,
 
131
        )
 
132
        subnets := []network.Subnet{{
 
133
                Name: to.StringPtr(internalSubnetName),
 
134
                Properties: &network.SubnetPropertiesFormat{
 
135
                        AddressPrefix: to.StringPtr(internalSubnetPrefix),
 
136
                        NetworkSecurityGroup: &network.SecurityGroup{
 
137
                                ID: to.StringPtr(nsgId),
 
138
                        },
 
139
                },
 
140
        }, {
 
141
                Name: to.StringPtr(controllerSubnetName),
 
142
                Properties: &network.SubnetPropertiesFormat{
 
143
                        AddressPrefix: to.StringPtr(controllerSubnetPrefix),
 
144
                        NetworkSecurityGroup: &network.SecurityGroup{
 
145
                                ID: to.StringPtr(nsgId),
 
146
                        },
 
147
                },
 
148
        }}
 
149
 
 
150
        addressPrefixes := []string{internalSubnetPrefix, controllerSubnetPrefix}
 
151
        resources := []armtemplates.Resource{{
 
152
                APIVersion: network.APIVersion,
 
153
                Type:       "Microsoft.Network/networkSecurityGroups",
 
154
                Name:       internalSecurityGroupName,
 
155
                Location:   location,
 
156
                Tags:       envTags,
160
157
                Properties: &network.SecurityGroupPropertiesFormat{
161
158
                        SecurityRules: &securityRules,
162
159
                },
163
 
        }
164
 
        securityGroupClient := network.SecurityGroupsClient{client}
165
 
        securityGroupName := internalSecurityGroupName
166
 
        logger.Debugf("creating security group %q", securityGroupName)
167
 
        if err := callAPI(func() (autorest.Response, error) {
168
 
                return securityGroupClient.CreateOrUpdate(
169
 
                        resourceGroup, securityGroupName, nsg,
170
 
                        nil, // abort channel
171
 
                )
172
 
        }); err != nil {
173
 
                return nil, errors.Annotatef(err, "creating security group %q", securityGroupName)
174
 
        }
175
 
        nsg.ID = to.StringPtr(internalNetworkSecurityGroupId(subscriptionId, resourceGroup))
176
 
        return &nsg, nil
177
 
}
178
 
 
179
 
// createInternalSubnet creates an internal subnet for the specified resource group,
180
 
// within the specified virtual network.
181
 
//
182
 
// NOTE(axw) this method expects an up-to-date VirtualNetwork, and expects that are
183
 
// no concurrent subnet additions to the virtual network. At the moment we have only
184
 
// three places where we modify subnets: at bootstrap, when a new environment is
185
 
// created, and when an environment is destroyed.
186
 
func createInternalNetworkSubnet(
187
 
        callAPI callAPIFunc,
188
 
        client network.ManagementClient,
189
 
        subscriptionId, resourceGroup string,
190
 
        subnetName, addressPrefix string,
191
 
        location string,
192
 
        tags map[string]string,
193
 
) (*network.Subnet, error) {
194
 
        // Now create a subnet with the next available address prefix, and
195
 
        // associate the subnet with the NSG created above.
196
 
        subnet := network.Subnet{
197
 
                Properties: &network.SubnetPropertiesFormat{
198
 
                        AddressPrefix: to.StringPtr(addressPrefix),
199
 
                        NetworkSecurityGroup: &network.SecurityGroup{
200
 
                                ID: to.StringPtr(internalNetworkSecurityGroupId(
201
 
                                        subscriptionId, resourceGroup,
202
 
                                )),
203
 
                        },
204
 
                },
205
 
        }
206
 
        logger.Debugf("creating subnet %q (%s)", subnetName, addressPrefix)
207
 
        subnetClient := network.SubnetsClient{client}
208
 
        if err := callAPI(func() (autorest.Response, error) {
209
 
                return subnetClient.CreateOrUpdate(
210
 
                        resourceGroup, internalNetworkName, subnetName, subnet,
211
 
                        nil, // abort channel
212
 
                )
213
 
        }); err != nil {
214
 
                return nil, errors.Annotatef(err, "creating subnet %q", subnetName)
215
 
        }
216
 
        subnet.ID = to.StringPtr(internalNetworkSubnetId(
217
 
                subscriptionId, resourceGroup, subnetName,
218
 
        ))
219
 
        return &subnet, nil
220
 
}
221
 
 
222
 
type subnetParams struct {
223
 
        name   string
224
 
        prefix string
225
 
}
226
 
 
227
 
// newNetworkProfile creates a public IP and NIC(s) for the VM with the
228
 
// specified name. A separate NIC will be created for each subnet; the
229
 
// first subnet in the list will be associated with the primary NIC.
230
 
func newNetworkProfile(
231
 
        callAPI callAPIFunc,
232
 
        client network.ManagementClient,
233
 
        vmName string,
234
 
        controller bool,
235
 
        subscriptionId, resourceGroup string,
236
 
        location string,
237
 
        tags map[string]string,
238
 
) (*compute.NetworkProfile, error) {
239
 
        logger.Debugf("creating network profile for %q", vmName)
240
 
 
241
 
        // Create a public IP for the NIC. Public IP addresses are dynamic.
242
 
        logger.Debugf("- allocating public IP address")
243
 
        pipClient := network.PublicIPAddressesClient{client}
244
 
        publicIPAddress := network.PublicIPAddress{
245
 
                Location: to.StringPtr(location),
246
 
                Tags:     to.StringMapPtr(tags),
247
 
                Properties: &network.PublicIPAddressPropertiesFormat{
248
 
                        PublicIPAllocationMethod: network.Dynamic,
249
 
                },
250
 
        }
251
 
        publicIPAddressName := vmName + "-public-ip"
252
 
        if err := callAPI(func() (autorest.Response, error) {
253
 
                return pipClient.CreateOrUpdate(
254
 
                        resourceGroup, publicIPAddressName, publicIPAddress,
255
 
                        nil, // abort channel
256
 
                )
257
 
        }); err != nil {
258
 
                return nil, errors.Annotatef(err, "creating public IP address for %q", vmName)
259
 
        }
260
 
        publicIPAddress.ID = to.StringPtr(publicIPAddressId(
261
 
                subscriptionId, resourceGroup, publicIPAddressName,
262
 
        ))
263
 
 
264
 
        // Controller and non-controller machines are assigned to separate
265
 
        // subnets. This enables us to create controller-specific NSG rules
266
 
        // just by targeting the controller subnet.
267
 
        subnetName := internalSubnetName
268
 
        subnetPrefix := internalSubnetPrefix
269
 
        if controller {
270
 
                subnetName = controllerSubnetName
271
 
                subnetPrefix = controllerSubnetPrefix
272
 
        }
273
 
        subnetId := internalNetworkSubnetId(subscriptionId, resourceGroup, subnetName)
274
 
 
275
 
        // Determine the next available private IP address.
276
 
        nicClient := network.InterfacesClient{client}
277
 
        privateIPAddress, err := nextSubnetIPAddress(nicClient, resourceGroup, subnetPrefix)
278
 
        if err != nil {
279
 
                return nil, errors.Annotatef(err, "querying private IP addresses")
280
 
        }
281
 
 
282
 
        // Create a primary NIC for the machine. The private IP address needs
283
 
        // to be static so that we can create NSG rules that don't become
284
 
        // invalid.
285
 
        logger.Debugf("- creating primary NIC")
286
 
        ipConfigurations := []network.InterfaceIPConfiguration{{
287
 
                Name: to.StringPtr("primary"),
288
 
                Properties: &network.InterfaceIPConfigurationPropertiesFormat{
289
 
                        Primary:                   to.BoolPtr(true),
290
 
                        PrivateIPAddress:          to.StringPtr(privateIPAddress),
291
 
                        PrivateIPAllocationMethod: network.Static,
292
 
                        Subnet: &network.Subnet{ID: to.StringPtr(subnetId)},
293
 
                        PublicIPAddress: &network.PublicIPAddress{
294
 
                                ID: publicIPAddress.ID,
295
 
                        },
296
 
                },
297
 
        }}
298
 
        nicName := vmName + "-primary"
299
 
        nic := network.Interface{
300
 
                Location: to.StringPtr(location),
301
 
                Tags:     to.StringMapPtr(tags),
302
 
                Properties: &network.InterfacePropertiesFormat{
303
 
                        IPConfigurations: &ipConfigurations,
304
 
                },
305
 
        }
306
 
        if err := callAPI(func() (autorest.Response, error) {
307
 
                return nicClient.CreateOrUpdate(resourceGroup, nicName, nic, nil)
308
 
        }); err != nil {
309
 
                return nil, errors.Annotatef(err, "creating network interface for %q", vmName)
310
 
        }
311
 
 
312
 
        nics := []compute.NetworkInterfaceReference{{
313
 
                ID: to.StringPtr(networkInterfaceId(subscriptionId, resourceGroup, nicName)),
314
 
                Properties: &compute.NetworkInterfaceReferenceProperties{
315
 
                        Primary: to.BoolPtr(true),
316
 
                },
317
 
        }}
318
 
        return &compute.NetworkProfile{&nics}, nil
 
160
        }, {
 
161
                APIVersion: network.APIVersion,
 
162
                Type:       "Microsoft.Network/virtualNetworks",
 
163
                Name:       internalNetworkName,
 
164
                Location:   location,
 
165
                Tags:       envTags,
 
166
                Properties: &network.VirtualNetworkPropertiesFormat{
 
167
                        AddressSpace: &network.AddressSpace{&addressPrefixes},
 
168
                        Subnets:      &subnets,
 
169
                },
 
170
                DependsOn: []string{nsgId},
 
171
        }}
 
172
        return resources
319
173
}
320
174
 
321
175
// nextSecurityRulePriority returns the next available priority in the given
341
195
        )
342
196
}
343
197
 
344
 
// nextSubnetIPAddress returns the next available IP address in the given subnet.
345
 
func nextSubnetIPAddress(
346
 
        nicClient network.InterfacesClient,
347
 
        resourceGroup string,
348
 
        subnetPrefix string,
349
 
) (string, error) {
 
198
// machineSubnetIP returns the private IP address to use for the given
 
199
// subnet prefix.
 
200
func machineSubnetIP(subnetPrefix, machineId string) (net.IP, error) {
350
201
        _, ipnet, err := net.ParseCIDR(subnetPrefix)
351
202
        if err != nil {
352
 
                return "", errors.Annotate(err, "parsing subnet prefix")
353
 
        }
354
 
        results, err := nicClient.List(resourceGroup)
355
 
        if err != nil {
356
 
                return "", errors.Annotate(err, "listing NICs")
357
 
        }
358
 
        var ipsInUse []net.IP
359
 
        if results.Value != nil {
360
 
                ipsInUse = make([]net.IP, 0, len(*results.Value))
361
 
                for _, item := range *results.Value {
362
 
                        if item.Properties.IPConfigurations == nil {
363
 
                                continue
364
 
                        }
365
 
                        for _, ipConfiguration := range *item.Properties.IPConfigurations {
366
 
                                ip := net.ParseIP(to.String(ipConfiguration.Properties.PrivateIPAddress))
367
 
                                if ip != nil && ipnet.Contains(ip) {
368
 
                                        ipsInUse = append(ipsInUse, ip)
369
 
                                }
370
 
                        }
371
 
                }
372
 
        }
373
 
        ip, err := iputils.NextSubnetIP(ipnet, ipsInUse)
374
 
        if err != nil {
375
 
                return "", errors.Trace(err)
376
 
        }
377
 
        return ip.String(), nil
378
 
}
379
 
 
380
 
// internalNetworkSubnetId returns the Azure resource ID of the subnet with
381
 
// the specified name, within the internal network subnet for the specified
382
 
// resource group.
383
 
func internalNetworkSubnetId(subscriptionId, resourceGroup, subnetName string) string {
384
 
        return path.Join(
385
 
                internalNetworkId(subscriptionId, resourceGroup),
386
 
                "subnets", subnetName,
387
 
        )
388
 
}
389
 
 
390
 
func internalNetworkId(subscriptionId, resourceGroup string) string {
391
 
        return resourceId(subscriptionId, resourceGroup, "Microsoft.Network",
392
 
                "virtualNetworks", internalNetworkName,
393
 
        )
394
 
}
395
 
 
396
 
func internalNetworkSecurityGroupId(subscriptionId, resourceGroup string) string {
397
 
        return resourceId(subscriptionId, resourceGroup, "Microsoft.Network",
398
 
                "networkSecurityGroups", internalSecurityGroupName,
399
 
        )
400
 
}
401
 
 
402
 
func publicIPAddressId(subscriptionId, resourceGroup, publicIPAddressName string) string {
403
 
        return resourceId(subscriptionId, resourceGroup, "Microsoft.Network",
404
 
                "publicIPAddresses", publicIPAddressName,
405
 
        )
406
 
}
407
 
 
408
 
func networkInterfaceId(subscriptionId, resourceGroup, interfaceName string) string {
409
 
        return resourceId(subscriptionId, resourceGroup, "Microsoft.Network",
410
 
                "networkInterfaces", interfaceName,
411
 
        )
412
 
}
413
 
 
414
 
func resourceId(subscriptionId, resourceGroup, provider string, resourceId ...string) string {
415
 
        args := append([]string{
416
 
                "/subscriptions", subscriptionId,
417
 
                "resourceGroups", resourceGroup,
418
 
                "providers", provider,
419
 
        }, resourceId...)
420
 
        return path.Join(args...)
 
203
                return nil, errors.Annotate(err, "parsing subnet prefix")
 
204
        }
 
205
        n, err := strconv.Atoi(machineId)
 
206
        if err != nil {
 
207
                return nil, errors.Annotate(err, "parsing machine ID")
 
208
        }
 
209
        ip := iputils.NthSubnetIP(ipnet, n)
 
210
        if ip == nil {
 
211
                // TODO(axw) getting nil means we've cycled through roughly
 
212
                // 2^12 machines. To work around this limitation, we must
 
213
                // maintain an in-memory set of in-use IP addresses for each
 
214
                // subnet.
 
215
                return nil, errors.Errorf(
 
216
                        "no available IP addresses in %s", subnetPrefix,
 
217
                )
 
218
        }
 
219
        return ip, nil
421
220
}