mirror of https://github.com/docker/docs.git
751 lines
26 KiB
Go
751 lines
26 KiB
Go
package azureutil
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/machine/drivers/azure/logutil"
|
|
"github.com/docker/machine/libmachine/log"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
|
"github.com/Azure/azure-sdk-for-go/arm/network"
|
|
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
|
|
"github.com/Azure/azure-sdk-for-go/arm/storage"
|
|
blobstorage "github.com/Azure/azure-sdk-for-go/storage"
|
|
"github.com/Azure/go-autorest/autorest"
|
|
"github.com/Azure/go-autorest/autorest/azure"
|
|
"github.com/Azure/go-autorest/autorest/to"
|
|
)
|
|
|
|
const (
|
|
storageAccountPrefix = "vhds" // do not contaminate to user's existing storage accounts
|
|
fmtOSDiskContainer = "vhd-%s" // place vhds of VMs in separate containers for ease of cleanup
|
|
fmtOSDiskBlobName = "%s-os-disk.vhd"
|
|
fmtOSDiskResourceName = "%s-os-disk"
|
|
defaultStorageAPIVersion = blobstorage.DefaultAPIVersion
|
|
)
|
|
|
|
var (
|
|
// Private IPv4 address space per RFC 1918.
|
|
defaultVnetAddressPrefixes = []string{
|
|
"192.168.0.0/16",
|
|
"10.0.0.0/6",
|
|
"172.16.0.0/12"}
|
|
|
|
// Polling interval for VM power state check.
|
|
powerStatePollingInterval = time.Second * 5
|
|
waitStartTimeout = time.Minute * 10
|
|
waitPowerOffTimeout = time.Minute * 5
|
|
)
|
|
|
|
type AzureClient struct {
|
|
env azure.Environment
|
|
subscriptionID string
|
|
auth autorest.Authorizer
|
|
}
|
|
|
|
func New(env azure.Environment, subsID string, auth autorest.Authorizer) *AzureClient {
|
|
return &AzureClient{env, subsID, auth}
|
|
}
|
|
|
|
// RegisterResourceProviders registers current subscription to the specified
|
|
// resource provider namespaces if they are not already registered. Namespaces
|
|
// are case-insensitive.
|
|
func (a AzureClient) RegisterResourceProviders(namespaces ...string) error {
|
|
l, err := a.providersClient().List(nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if l.Value == nil {
|
|
return errors.New("Resource Providers list is returned as nil.")
|
|
}
|
|
|
|
m := make(map[string]bool)
|
|
for _, p := range *l.Value {
|
|
m[strings.ToLower(to.String(p.Namespace))] = to.String(p.RegistrationState) == "Registered"
|
|
}
|
|
|
|
for _, ns := range namespaces {
|
|
registered, ok := m[strings.ToLower(ns)]
|
|
if !ok {
|
|
return fmt.Errorf("Unknown resource provider %q", ns)
|
|
}
|
|
if registered {
|
|
log.Debugf("Already registered for %q", ns)
|
|
} else {
|
|
log.Info("Registering subscription to resource provider.", logutil.Fields{
|
|
"ns": ns,
|
|
"subs": a.subscriptionID,
|
|
})
|
|
if _, err := a.providersClient().Register(ns); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateResourceGroup creates a Resource Group if not exists
|
|
func (a AzureClient) CreateResourceGroup(name, location string) error {
|
|
if ok, err := a.resourceGroupExists(name); err != nil {
|
|
return err
|
|
} else if ok {
|
|
log.Infof("Resource group %q already exists.", name)
|
|
return nil
|
|
}
|
|
|
|
log.Info("Creating resource group...", logutil.Fields{
|
|
"name": name,
|
|
"location": location})
|
|
_, err := a.resourceGroupsClient().CreateOrUpdate(name,
|
|
resources.ResourceGroup{
|
|
Location: to.StringPtr(location),
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (a AzureClient) resourceGroupExists(name string) (bool, error) {
|
|
log.Info("Querying existing resource group...", logutil.Fields{"name": name})
|
|
_, err := a.resourceGroupsClient().Get(name)
|
|
return checkResourceExistsFromError(err)
|
|
}
|
|
|
|
func (a AzureClient) CreateNetworkSecurityGroup(ctx *DeploymentContext, resourceGroup, name, location string, rules *[]network.SecurityRule) error {
|
|
log.Info("Creating network security group...", logutil.Fields{
|
|
"name": name,
|
|
"location": location})
|
|
_, err := a.securityGroupsClient().CreateOrUpdate(resourceGroup, name,
|
|
network.SecurityGroup{
|
|
Location: to.StringPtr(location),
|
|
Properties: &network.SecurityGroupPropertiesFormat{
|
|
SecurityRules: rules,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nsg, err := a.securityGroupsClient().Get(resourceGroup, name, "")
|
|
ctx.NetworkSecurityGroupID = to.String(nsg.ID)
|
|
return err
|
|
}
|
|
|
|
func (a AzureClient) DeleteNetworkSecurityGroupIfExists(resourceGroup, name string) error {
|
|
return deleteResourceIfExists("Network Security Group", name,
|
|
func() error {
|
|
_, err := a.securityGroupsClient().Get(resourceGroup, name, "")
|
|
return err
|
|
},
|
|
func() (autorest.Response, error) { return a.securityGroupsClient().Delete(resourceGroup, name) })
|
|
}
|
|
|
|
func (a AzureClient) CreatePublicIPAddress(ctx *DeploymentContext, resourceGroup, name, location string) error {
|
|
log.Info("Creating public IP address...", logutil.Fields{"name": name})
|
|
_, err := a.publicIPAddressClient().CreateOrUpdate(resourceGroup, name,
|
|
network.PublicIPAddress{
|
|
Location: to.StringPtr(location),
|
|
Properties: &network.PublicIPAddressPropertiesFormat{
|
|
PublicIPAllocationMethod: network.Dynamic,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ip, err := a.publicIPAddressClient().Get(resourceGroup, name, "")
|
|
ctx.PublicIPAddressID = to.String(ip.ID)
|
|
return err
|
|
}
|
|
|
|
func (a AzureClient) DeletePublicIPAddressIfExists(resourceGroup, name string) error {
|
|
return deleteResourceIfExists("Public IP", name,
|
|
func() error {
|
|
_, err := a.publicIPAddressClient().Get(resourceGroup, name, "")
|
|
return err
|
|
},
|
|
func() (autorest.Response, error) { return a.publicIPAddressClient().Delete(resourceGroup, name) })
|
|
}
|
|
|
|
func (a AzureClient) CreateVirtualNetworkIfNotExists(resourceGroup, name, location string) error {
|
|
f := logutil.Fields{
|
|
"name": name,
|
|
"location": location}
|
|
|
|
log.Info("Querying if virtual network already exists...", f)
|
|
|
|
if exists, err := a.virtualNetworkExists(resourceGroup, name); err != nil {
|
|
return err
|
|
} else if exists {
|
|
log.Info("Virtual network already exists.", f)
|
|
return nil
|
|
}
|
|
|
|
log.Debug("Virtual network does not exist, creating...", f)
|
|
_, err := a.virtualNetworksClient().CreateOrUpdate(resourceGroup, name,
|
|
network.VirtualNetwork{
|
|
Location: to.StringPtr(location),
|
|
Properties: &network.VirtualNetworkPropertiesFormat{
|
|
AddressSpace: &network.AddressSpace{
|
|
AddressPrefixes: to.StringSlicePtr(defaultVnetAddressPrefixes),
|
|
},
|
|
},
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (a AzureClient) virtualNetworkExists(resourceGroup, name string) (bool, error) {
|
|
_, err := a.virtualNetworksClient().Get(resourceGroup, name, "")
|
|
return checkResourceExistsFromError(err)
|
|
}
|
|
|
|
// CleanupVirtualNetworkIfExists removes a subnet if there are no subnets
|
|
// attached to it. Note that this method is not safe for multiple concurrent
|
|
// writers, in case of races, deployment of a machine could fail or resource
|
|
// might not be cleaned up.
|
|
func (a AzureClient) CleanupVirtualNetworkIfExists(resourceGroup, name string) error {
|
|
return a.cleanupResourceIfExists(&vnetCleanup{rg: resourceGroup, name: name})
|
|
}
|
|
|
|
func (a AzureClient) GetSubnet(resourceGroup, virtualNetwork, name string) (network.Subnet, error) {
|
|
return a.subnetsClient().Get(resourceGroup, virtualNetwork, name, "")
|
|
}
|
|
|
|
func (a AzureClient) CreateSubnet(ctx *DeploymentContext, resourceGroup, virtualNetwork, name, subnetPrefix string) error {
|
|
log.Info("Creating subnet...", logutil.Fields{
|
|
"name": name,
|
|
"vnet": virtualNetwork,
|
|
"cidr": subnetPrefix})
|
|
_, err := a.subnetsClient().CreateOrUpdate(resourceGroup, virtualNetwork, name,
|
|
network.Subnet{
|
|
Properties: &network.SubnetPropertiesFormat{
|
|
AddressPrefix: to.StringPtr(subnetPrefix),
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
subnet, err := a.subnetsClient().Get(resourceGroup, virtualNetwork, name, "")
|
|
ctx.SubnetID = to.String(subnet.ID)
|
|
return err
|
|
}
|
|
|
|
// CleanupSubnetIfExists removes a subnet if there are no IP configurations
|
|
// (through NICs) are attached to it. Note that this method is not safe for
|
|
// multiple concurrent writers, in case of races, deployment of a machine could
|
|
// fail or resource might not be cleaned up.
|
|
func (a AzureClient) CleanupSubnetIfExists(resourceGroup, virtualNetwork, name string) error {
|
|
return a.cleanupResourceIfExists(&subnetCleanup{
|
|
rg: resourceGroup, vnet: virtualNetwork, name: name,
|
|
})
|
|
}
|
|
|
|
func (a AzureClient) CreateNetworkInterface(ctx *DeploymentContext, resourceGroup, name, location, publicIPAddressID, subnetID, nsgID, privateIPAddress string) error {
|
|
// NOTE(ahmetalpbalkan) This method is expected to fail if the user
|
|
// specified Azure location is different than location of the virtual
|
|
// network as Azure does not support cross-region virtual networks. In this
|
|
// situation, user will get an explanatory API error from Azure.
|
|
log.Info("Creating network interface...", logutil.Fields{"name": name})
|
|
|
|
var publicIP *network.PublicIPAddress
|
|
if publicIPAddressID != "" {
|
|
publicIP = &network.PublicIPAddress{ID: to.StringPtr(publicIPAddressID)}
|
|
}
|
|
|
|
var privateIPAllocMethod = network.Dynamic
|
|
if privateIPAddress != "" {
|
|
privateIPAllocMethod = network.Static
|
|
}
|
|
_, err := a.networkInterfacesClient().CreateOrUpdate(resourceGroup, name, network.Interface{
|
|
Location: to.StringPtr(location),
|
|
Properties: &network.InterfacePropertiesFormat{
|
|
NetworkSecurityGroup: &network.SecurityGroup{
|
|
ID: to.StringPtr(nsgID),
|
|
},
|
|
IPConfigurations: &[]network.InterfaceIPConfiguration{
|
|
{
|
|
Name: to.StringPtr("ip"),
|
|
Properties: &network.InterfaceIPConfigurationPropertiesFormat{
|
|
PrivateIPAddress: to.StringPtr(privateIPAddress),
|
|
PrivateIPAllocationMethod: privateIPAllocMethod,
|
|
PublicIPAddress: publicIP,
|
|
Subnet: &network.Subnet{
|
|
ID: to.StringPtr(subnetID),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nic, err := a.networkInterfacesClient().Get(resourceGroup, name, "")
|
|
ctx.NetworkInterfaceID = to.String(nic.ID)
|
|
return err
|
|
}
|
|
|
|
func (a AzureClient) DeleteNetworkInterfaceIfExists(resourceGroup, name string) error {
|
|
return deleteResourceIfExists("Network Interface", name,
|
|
func() error {
|
|
_, err := a.networkInterfacesClient().Get(resourceGroup, name, "")
|
|
return err
|
|
},
|
|
func() (autorest.Response, error) { return a.networkInterfacesClient().Delete(resourceGroup, name) })
|
|
}
|
|
|
|
func (a AzureClient) CreateStorageAccount(ctx *DeploymentContext, resourceGroup, location string, storageType storage.AccountType) error {
|
|
s, err := a.findOrCreateStorageAccount(resourceGroup, location, storageType)
|
|
ctx.StorageAccount = s
|
|
return err
|
|
}
|
|
|
|
func (a AzureClient) findOrCreateStorageAccount(resourceGroup, location string, storageType storage.AccountType) (*storage.AccountProperties, error) {
|
|
prefix := storageAccountPrefix
|
|
if s, err := a.findStorageAccount(resourceGroup, location, prefix, storageType); err != nil {
|
|
return nil, err
|
|
} else if s != nil {
|
|
return s, nil
|
|
}
|
|
|
|
log.Debug("No eligible storage account found.", logutil.Fields{
|
|
"location": location,
|
|
"type": storageType})
|
|
return a.createStorageAccount(resourceGroup, location, storageType)
|
|
}
|
|
|
|
func (a AzureClient) findStorageAccount(resourceGroup, location, prefix string, storageType storage.AccountType) (*storage.AccountProperties, error) {
|
|
f := logutil.Fields{
|
|
"type": storageType,
|
|
"prefix": prefix,
|
|
"location": location}
|
|
log.Debug("Querying existing storage accounts...", f)
|
|
l, err := a.storageAccountsClient().ListByResourceGroup(resourceGroup)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if l.Value != nil {
|
|
for _, v := range *l.Value {
|
|
log.Debug("Iterating...", logutil.Fields{
|
|
"name": to.String(v.Name),
|
|
"type": storageType,
|
|
"location": to.String(v.Location),
|
|
})
|
|
if to.String(v.Location) == location && v.Properties.AccountType == storageType && strings.HasPrefix(to.String(v.Name), prefix) {
|
|
log.Debug("Found eligible storage account.", logutil.Fields{"name": to.String(v.Name)})
|
|
return v.Properties, nil
|
|
}
|
|
}
|
|
}
|
|
log.Debug("No account matching the pattern is found.", f)
|
|
return nil, err
|
|
}
|
|
|
|
func (a AzureClient) createStorageAccount(resourceGroup, location string, storageType storage.AccountType) (*storage.AccountProperties, error) {
|
|
name := randomAzureStorageAccountName() // if it's not random enough, then you're unlucky
|
|
f := logutil.Fields{
|
|
"name": name,
|
|
"location": location}
|
|
|
|
log.Info("Creating storage account...", f)
|
|
_, err := a.storageAccountsClient().Create(resourceGroup, name,
|
|
storage.AccountCreateParameters{
|
|
Location: to.StringPtr(location),
|
|
Properties: &storage.AccountPropertiesCreateParameters{
|
|
AccountType: storageType,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// NOTE(ahmetalpbalkan) The following loop should eventually be deleted.
|
|
// Azure Storage Provider has a different polling logic than other Core RPs
|
|
// and that is not currently implemented in go-autorest. In this loop we are
|
|
// polling until the property we need is present.
|
|
for {
|
|
// Issue a GET call because polling endpoint (?monitor=true) does not respond with
|
|
// full storage object (has all .Properties)
|
|
log.Debug("Waiting for storage account to be ready.", f)
|
|
s, err := a.storageAccountsClient().GetProperties(resourceGroup, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if s.Properties != nil && s.Properties.PrimaryEndpoints != nil &&
|
|
s.Properties.PrimaryEndpoints.Blob != nil {
|
|
return s.Properties, err
|
|
}
|
|
log.Debug("Storage account is not yet ready.", f)
|
|
time.Sleep(time.Second * 10)
|
|
}
|
|
}
|
|
|
|
func (a AzureClient) VirtualMachineExists(resourceGroup, name string) (bool, error) {
|
|
_, err := a.virtualMachinesClient().Get(resourceGroup, name, "")
|
|
return checkResourceExistsFromError(err)
|
|
}
|
|
|
|
func (a AzureClient) DeleteVirtualMachineIfExists(resourceGroup, name string) error {
|
|
var vmRef compute.VirtualMachine
|
|
err := deleteResourceIfExists("Virtual Machine", name,
|
|
func() error {
|
|
vm, err := a.virtualMachinesClient().Get(resourceGroup, name, "")
|
|
vmRef = vm
|
|
return err
|
|
},
|
|
func() (autorest.Response, error) { return a.virtualMachinesClient().Delete(resourceGroup, name) })
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Remove disk
|
|
if vmRef.Properties != nil {
|
|
vhdURL := to.String(vmRef.Properties.StorageProfile.OsDisk.Vhd.URI)
|
|
return a.removeOSDiskBlob(resourceGroup, name, vhdURL)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a AzureClient) removeOSDiskBlob(resourceGroup, vmName, vhdURL string) error {
|
|
// NOTE(ahmetalpbalkan) Currently Azure APIs do not offer a Delete Virtual
|
|
// Machine functionality which deletes the attached disks along with the VM
|
|
// as well. Therefore we find out the storage account from OS disk URL and
|
|
// fetch storage account keys to delete the container containing the disk.
|
|
log.Debug("Attempting to remove OS disk...", logutil.Fields{"vm": vmName})
|
|
log.Debugf("OS Disk vhd URL: %q", vhdURL)
|
|
|
|
vhdContainer := osDiskStorageContainerName(vmName)
|
|
|
|
storageAccount, blobServiceBaseURL := extractStorageAccountFromVHDURL(vhdURL)
|
|
if storageAccount == "" {
|
|
log.Warn("Could not extract the storage account name from URL. Please clean up the disk yourself.")
|
|
return nil
|
|
}
|
|
log.Debug("Fetching storage account keys.", logutil.Fields{
|
|
"account": storageAccount,
|
|
"storageBase": blobServiceBaseURL,
|
|
})
|
|
keys, err := a.storageAccountsClient().ListKeys(resourceGroup, storageAccount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
storageAccountKey := to.String(keys.Key1)
|
|
bs, err := blobstorage.NewClient(storageAccount, storageAccountKey, blobServiceBaseURL, defaultStorageAPIVersion, true)
|
|
if err != nil {
|
|
return fmt.Errorf("Error constructing blob storage client :%v", err)
|
|
}
|
|
|
|
f := logutil.Fields{
|
|
"account": storageAccount,
|
|
"container": vhdContainer}
|
|
log.Debug("Removing container of disk blobs.", f)
|
|
ok, err := bs.GetBlobService().DeleteContainerIfExists(vhdContainer) // HTTP round-trip will not be inspected
|
|
if err != nil {
|
|
log.Debugf("Container remove happened: %v", ok)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (a AzureClient) CreateVirtualMachine(resourceGroup, name, location, size, availabilitySetID, networkInterfaceID,
|
|
username, sshPublicKey, imageName string, storageAccount *storage.AccountProperties) error {
|
|
log.Info("Creating Virtual Machine...", logutil.Fields{
|
|
"name": name,
|
|
"location": location,
|
|
"size": size,
|
|
"username": username,
|
|
"osImage": imageName,
|
|
})
|
|
|
|
img, err := parseImageName(imageName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
osDiskBlobURL = osDiskStorageBlobURL(storageAccount, name)
|
|
sshKeyPath = fmt.Sprintf("/home/%s/.ssh/authorized_keys", username)
|
|
)
|
|
log.Debugf("OS disk blob will be placed at: %s", osDiskBlobURL)
|
|
log.Debugf("SSH key will be placed at: %s", sshKeyPath)
|
|
|
|
_, err = a.virtualMachinesClient().CreateOrUpdate(resourceGroup, name,
|
|
compute.VirtualMachine{
|
|
Location: to.StringPtr(location),
|
|
Properties: &compute.VirtualMachineProperties{
|
|
AvailabilitySet: &compute.SubResource{
|
|
ID: to.StringPtr(availabilitySetID),
|
|
},
|
|
HardwareProfile: &compute.HardwareProfile{
|
|
VMSize: compute.VirtualMachineSizeTypes(size),
|
|
},
|
|
NetworkProfile: &compute.NetworkProfile{
|
|
NetworkInterfaces: &[]compute.NetworkInterfaceReference{
|
|
{
|
|
ID: to.StringPtr(networkInterfaceID),
|
|
},
|
|
},
|
|
},
|
|
OsProfile: &compute.OSProfile{
|
|
ComputerName: to.StringPtr(name),
|
|
AdminUsername: to.StringPtr(username),
|
|
LinuxConfiguration: &compute.LinuxConfiguration{
|
|
DisablePasswordAuthentication: to.BoolPtr(true),
|
|
SSH: &compute.SSHConfiguration{
|
|
PublicKeys: &[]compute.SSHPublicKey{
|
|
{
|
|
Path: to.StringPtr(sshKeyPath),
|
|
KeyData: to.StringPtr(sshPublicKey),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
StorageProfile: &compute.StorageProfile{
|
|
ImageReference: &compute.ImageReference{
|
|
Publisher: to.StringPtr(img.publisher),
|
|
Offer: to.StringPtr(img.offer),
|
|
Sku: to.StringPtr(img.sku),
|
|
Version: to.StringPtr(img.version),
|
|
},
|
|
OsDisk: &compute.OSDisk{
|
|
Name: to.StringPtr(fmt.Sprintf(fmtOSDiskResourceName, name)),
|
|
Caching: compute.ReadWrite,
|
|
CreateOption: compute.FromImage,
|
|
Vhd: &compute.VirtualHardDisk{
|
|
URI: to.StringPtr(osDiskBlobURL),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (a AzureClient) GetVirtualMachinePowerState(resourceGroup, name string) (VMPowerState, error) {
|
|
log.Debug("Querying instance view for power state.")
|
|
vm, err := a.virtualMachinesClient().Get(resourceGroup, name, "instanceView")
|
|
if err != nil {
|
|
log.Errorf("Error querying instance view: %v", err)
|
|
return Unknown, err
|
|
}
|
|
return powerStateFromInstanceView(vm.Properties.InstanceView), nil
|
|
}
|
|
|
|
func (a AzureClient) GetAvailabilitySet(resourceGroup, name string) (compute.AvailabilitySet, error) {
|
|
return a.availabilitySetsClient().Get(resourceGroup, name)
|
|
}
|
|
|
|
func (a AzureClient) CreateAvailabilitySetIfNotExists(ctx *DeploymentContext, resourceGroup, name, location string) error {
|
|
f := logutil.Fields{"name": name}
|
|
if ctx.AvailabilitySetID != "" {
|
|
log.Info("Availability Set already exists.", f)
|
|
return nil
|
|
}
|
|
log.Debug("Could not find existing availability set.", f)
|
|
log.Info("Creating availability set...", f)
|
|
as, err := a.availabilitySetsClient().CreateOrUpdate(resourceGroup, name,
|
|
compute.AvailabilitySet{
|
|
Location: to.StringPtr(location),
|
|
})
|
|
ctx.AvailabilitySetID = to.String(as.ID)
|
|
return err
|
|
}
|
|
|
|
// CleanupAvailabilitySetIfExists removes an availability set if there are no
|
|
// virtual machines attached to it. Note that this method is not safe for
|
|
// multiple concurrent writers, in case of races, deployment of a machine could
|
|
// fail or resource might not be cleaned up.
|
|
func (a AzureClient) CleanupAvailabilitySetIfExists(resourceGroup, name string) error {
|
|
return a.cleanupResourceIfExists(&avSetCleanup{rg: resourceGroup, name: name})
|
|
}
|
|
|
|
// GetPublicIPAddress attempts to get public IP address from the Public IP
|
|
// resource. If IP address is not allocated yet, returns empty string.
|
|
func (a AzureClient) GetPublicIPAddress(resourceGroup, name string) (string, error) {
|
|
f := logutil.Fields{"name": name}
|
|
log.Debug("Querying public IP address.", f)
|
|
ip, err := a.publicIPAddressClient().Get(resourceGroup, name, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if ip.Properties == nil {
|
|
log.Debug("publicIP.Properties is nil. Could not determine IP address", f)
|
|
return "", nil
|
|
}
|
|
return to.String(ip.Properties.IPAddress), nil
|
|
}
|
|
|
|
// GetPrivateIPAddress attempts to retrieve private IP address of the specified
|
|
// network interface name. If IP address is not allocated yet, returns empty
|
|
// string.
|
|
func (a AzureClient) GetPrivateIPAddress(resourceGroup, name string) (string, error) {
|
|
f := logutil.Fields{"name": name}
|
|
log.Debug("Querying network interface.", f)
|
|
nic, err := a.networkInterfacesClient().Get(resourceGroup, name, "")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if nic.Properties == nil || nic.Properties.IPConfigurations == nil ||
|
|
len(*nic.Properties.IPConfigurations) == 0 {
|
|
log.Debug("No IPConfigurations found on NIC", f)
|
|
return "", nil
|
|
}
|
|
return to.String((*nic.Properties.IPConfigurations)[0].Properties.PrivateIPAddress), nil
|
|
}
|
|
|
|
// StartVirtualMachine starts the virtual machine and waits until it reaches
|
|
// the goal state (running) or times out.
|
|
func (a AzureClient) StartVirtualMachine(resourceGroup, name string) error {
|
|
log.Info("Starting virtual machine.", logutil.Fields{"vm": name})
|
|
if _, err := a.virtualMachinesClient().Start(resourceGroup, name); err != nil {
|
|
return err
|
|
}
|
|
return a.waitVMPowerState(resourceGroup, name, Running, waitStartTimeout)
|
|
}
|
|
|
|
// StopVirtualMachine power offs the virtual machine and waits until it reaches
|
|
// the goal state (stopped) or times out.
|
|
func (a AzureClient) StopVirtualMachine(resourceGroup, name string) error {
|
|
log.Info("Stopping virtual machine.", logutil.Fields{"vm": name})
|
|
if _, err := a.virtualMachinesClient().PowerOff(resourceGroup, name); err != nil {
|
|
return err
|
|
}
|
|
return a.waitVMPowerState(resourceGroup, name, Stopped, waitPowerOffTimeout)
|
|
}
|
|
|
|
// RestartVirtualMachine restarts the virtual machine and waits until it reaches
|
|
// the goal state (stopped) or times out.
|
|
func (a AzureClient) RestartVirtualMachine(resourceGroup, name string) error {
|
|
log.Info("Restarting virtual machine.", logutil.Fields{"vm": name})
|
|
if _, err := a.virtualMachinesClient().Restart(resourceGroup, name); err != nil {
|
|
return err
|
|
}
|
|
return a.waitVMPowerState(resourceGroup, name, Running, waitStartTimeout)
|
|
}
|
|
|
|
// deleteResourceIfExists is an utility method to determine if a resource exists
|
|
// from the error returned from its Get response. If so, deletes it. name is
|
|
// used only for logging purposes.
|
|
func deleteResourceIfExists(resourceType, name string, getFunc func() error, deleteFunc func() (autorest.Response, error)) error {
|
|
f := logutil.Fields{"name": name}
|
|
log.Debug(fmt.Sprintf("Querying if %s exists.", resourceType), f)
|
|
if exists, err := checkResourceExistsFromError(getFunc()); err != nil {
|
|
return err
|
|
} else if !exists {
|
|
log.Info(fmt.Sprintf("%s does not exist. Skipping.", resourceType), f)
|
|
return nil
|
|
}
|
|
log.Info(fmt.Sprintf("Removing %s resource...", resourceType), f)
|
|
_, err := deleteFunc()
|
|
return err
|
|
}
|
|
|
|
// waitVMPowerState polls the Virtual Machine instance view until it reaches the
|
|
// specified goal power state or times out. If checking for virtual machine
|
|
// state fails or waiting times out, an error is returned.
|
|
func (a AzureClient) waitVMPowerState(resourceGroup, name string, goalState VMPowerState, timeout time.Duration) error {
|
|
// NOTE(ahmetalpbalkan): Azure APIs for Start and Stop are actually async
|
|
// operations on which our SDK blocks and does polling until the operation
|
|
// is complete.
|
|
//
|
|
// By the time the issued power cycle operation is complete, the VM will be
|
|
// already in the goal PowerState. Hence, this method will return in the
|
|
// first check, however there is no harm in being defensive.
|
|
log.Debug("Waiting until VM reaches goal power state.", logutil.Fields{
|
|
"vm": name,
|
|
"goalState": goalState,
|
|
"timeout": timeout,
|
|
})
|
|
|
|
chErr := make(chan error)
|
|
go func(ch chan error) {
|
|
for {
|
|
select {
|
|
case <-ch:
|
|
// channel closed
|
|
return
|
|
default:
|
|
state, err := a.GetVirtualMachinePowerState(resourceGroup, name)
|
|
if err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
if state != goalState {
|
|
log.Debug(fmt.Sprintf("Waiting %v...", powerStatePollingInterval),
|
|
logutil.Fields{
|
|
"goalState": goalState,
|
|
"state": state,
|
|
})
|
|
time.Sleep(powerStatePollingInterval)
|
|
} else {
|
|
log.Debug("Reached goal power state.",
|
|
logutil.Fields{"state": state})
|
|
ch <- nil
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}(chErr)
|
|
|
|
select {
|
|
case <-time.After(timeout):
|
|
close(chErr)
|
|
return fmt.Errorf("Waiting for goal state %q timed out after %v", goalState, timeout)
|
|
case err := <-chErr:
|
|
return err
|
|
}
|
|
}
|
|
|
|
// checkExistsFromError inspects an error and returns a true if err is nil,
|
|
// false if error is an autorest.Error with StatusCode=404 and will return the
|
|
// error back if error is another status code or another type of error.
|
|
func checkResourceExistsFromError(err error) (bool, error) {
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
v, ok := err.(autorest.DetailedError)
|
|
if ok && v.StatusCode == http.StatusNotFound {
|
|
return false, nil
|
|
}
|
|
return false, v
|
|
}
|
|
|
|
// osDiskStorageBlobURL gives the full url of the VHD blob where the OS disk for
|
|
// the given VM should be stored.
|
|
func osDiskStorageBlobURL(account *storage.AccountProperties, vmName string) string {
|
|
containerURL := osDiskStorageContainerURL(account, vmName) // has trailing slash
|
|
blobName := fmt.Sprintf(fmtOSDiskBlobName, vmName)
|
|
return containerURL + blobName
|
|
}
|
|
|
|
// osDiskStorageContainerName returns the container name the OS disk for the VM
|
|
// should be saved.
|
|
func osDiskStorageContainerName(vm string) string { return fmt.Sprintf(fmtOSDiskContainer, vm) }
|
|
|
|
// osDiskStorageContainerURL crafts a URL with a trailing slash pointing
|
|
// to the full Azure Blob Container URL for given VM name.
|
|
func osDiskStorageContainerURL(account *storage.AccountProperties, vmName string) string {
|
|
return fmt.Sprintf("%s%s/", to.String(account.PrimaryEndpoints.Blob), osDiskStorageContainerName(vmName))
|
|
}
|
|
|
|
// extractStorageAccountFromVHDURL parses a blob URL and extracts the Azure
|
|
// Storage account name from the URL, namely first subdomain of the hostname and
|
|
// the Azure Storage service base URL (e.g. core.windows.net). If it could not
|
|
// be parsed, returns empty string.
|
|
func extractStorageAccountFromVHDURL(vhdURL string) (string, string) {
|
|
u, err := url.Parse(vhdURL)
|
|
if err != nil {
|
|
log.Warn(fmt.Sprintf("URL parse error: %v", err), logutil.Fields{"url": vhdURL})
|
|
return "", ""
|
|
}
|
|
parts := strings.SplitN(u.Host, ".", 2)
|
|
if len(parts) != 2 {
|
|
log.Warnf("Could not split account name and storage base URL: %s", vhdURL)
|
|
return "", ""
|
|
}
|
|
return parts[0], strings.TrimPrefix(parts[1], "blob.") // "blob." prefix will added by azure storage sdk
|
|
}
|