// Addresses is specified in the Instance interface. func (inst *azureInstance) Addresses() ([]jujunetwork.Address, error) { addresses := make([]jujunetwork.Address, 0, len(inst.networkInterfaces)+len(inst.publicIPAddresses)) for _, nic := range inst.networkInterfaces { if nic.Properties.IPConfigurations == nil { continue } for _, ipConfiguration := range *nic.Properties.IPConfigurations { privateIpAddress := ipConfiguration.Properties.PrivateIPAddress if privateIpAddress == nil { continue } addresses = append(addresses, jujunetwork.NewScopedAddress( to.String(privateIpAddress), jujunetwork.ScopeCloudLocal, )) } } for _, pip := range inst.publicIPAddresses { if pip.Properties.IPAddress == nil { continue } addresses = append(addresses, jujunetwork.NewScopedAddress( to.String(pip.Properties.IPAddress), jujunetwork.ScopePublic, )) } return addresses, nil }
// internalNetworkAddress returns the instance's jujunetwork.Address for the // internal virtual network. This address is used to identify the machine in // network security rules. func (inst *azureInstance) internalNetworkAddress() (jujunetwork.Address, error) { inst.env.mu.Lock() subscriptionId := inst.env.config.subscriptionId resourceGroup := inst.env.resourceGroup controllerResourceGroup := inst.env.controllerResourceGroup inst.env.mu.Unlock() internalSubnetId := internalSubnetId( resourceGroup, controllerResourceGroup, subscriptionId, ) for _, nic := range inst.networkInterfaces { if nic.Properties.IPConfigurations == nil { continue } for _, ipConfiguration := range *nic.Properties.IPConfigurations { if ipConfiguration.Properties.Subnet == nil { continue } if strings.ToLower(to.String(ipConfiguration.Properties.Subnet.ID)) != strings.ToLower(internalSubnetId) { continue } privateIpAddress := ipConfiguration.Properties.PrivateIPAddress if privateIpAddress == nil { continue } return jujunetwork.NewScopedAddress( to.String(privateIpAddress), jujunetwork.ScopeCloudLocal, ), nil } } return jujunetwork.Address{}, errors.NotFoundf("internal network address") }
func (v *azureVolumeSource) detachVolume( vm *compute.VirtualMachine, p storage.VolumeAttachmentParams, ) (updated bool) { dataDisksRoot := dataDiskVhdRoot(v.env.config.location, v.env.config.storageAccount) dataDiskName := p.VolumeId vhdURI := dataDisksRoot + dataDiskName + vhdExtension var dataDisks []compute.DataDisk if vm.Properties.StorageProfile.DataDisks != nil { dataDisks = *vm.Properties.StorageProfile.DataDisks } for i, disk := range dataDisks { if to.String(disk.Name) != p.VolumeId { continue } if to.String(disk.Vhd.URI) != vhdURI { continue } dataDisks = append(dataDisks[:i], dataDisks[i+1:]...) if len(dataDisks) == 0 { vm.Properties.StorageProfile.DataDisks = nil } else { *vm.Properties.StorageProfile.DataDisks = dataDisks } return true } return false }
// JobListResultPreparer prepares a request to retrieve the next set of results. It returns // nil if no more results exist. func (client JobListResult) JobListResultPreparer() (*http.Request, error) { if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 { return nil, nil } return autorest.Prepare(&http.Request{}, autorest.AsJSON(), autorest.AsGet(), autorest.WithBaseURL(to.String(client.NextLink))) }
// OperationResultCollectionPreparer prepares a request to retrieve the next set of results. It returns // nil if no more results exist. func (client OperationResultCollection) OperationResultCollectionPreparer() (*http.Request, error) { if client.Nextlink == nil || len(to.String(client.Nextlink)) <= 0 { return nil, nil } return autorest.Prepare(&http.Request{}, autorest.AsJSON(), autorest.AsGet(), autorest.WithBaseURL(to.String(client.Nextlink))) }
// VirtualMachineScaleSetListWithLinkResultPreparer prepares a request to retrieve the next set of results. It returns // nil if no more results exist. func (client VirtualMachineScaleSetListWithLinkResult) VirtualMachineScaleSetListWithLinkResultPreparer() (*http.Request, error) { if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 { return nil, nil } return autorest.Prepare(&http.Request{}, autorest.AsJSON(), autorest.AsGet(), autorest.WithBaseURL(to.String(client.NextLink))) }
// ResourceProviderOperationDetailListResultPreparer prepares a request to retrieve the next set of results. It returns // nil if no more results exist. func (client ResourceProviderOperationDetailListResult) ResourceProviderOperationDetailListResultPreparer() (*http.Request, error) { if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 { return nil, nil } return autorest.Prepare(&http.Request{}, autorest.AsJSON(), autorest.AsGet(), autorest.WithBaseURL(to.String(client.NextLink))) }
// SharedAccessAuthorizationRuleListResultPreparer prepares a request to retrieve the next set of results. It returns // nil if no more results exist. func (client SharedAccessAuthorizationRuleListResult) SharedAccessAuthorizationRuleListResultPreparer() (*http.Request, error) { if client.NextLink == nil || len(to.String(client.NextLink)) <= 0 { return nil, nil } return autorest.Prepare(&http.Request{}, autorest.AsJSON(), autorest.AsGet(), autorest.WithBaseURL(to.String(client.NextLink))) }
func (v *azureVolumeSource) attachVolume( vm *compute.VirtualMachine, p storage.VolumeAttachmentParams, ) (_ *storage.VolumeAttachment, updated bool, _ error) { dataDisksRoot := dataDiskVhdRoot(v.env.config.location, v.env.config.storageAccount) dataDiskName := p.VolumeId vhdURI := dataDisksRoot + dataDiskName + vhdExtension var dataDisks []compute.DataDisk if vm.Properties.StorageProfile.DataDisks != nil { dataDisks = *vm.Properties.StorageProfile.DataDisks } for _, disk := range dataDisks { if to.String(disk.Name) != p.VolumeId { continue } if to.String(disk.Vhd.URI) != vhdURI { continue } // Disk is already attached. volumeAttachment := &storage.VolumeAttachment{ p.Volume, p.Machine, storage.VolumeAttachmentInfo{ BusAddress: diskBusAddress(to.Int(disk.Lun)), }, } return volumeAttachment, false, nil } lun, err := nextAvailableLUN(vm) if err != nil { return nil, false, errors.Annotate(err, "choosing LUN") } dataDisk := compute.DataDisk{ Lun: to.IntPtr(lun), Name: to.StringPtr(dataDiskName), Vhd: &compute.VirtualHardDisk{to.StringPtr(vhdURI)}, Caching: compute.ReadWrite, CreateOption: compute.Attach, } dataDisks = append(dataDisks, dataDisk) vm.Properties.StorageProfile.DataDisks = &dataDisks volumeAttachment := storage.VolumeAttachment{ p.Volume, p.Machine, storage.VolumeAttachmentInfo{ BusAddress: diskBusAddress(lun), }, } return &volumeAttachment, true, nil }
func checkName(name string) { c, err := helpers.LoadCredentials() if err != nil { log.Fatalf("Error: %v", err) } ac := storage.NewAccountsClient(c["subscriptionID"]) spt, err := helpers.NewServicePrincipalTokenFromCredentials(c, azure.AzureResourceManagerScope) if err != nil { log.Fatalf("Error: %v", err) } ac.Authorizer = spt ac.Sender = autorest.CreateSender( autorest.WithLogging(log.New(os.Stdout, "sdk-example: ", log.LstdFlags))) ac.RequestInspector = withInspection() ac.ResponseInspector = byInspecting() cna, err := ac.CheckNameAvailability( storage.AccountCheckNameAvailabilityParameters{ Name: to.StringPtr(name), Type: to.StringPtr("Microsoft.Storage/storageAccounts")}) if err != nil { log.Fatalf("Error: %v", err) } else { if to.Bool(cna.NameAvailable) { fmt.Printf("The name '%s' is available\n", name) } else { fmt.Printf("The name '%s' is unavailable because %s\n", name, to.String(cna.Message)) } } }
// deleteInstanceNetworkSecurityRules deletes network security rules in the // internal network security group that correspond to the specified machine. // // This is expected to delete *all* security rules related to the instance, // i.e. both the ones opened by OpenPorts above, and the ones opened for API // access. func deleteInstanceNetworkSecurityRules( resourceGroup string, id instance.Id, nsgClient network.SecurityGroupsClient, securityRuleClient network.SecurityRulesClient, ) error { nsg, err := nsgClient.Get(resourceGroup, internalSecurityGroupName) if err != nil { return errors.Annotate(err, "querying network security group") } if nsg.Properties.SecurityRules == nil { return nil } prefix := instanceNetworkSecurityRulePrefix(id) for _, rule := range *nsg.Properties.SecurityRules { ruleName := to.String(rule.Name) if !strings.HasPrefix(ruleName, prefix) { continue } result, err := securityRuleClient.Delete( resourceGroup, internalSecurityGroupName, ruleName, ) if err != nil { if result.Response == nil || result.StatusCode != http.StatusNotFound { return errors.Annotatef(err, "deleting security rule %q", ruleName) } } } return nil }
// Status is specified in the Instance interface. func (inst *azureInstance) Status() string { // NOTE(axw) ideally we would use the power state, but that is only // available when using the "instance view". Instance view is only // delivered when explicitly requested, and you can only request it // when querying a single VM. This means the results of AllInstances // or Instances would have the instance view missing. return to.String(inst.Properties.ProvisioningState) }
func createStorageAccount( client storage.AccountsClient, accountType storage.AccountType, resourceGroup string, location string, tags map[string]string, accountNameGenerator func() string, ) (string, string, error) { logger.Debugf("creating storage account (finding available name)") const maxAttempts = 10 for remaining := maxAttempts; remaining > 0; remaining-- { accountName := accountNameGenerator() logger.Debugf("- checking storage account name %q", accountName) result, err := client.CheckNameAvailability( storage.AccountCheckNameAvailabilityParameters{ Name: to.StringPtr(accountName), // Azure is a little inconsistent with when Type is // required. It's required here. Type: to.StringPtr("Microsoft.Storage/storageAccounts"), }, ) if err != nil { return "", "", errors.Annotate(err, "checking account name availability") } if !to.Bool(result.NameAvailable) { logger.Debugf( "%q is not available (%v): %v", accountName, result.Reason, result.Message, ) continue } createParams := storage.AccountCreateParameters{ Location: to.StringPtr(location), Tags: toTagsPtr(tags), Properties: &storage.AccountPropertiesCreateParameters{ AccountType: accountType, }, } logger.Debugf("- creating %q storage account %q", accountType, accountName) // TODO(axw) account creation can fail if the account name is // available, but contains profanity. We should retry a set // number of times even if creating fails. if _, err := client.Create(resourceGroup, accountName, createParams); err != nil { return "", "", errors.Trace(err) } logger.Debugf("- listing storage account keys") listKeysResult, err := client.ListKeys(resourceGroup, accountName) if err != nil { return "", "", errors.Annotate(err, "listing storage account keys") } return accountName, to.String(listKeysResult.Key1), nil } return "", "", errors.New("could not find available storage account name") }
// nextSubnetIPAddress returns the next available IP address in the given subnet. func nextSubnetIPAddress( nicClient network.InterfacesClient, resourceGroup string, subnet *network.Subnet, ) (string, error) { _, ipnet, err := net.ParseCIDR(to.String(subnet.Properties.AddressPrefix)) if err != nil { return "", errors.Annotate(err, "parsing subnet prefix") } results, err := nicClient.List(resourceGroup) if err != nil { return "", errors.Annotate(err, "listing NICs") } // Azure reserves the first 4 addresses in the subnet. var ipsInUse []net.IP if results.Value != nil { ipsInUse = make([]net.IP, 0, len(*results.Value)) for _, item := range *results.Value { if item.Properties.IPConfigurations == nil { continue } for _, ipConfiguration := range *item.Properties.IPConfigurations { if to.String(ipConfiguration.Properties.Subnet.ID) != to.String(subnet.ID) { continue } ip := net.ParseIP(to.String(ipConfiguration.Properties.PrivateIPAddress)) if ip != nil { ipsInUse = append(ipsInUse, ip) } } } } ip, err := iputils.NextSubnetIP(ipnet, ipsInUse) if err != nil { return "", errors.Trace(err) } return ip.String(), nil }
func (s *environSuite) assertStartInstanceRequests(c *gc.C) startInstanceRequests { // Clear the fields that don't get sent in the request. s.publicIPAddress.ID = nil s.publicIPAddress.Name = nil s.publicIPAddress.Properties.IPAddress = nil s.newNetworkInterface.ID = nil s.newNetworkInterface.Name = nil (*s.newNetworkInterface.Properties.IPConfigurations)[0].ID = nil s.jujuAvailabilitySet.ID = nil s.jujuAvailabilitySet.Name = nil s.virtualMachine.ID = nil s.virtualMachine.Name = nil s.virtualMachine.Properties.ProvisioningState = nil // Validate HTTP request bodies. c.Assert(s.requests, gc.HasLen, 8) c.Assert(s.requests[0].Method, gc.Equals, "GET") // vmSizes c.Assert(s.requests[1].Method, gc.Equals, "GET") // juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d c.Assert(s.requests[2].Method, gc.Equals, "GET") // skus c.Assert(s.requests[3].Method, gc.Equals, "PUT") assertRequestBody(c, s.requests[3], s.publicIPAddress) c.Assert(s.requests[4].Method, gc.Equals, "GET") // NICs c.Assert(s.requests[5].Method, gc.Equals, "PUT") assertRequestBody(c, s.requests[5], s.newNetworkInterface) c.Assert(s.requests[6].Method, gc.Equals, "PUT") assertRequestBody(c, s.requests[6], s.jujuAvailabilitySet) c.Assert(s.requests[7].Method, gc.Equals, "PUT") // CustomData is non-deterministic, so don't compare it. // TODO(axw) shouldn't CustomData be deterministic? Look into this. var virtualMachine compute.VirtualMachine unmarshalRequestBody(c, s.requests[7], &virtualMachine) c.Assert(to.String(virtualMachine.Properties.OsProfile.CustomData), gc.Not(gc.HasLen), 0) virtualMachine.Properties.OsProfile.CustomData = to.StringPtr("<juju-goes-here>") c.Assert(&virtualMachine, jc.DeepEquals, s.virtualMachine) return startInstanceRequests{ vmSizes: s.requests[0], subnet: s.requests[1], skus: s.requests[2], publicIPAddress: s.requests[3], nics: s.requests[4], networkInterface: s.requests[5], availabilitySet: s.requests[6], virtualMachine: s.requests[7], } }
// ubuntuSKU returns the best SKU for the Canonical:UbuntuServer offering, // matching the given series. func ubuntuSKU(series, stream, location string, client compute.VirtualMachineImagesClient) (string, error) { seriesVersion, err := jujuseries.SeriesVersion(series) if err != nil { return "", errors.Trace(err) } logger.Debugf("listing SKUs: Location=%s, Publisher=%s, Offer=%s", location, ubuntuPublisher, ubuntuOffering) result, err := client.ListSkus(location, ubuntuPublisher, ubuntuOffering) if err != nil { return "", errors.Annotate(err, "listing Ubuntu SKUs") } if result.Value == nil || len(*result.Value) == 0 { return "", errors.NotFoundf("Ubuntu SKUs") } skuNamesByVersion := make(map[ubuntuVersion]string) var versions ubuntuVersions for _, result := range *result.Value { skuName := to.String(result.Name) if !strings.HasPrefix(skuName, seriesVersion) { logger.Debugf("ignoring SKU %q (does not match series %q)", skuName, series) continue } version, tag, err := parseUbuntuSKU(skuName) if err != nil { logger.Errorf("ignoring SKU %q (failed to parse: %s)", skuName, err) continue } var skuStream string switch tag { case "", "LTS": skuStream = imagemetadata.ReleasedStream case "DAILY", "DAILY-LTS": skuStream = dailyStream } if skuStream == "" || skuStream != stream { logger.Debugf("ignoring SKU %q (not in %q stream)", skuName, stream) continue } skuNamesByVersion[version] = skuName versions = append(versions, version) } if len(versions) == 0 { return "", errors.NotFoundf("Ubuntu SKUs for %s stream", stream) } sort.Sort(versions) bestVersion := versions[len(versions)-1] return skuNamesByVersion[bestVersion], nil }
func main() { name := "storage-account-name" c, err := helpers.LoadCredentials() if err != nil { log.Fatalf("Error: %v", err) } sid := c["subscriptionID"] tid := c["tenantID"] cid := c["clientID"] secret := c["clientSecret"] spt, err := azure.NewServicePrincipalToken(cid, secret, tid, azure.AzureResourceManagerScope) if err != nil { log.Fatalf("Error: %v", err) } arm := arm.NewClient(sid, spt) arm.RequestInspector = helpers.WithInspection() arm.ResponseInspector = helpers.ByInspecting() ac := arm.StorageAccounts() cna, err := ac.CheckNameAvailability( storage.AccountCheckNameAvailabilityParameters{ Name: to.StringPtr(name), Type: to.StringPtr("Microsoft.Storage/storageAccounts")}) if err != nil { log.Fatalf("Error: %v", err) } else { if to.Bool(cna.NameAvailable) { fmt.Printf("The name '%s' is available\n", name) } else { fmt.Printf("The name '%s' is unavailable because %s\n", name, to.String(cna.Message)) } } }
// updateVirtualMachines updates virtual machines in the given map by iterating // through the list of instance IDs in order, and updating each corresponding // virtual machine at most once. func (v *azureVolumeSource) updateVirtualMachines( virtualMachines map[instance.Id]*maybeVirtualMachine, instanceIds []instance.Id, ) ([]error, error) { results := make([]error, len(instanceIds)) vmsClient := compute.VirtualMachinesClient{v.env.compute} for i, instanceId := range instanceIds { vm, ok := virtualMachines[instanceId] if !ok { continue } if vm.err != nil { results[i] = vm.err continue } if _, err := vmsClient.CreateOrUpdate(v.env.resourceGroup, to.String(vm.vm.Name), *vm.vm); err != nil { results[i] = err vm.err = err continue } // successfully updated, don't update again delete(virtualMachines, instanceId) } return results, nil }
// deleteInstances deletes a virtual machine and all of the resources that // it owns, and any corresponding network security rules. func deleteInstance( inst *azureInstance, computeClient compute.ManagementClient, networkClient network.ManagementClient, storageClient internalazurestorage.Client, ) error { vmName := string(inst.Id()) vmClient := compute.VirtualMachinesClient{computeClient} nicClient := network.InterfacesClient{networkClient} nsgClient := network.SecurityGroupsClient{networkClient} securityRuleClient := network.SecurityRulesClient{networkClient} publicIPClient := network.PublicIPAddressesClient{networkClient} logger.Debugf("deleting instance %q", vmName) logger.Debugf("- deleting virtual machine") deleteResult, err := vmClient.Delete(inst.env.resourceGroup, vmName) if err != nil { if deleteResult.Response == nil || deleteResult.StatusCode != http.StatusNotFound { return errors.Annotate(err, "deleting virtual machine") } } // Delete the VM's OS disk VHD. logger.Debugf("- deleting OS VHD") blobClient := storageClient.GetBlobService() if _, err := blobClient.DeleteBlobIfExists(osDiskVHDContainer, vmName); err != nil { return errors.Annotate(err, "deleting OS VHD") } // Delete network security rules that refer to the VM. logger.Debugf("- deleting security rules") if err := deleteInstanceNetworkSecurityRules( inst.env.resourceGroup, inst.Id(), nsgClient, securityRuleClient, ); err != nil { return errors.Annotate(err, "deleting network security rules") } // Detach public IPs from NICs. This must be done before public // IPs can be deleted. In the future, VMs may not necessarily // have a public IP, so we don't use the presence of a public // IP to indicate the existence of an instance. logger.Debugf("- detaching public IP addresses") for _, nic := range inst.networkInterfaces { if nic.Properties.IPConfigurations == nil { continue } var detached bool for i, ipConfiguration := range *nic.Properties.IPConfigurations { if ipConfiguration.Properties.PublicIPAddress == nil { continue } ipConfiguration.Properties.PublicIPAddress = nil (*nic.Properties.IPConfigurations)[i] = ipConfiguration detached = true } if detached { if _, err := nicClient.CreateOrUpdate( inst.env.resourceGroup, to.String(nic.Name), nic, ); err != nil { return errors.Annotate(err, "detaching public IP addresses") } } } // Delete public IPs. logger.Debugf("- deleting public IPs") for _, pip := range inst.publicIPAddresses { pipName := to.String(pip.Name) logger.Tracef("deleting public IP %q", pipName) result, err := publicIPClient.Delete(inst.env.resourceGroup, pipName) if err != nil { if result.Response == nil || result.StatusCode != http.StatusNotFound { return errors.Annotate(err, "deleting public IP") } } } // Delete NICs. // // NOTE(axw) this *must* be deleted last, or we risk leaking resources. logger.Debugf("- deleting network interfaces") for _, nic := range inst.networkInterfaces { nicName := to.String(nic.Name) logger.Tracef("deleting NIC %q", nicName) result, err := nicClient.Delete(inst.env.resourceGroup, nicName) if err != nil { if result.Response == nil || result.StatusCode != http.StatusNotFound { return errors.Annotate(err, "deleting NIC") } } } return nil }
// createAvailabilitySet creates the availability set for a machine to use // if it doesn't already exist, and returns the availability set's ID. The // algorithm used for choosing the availability set is: // - if there is a distribution group, use the same availability set as // the instances in that group. Instances in the group may be in // different availability sets (when multiple services colocated on a // machine), so we pick one arbitrarily // - if there is no distribution group, create an availability name with // a name based on the value of the tags.JujuUnitsDeployed tag in vmTags, // if it exists // - if there are no units assigned to the machine, then use the "juju" // availability set func createAvailabilitySet( client compute.AvailabilitySetsClient, vmName, resourceGroup, location string, vmTags, envTags map[string]string, distributionGroupFunc func() ([]instance.Id, error), instancesFunc func([]instance.Id) ([]instance.Instance, error), ) (string, error) { logger.Debugf("selecting availability set for %q", vmName) // First we check if there's a distribution group, and if so, // use the availability set of the first instance we find in it. var instanceIds []instance.Id if distributionGroupFunc != nil { var err error instanceIds, err = distributionGroupFunc() if err != nil { return "", errors.Annotate( err, "querying distribution group", ) } } instances, err := instancesFunc(instanceIds) switch err { case nil, environs.ErrPartialInstances, environs.ErrNoInstances: default: return "", errors.Annotate( err, "querying distribution group instances", ) } for _, instance := range instances { if instance == nil { continue } instance := instance.(*azureInstance) availabilitySetSubResource := instance.Properties.AvailabilitySet if availabilitySetSubResource == nil || availabilitySetSubResource.ID == nil { continue } logger.Debugf("- selecting availability set of %q", instance.Name) return to.String(availabilitySetSubResource.ID), nil } // We'll have to create an availability set. Use the name of one of the // services assigned to the machine. availabilitySetName := "juju" if unitNames, ok := vmTags[tags.JujuUnitsDeployed]; ok { for _, unitName := range strings.Fields(unitNames) { if !names.IsValidUnit(unitName) { continue } serviceName, err := names.UnitService(unitName) if err != nil { return "", errors.Annotate( err, "getting service name", ) } availabilitySetName = serviceName break } } logger.Debugf("- creating availability set %q", availabilitySetName) availabilitySet, err := client.CreateOrUpdate( resourceGroup, availabilitySetName, compute.AvailabilitySet{ Location: to.StringPtr(location), // NOTE(axw) we do *not* want to use vmTags here, // because an availability set is shared by machines. Tags: toTagsPtr(envTags), }, ) if err != nil { return "", errors.Annotatef( err, "creating availability set %q", availabilitySetName, ) } return to.String(availabilitySet.ID), nil }
// Ports is specified in the Instance interface. func (inst *azureInstance) Ports(machineId string) (ports []jujunetwork.PortRange, err error) { inst.env.mu.Lock() nsgClient := network.SecurityGroupsClient{inst.env.network} inst.env.mu.Unlock() securityGroupName := internalSecurityGroupName nsg, err := nsgClient.Get(inst.env.resourceGroup, securityGroupName) if err != nil { return nil, errors.Annotate(err, "querying network security group") } if nsg.Properties.SecurityRules == nil { return nil, nil } vmName := resourceName(names.NewMachineTag(machineId)) prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName)) for _, rule := range *nsg.Properties.SecurityRules { if rule.Properties.Direction != network.Inbound { continue } if rule.Properties.Access != network.Allow { continue } if to.Int(rule.Properties.Priority) <= securityRuleInternalMax { continue } if !strings.HasPrefix(to.String(rule.Name), prefix) { continue } var portRange jujunetwork.PortRange if *rule.Properties.DestinationPortRange == "*" { portRange.FromPort = 0 portRange.ToPort = 65535 } else { portRange, err = jujunetwork.ParsePortRange( *rule.Properties.DestinationPortRange, ) if err != nil { return nil, errors.Annotatef( err, "parsing port range for security rule %q", to.String(rule.Name), ) } } var protocols []string switch rule.Properties.Protocol { case network.SecurityRuleProtocolTCP: protocols = []string{"tcp"} case network.SecurityRuleProtocolUDP: protocols = []string{"udp"} default: protocols = []string{"tcp", "udp"} } for _, protocol := range protocols { portRange.Protocol = protocol ports = append(ports, portRange) } } return ports, nil }
// createInternalSubnet creates an internal subnet for the specified resource group, // within the specified virtual network. // // NOTE(axw) this method expects an up-to-date VirtualNetwork, and expects that are // no concurrent subnet additions to the virtual network. At the moment we have only // three places where we modify subnets: at bootstrap, when a new environment is // created, and when an environment is destroyed. func createInternalSubnet( client network.ManagementClient, resourceGroup string, vnet *network.VirtualNetwork, location string, tags map[string]string, ) (*network.Subnet, error) { nextAddressPrefix := (*vnet.Properties.AddressSpace.AddressPrefixes)[0] if vnet.Properties.Subnets != nil { if len(*vnet.Properties.Subnets) == len(*vnet.Properties.AddressSpace.AddressPrefixes) { return nil, errors.Errorf( "no available address prefixes in vnet %q", to.String(vnet.Name), ) } addressPrefixesInUse := make(set.Strings) for _, subnet := range *vnet.Properties.Subnets { addressPrefixesInUse.Add(to.String(subnet.Properties.AddressPrefix)) } for _, addressPrefix := range *vnet.Properties.AddressSpace.AddressPrefixes { if !addressPrefixesInUse.Contains(addressPrefix) { nextAddressPrefix = addressPrefix break } } } // Create a network security group for the environment. There is only // one NSG per environment (there's a limit of 100 per subscription), // in which we manage rules for each exposed machine. securityRules := []network.SecurityRule{sshSecurityRule} securityGroupParams := network.SecurityGroup{ Location: to.StringPtr(location), Tags: toTagsPtr(tags), Properties: &network.SecurityGroupPropertiesFormat{ SecurityRules: &securityRules, }, } securityGroupClient := network.SecurityGroupsClient{client} securityGroupName := internalSecurityGroupName logger.Debugf("creating security group %q", securityGroupName) nsg, err := securityGroupClient.CreateOrUpdate( resourceGroup, securityGroupName, securityGroupParams, ) if err != nil { return nil, errors.Annotatef(err, "creating security group %q", securityGroupName) } // Now create a subnet with the next available address prefix, and // associate the subnet with the NSG created above. subnetName := internalSubnetName subnetParams := network.Subnet{ Properties: &network.SubnetPropertiesFormat{ AddressPrefix: to.StringPtr(nextAddressPrefix), NetworkSecurityGroup: &network.SubResource{nsg.ID}, }, } logger.Debugf("creating subnet %q (%s)", subnetName, nextAddressPrefix) subnetClient := network.SubnetsClient{client} subnet, err := subnetClient.CreateOrUpdate( resourceGroup, internalNetworkName, subnetName, subnetParams, ) if err != nil { return nil, errors.Annotatef(err, "creating subnet %q", subnetName) } return &subnet, nil }
// allInstances returns all of the instances in the given resource group, // and optionally ensures that each instance's addresses are up-to-date. func (env *azureEnviron) allInstances( resourceGroup string, refreshAddresses bool, ) ([]instance.Instance, error) { env.mu.Lock() vmClient := compute.VirtualMachinesClient{env.compute} nicClient := network.InterfacesClient{env.network} pipClient := network.PublicIPAddressesClient{env.network} env.mu.Unlock() // Due to how deleting instances works, we have to get creative about // listing instances. We list NICs and return an instance for each // unique value of the jujuMachineNameTag tag. // // The machine provisioner will call AllInstances so it can delete // unknown instances. StopInstances must delete VMs before NICs and // public IPs, because a VM cannot have less than 1 NIC. Thus, we can // potentially delete a VM but then fail to delete its NIC. nicsResult, err := nicClient.List(resourceGroup) if err != nil { if nicsResult.Response.Response != nil && nicsResult.StatusCode == http.StatusNotFound { // This will occur if the resource group does not // exist, e.g. in a fresh hosted environment. return nil, nil } return nil, errors.Trace(err) } if nicsResult.Value == nil || len(*nicsResult.Value) == 0 { return nil, nil } // Create an azureInstance for each VM. result, err := vmClient.List(resourceGroup) if err != nil { return nil, errors.Annotate(err, "listing virtual machines") } vmNames := make(set.Strings) var azureInstances []*azureInstance if result.Value != nil { azureInstances = make([]*azureInstance, len(*result.Value)) for i, vm := range *result.Value { inst := &azureInstance{vm, env, nil, nil} azureInstances[i] = inst vmNames.Add(to.String(vm.Name)) } } // Create additional azureInstances for NICs without machines. See // comments above for rationale. This needs to happen before calling // setInstanceAddresses, so we still associate the NICs/PIPs. for _, nic := range *nicsResult.Value { vmName, ok := toTags(nic.Tags)[jujuMachineNameTag] if !ok || vmNames.Contains(vmName) { continue } vm := compute.VirtualMachine{ Name: to.StringPtr(vmName), Properties: &compute.VirtualMachineProperties{ ProvisioningState: to.StringPtr("Partially Deleted"), }, } inst := &azureInstance{vm, env, nil, nil} azureInstances = append(azureInstances, inst) vmNames.Add(to.String(vm.Name)) } if len(azureInstances) > 0 && refreshAddresses { if err := setInstanceAddresses( pipClient, resourceGroup, azureInstances, nicsResult, ); err != nil { return nil, errors.Trace(err) } } instances := make([]instance.Instance, len(azureInstances)) for i, inst := range azureInstances { instances[i] = inst } return instances, nil }
// newInstanceType creates an InstanceType based on a VirtualMachineSize. func newInstanceType(size compute.VirtualMachineSize) instances.InstanceType { // We're not doing real costs for now; just made-up, relative // costs, to ensure we choose the right VMs given matching // constraints. This was based on the pricing for West US, // and assumes that all regions have the same relative costs. // // DS is the same price as D, but is targeted at Premium Storage. // Likewise for GS and G. We put the premium storage variants // directly after their non-premium counterparts. machineSizeCost := []string{ "Standard_A0", "Standard_A1", "Standard_D1", "Standard_DS1", "Standard_D1_v2", "Standard_A2", "Standard_D2", "Standard_DS2", "Standard_D2_v2", "Standard_D11", "Standard_DS11", "Standard_D11_v2", "Standard_A3", "Standard_D3", "Standard_DS3", "Standard_D3_v2", "Standard_D12", "Standard_DS12", "Standard_D12_v2", "Standard_A5", // Yes, A5 is cheaper than A4. "Standard_A4", "Standard_A6", "Standard_G1", "Standard_GS1", "Standard_D4", "Standard_DS4", "Standard_D4_v2", "Standard_D13", "Standard_DS13", "Standard_D13_v2", "Standard_A7", "Standard_A10", "Standard_G2", "Standard_GS2", "Standard_D5_v2", "Standard_D14", "Standard_DS14", "Standard_D14_v2", "Standard_A8", "Standard_A11", "Standard_G3", "Standard_GS3", "Standard_A9", "Standard_G4", "Standard_GS4", "Standard_GS5", "Standard_G5", // Basic instances are less capable than standard // ones, so we don't want to be providing them as // a default. This is achieved by costing them at // a higher price, even though they are cheaper // in reality. "Basic_A0", "Basic_A1", "Basic_A2", "Basic_A3", "Basic_A4", } // Anything not in the list is more expensive that is in the list. cost := len(machineSizeCost) sizeName := to.String(size.Name) for i, name := range machineSizeCost { if sizeName == name { cost = i break } } if cost == len(machineSizeCost) { logger.Warningf("found unknown VM size %q", sizeName) } vtype := "Hyper-V" return instances.InstanceType{ Id: sizeName, Name: sizeName, Arches: []string{arch.AMD64}, CpuCores: uint64(to.Int(size.NumberOfCores)), Mem: uint64(to.Int(size.MemoryInMB)), // NOTE(axw) size.OsDiskSizeInMB is the maximum root disk // size, but the actual disk size is limited to the size // of the image/VHD that the machine is backed by. The // Azure Resource Manager APIs do not provide a way of // determining the image size. // // All of the published images that we use are ~30GiB. RootDisk: uint64(29495), Cost: uint64(cost), VirtType: &vtype, // tags are not currently supported by azure } }
// Id is specified in the Instance interface. func (inst *azureInstance) Id() instance.Id { // Note: we use Name and not Id, since all VM operations are in // terms of the VM name (qualified by resource group). The ID is // an internal detail. return instance.Id(to.String(inst.VirtualMachine.Name)) }
// createInternalSubnet creates an internal subnet for the specified resource group, // within the specified virtual network. // // Subnets are tied to the resource group of the virtual network, so we must create // them all in the controller resource group. We create the network security group // for the subnet in the environment's resource group. // // NOTE(axw) this method expects an up-to-date VirtualNetwork, and expects that are // no concurrent subnet additions to the virtual network. At the moment we have only // three places where we modify subnets: at bootstrap, when a new environment is // created, and when an environment is destroyed. func createInternalSubnet( client network.ManagementClient, resourceGroup, controllerResourceGroup string, vnet *network.VirtualNetwork, location string, tags map[string]string, ) (*network.Subnet, error) { nextAddressPrefix := (*vnet.Properties.AddressSpace.AddressPrefixes)[0] if vnet.Properties.Subnets != nil { if len(*vnet.Properties.Subnets) == len(*vnet.Properties.AddressSpace.AddressPrefixes) { return nil, errors.Errorf( "no available address prefixes in vnet %q", to.String(vnet.Name), ) } addressPrefixesInUse := make(set.Strings) for _, subnet := range *vnet.Properties.Subnets { addressPrefixesInUse.Add(to.String(subnet.Properties.AddressPrefix)) } for _, addressPrefix := range *vnet.Properties.AddressSpace.AddressPrefixes { if !addressPrefixesInUse.Contains(addressPrefix) { nextAddressPrefix = addressPrefix break } } } // Create a network security group for the environment. There is only // one NSG per environment (there's a limit of 100 per subscription), // in which we manage rules for each exposed machine. securityRules := []network.SecurityRule{sshSecurityRule} securityGroupParams := network.SecurityGroup{ Location: to.StringPtr(location), Tags: toTagsPtr(tags), Properties: &network.SecurityGroupPropertiesFormat{ SecurityRules: &securityRules, }, } securityGroupClient := network.SecurityGroupsClient{client} securityGroupName := internalSecurityGroupName logger.Debugf("creating security group %q", securityGroupName) _, err := securityGroupClient.CreateOrUpdate( resourceGroup, securityGroupName, securityGroupParams, ) if err != nil { return nil, errors.Annotatef(err, "creating security group %q", securityGroupName) } // Now create a subnet with the next available address prefix. The // subnet must be created in the controller resource group, as it // must be co-located with the vnet. subnetName := resourceGroup subnetParams := network.Subnet{ Properties: &network.SubnetPropertiesFormat{ AddressPrefix: to.StringPtr(nextAddressPrefix), // NOTE(axw) we do NOT want to set the network security // group as default for the subnet, because that will // create a dependency from the controller resource // group to environment resource groups. Instead, we // set the NSG on NICs. }, } logger.Debugf("creating subnet %q (%s)", subnetName, nextAddressPrefix) subnetClient := network.SubnetsClient{client} subnet, err := subnetClient.CreateOrUpdate( controllerResourceGroup, internalNetworkName, subnetName, subnetParams, ) if err != nil { return nil, errors.Annotatef(err, "creating subnet %q", subnetName) } return &subnet, nil }
// OpenPorts is specified in the Instance interface. func (inst *azureInstance) OpenPorts(machineId string, ports []jujunetwork.PortRange) error { inst.env.mu.Lock() nsgClient := network.SecurityGroupsClient{inst.env.network} securityRuleClient := network.SecurityRulesClient{inst.env.network} inst.env.mu.Unlock() internalNetworkAddress, err := inst.internalNetworkAddress() if err != nil { return errors.Trace(err) } securityGroupName := internalSecurityGroupName nsg, err := nsgClient.Get(inst.env.resourceGroup, securityGroupName) if err != nil { return errors.Annotate(err, "querying network security group") } var securityRules []network.SecurityRule if nsg.Properties.SecurityRules != nil { securityRules = *nsg.Properties.SecurityRules } else { nsg.Properties.SecurityRules = &securityRules } // Create rules one at a time; this is necessary to avoid trampling // on changes made by the provisioner. We still record rules in the // NSG in memory, so we can easily tell which priorities are available. vmName := resourceName(names.NewMachineTag(machineId)) prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName)) for _, ports := range ports { ruleName := securityRuleName(prefix, ports) // Check if the rule already exists; OpenPorts must be idempotent. var found bool for _, rule := range securityRules { if to.String(rule.Name) == ruleName { found = true break } } if found { logger.Debugf("security rule %q already exists", ruleName) continue } logger.Debugf("creating security rule %q", ruleName) priority, err := nextSecurityRulePriority(nsg, securityRuleInternalMax+1, securityRuleMax) if err != nil { return errors.Annotatef(err, "getting security rule priority for %s", ports) } var protocol network.SecurityRuleProtocol switch ports.Protocol { case "tcp": protocol = network.SecurityRuleProtocolTCP case "udp": protocol = network.SecurityRuleProtocolUDP default: return errors.Errorf("invalid protocol %q", ports.Protocol) } var portRange string if ports.FromPort != ports.ToPort { portRange = fmt.Sprintf("%d-%d", ports.FromPort, ports.ToPort) } else { portRange = fmt.Sprint(ports.FromPort) } rule := network.SecurityRule{ Properties: &network.SecurityRulePropertiesFormat{ Description: to.StringPtr(ports.String()), Protocol: protocol, SourcePortRange: to.StringPtr("*"), DestinationPortRange: to.StringPtr(portRange), SourceAddressPrefix: to.StringPtr("*"), DestinationAddressPrefix: to.StringPtr(internalNetworkAddress.Value), Access: network.Allow, Priority: to.IntPtr(priority), Direction: network.Inbound, }, } if _, err := securityRuleClient.CreateOrUpdate( inst.env.resourceGroup, securityGroupName, ruleName, rule, ); err != nil { return errors.Annotatef(err, "creating security rule for %s", ports) } securityRules = append(securityRules, rule) } return nil }