diff --git a/pkg/cloudprovider/providers/azure/azure_blobDiskController.go b/pkg/cloudprovider/providers/azure/azure_blobDiskController.go index d0365936da5..2343d76230b 100644 --- a/pkg/cloudprovider/providers/azure/azure_blobDiskController.go +++ b/pkg/cloudprovider/providers/azure/azure_blobDiskController.go @@ -32,6 +32,7 @@ import ( "github.com/Azure/go-autorest/autorest/to" "github.com/golang/glog" "github.com/rubiojr/go-vhd/vhd" + kwait "k8s.io/apimachinery/pkg/util/wait" "k8s.io/kubernetes/pkg/volume" ) diff --git a/pkg/cloudprovider/providers/azure/azure_managedDiskController.go b/pkg/cloudprovider/providers/azure/azure_managedDiskController.go index 22d3d3bff30..8fa4515c493 100644 --- a/pkg/cloudprovider/providers/azure/azure_managedDiskController.go +++ b/pkg/cloudprovider/providers/azure/azure_managedDiskController.go @@ -40,22 +40,55 @@ type ManagedDiskController struct { common *controllerCommon } +// ManagedDiskOptions specifies the options of managed disks. +type ManagedDiskOptions struct { + DiskName string + SizeGB int + PVCName string + ResourceGroup string + Zoned bool + ZonePresent bool + ZonesPresent bool + AvailabilityZone string + AvailabilityZones string + Tags map[string]string + StorageAccountType storage.SkuName +} + func newManagedDiskController(common *controllerCommon) (*ManagedDiskController, error) { return &ManagedDiskController{common: common}, nil } //CreateManagedDisk : create managed disk -func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccountType storage.SkuName, resourceGroup string, - sizeGB int, tags map[string]string) (string, error) { - glog.V(4).Infof("azureDisk - creating new managed Name:%s StorageAccountType:%s Size:%v", diskName, storageAccountType, sizeGB) +func (c *ManagedDiskController) CreateManagedDisk(options *ManagedDiskOptions) (string, error) { + glog.V(4).Infof("azureDisk - creating new managed Name:%s StorageAccountType:%s Size:%v", options.DiskName, options.StorageAccountType, options.SizeGB) + // Validate and choose availability zone for creating disk. + var createAZ string + if options.Zoned && !options.ZonePresent && !options.ZonesPresent { + // TODO: get zones from active zones that with running nodes. + } + if !options.ZonePresent && options.ZonesPresent { + // Choose zone from specified zones. + if adminSetOfZones, err := util.ZonesToSet(options.AvailabilityZones); err != nil { + return "", err + } else { + createAZ = util.ChooseZoneForVolume(adminSetOfZones, options.PVCName) + } + } + if options.ZonePresent && !options.ZonesPresent { + if err := util.ValidateZone(options.AvailabilityZone); err != nil { + return "", err + } + createAZ = options.AvailabilityZone + } + + // insert original tags to newTags newTags := make(map[string]*string) azureDDTag := "kubernetes-azure-dd" newTags["created-by"] = &azureDDTag - - // insert original tags to newTags - if tags != nil { - for k, v := range tags { + if options.Tags != nil { + for k, v := range options.Tags { // Azure won't allow / (forward slash) in tags newKey := strings.Replace(k, "/", "-", -1) newValue := strings.Replace(v, "/", "-", -1) @@ -63,25 +96,30 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun } } - diskSizeGB := int32(sizeGB) + diskSizeGB := int32(options.SizeGB) model := compute.Disk{ Location: &c.common.location, Tags: newTags, Sku: &compute.DiskSku{ - Name: compute.StorageAccountTypes(storageAccountType), + Name: compute.StorageAccountTypes(options.StorageAccountType), }, DiskProperties: &compute.DiskProperties{ DiskSizeGB: &diskSizeGB, CreationData: &compute.CreationData{CreateOption: compute.Empty}, - }} + }, + } - if resourceGroup == "" { - resourceGroup = c.common.resourceGroup + if options.ResourceGroup == "" { + options.ResourceGroup = c.common.resourceGroup + } + if createAZ != "" { + createZones := []string{createAZ} + model.Zones = &createZones } ctx, cancel := getContextWithCancel() defer cancel() - _, err := c.common.cloud.DisksClient.CreateOrUpdate(ctx, resourceGroup, diskName, model) + _, err := c.common.cloud.DisksClient.CreateOrUpdate(ctx, options.ResourceGroup, options.DiskName, model) if err != nil { return "", err } @@ -89,7 +127,7 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun diskID := "" err = kwait.ExponentialBackoff(defaultBackOff, func() (bool, error) { - provisionState, id, err := c.getDisk(resourceGroup, diskName) + provisionState, id, err := c.getDisk(options.ResourceGroup, options.DiskName) diskID = id // We are waiting for provisioningState==Succeeded // We don't want to hand-off managed disks to k8s while they are @@ -104,9 +142,9 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun }) if err != nil { - glog.V(2).Infof("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v but was unable to confirm provisioningState in poll process", diskName, storageAccountType, sizeGB) + glog.V(2).Infof("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v but was unable to confirm provisioningState in poll process", options.DiskName, options.StorageAccountType, options.SizeGB) } else { - glog.V(2).Infof("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v", diskName, storageAccountType, sizeGB) + glog.V(2).Infof("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v", options.DiskName, options.StorageAccountType, options.SizeGB) } return diskID, nil diff --git a/pkg/cloudprovider/providers/azure/azure_zones.go b/pkg/cloudprovider/providers/azure/azure_zones.go index 29a15251965..e255bf8bae9 100644 --- a/pkg/cloudprovider/providers/azure/azure_zones.go +++ b/pkg/cloudprovider/providers/azure/azure_zones.go @@ -41,6 +41,20 @@ func (az *Cloud) makeZone(zoneID int) string { return fmt.Sprintf("%s-%d", strings.ToLower(az.Location), zoneID) } +// isAvailabilityZone returns true if the zone is in format of -. +func (az *Cloud) isAvailabilityZone(zone string) bool { + return strings.HasPrefix(zone, fmt.Sprintf("%s-", az.Location)) +} + +// GetZoneID returns the ID of zone from node's zone label. +func (az *Cloud) GetZoneID(zoneLabel string) string { + if !az.isAvailabilityZone(zoneLabel) { + return "" + } + + return strings.TrimPrefix(zoneLabel, fmt.Sprintf("%s-", az.Location)) +} + // GetZone returns the Zone containing the current availability zone and locality region that the program is running in. // If the node is not running with availability zones, then it will fall back to fault domain. func (az *Cloud) GetZone(ctx context.Context) (cloudprovider.Zone, error) { diff --git a/pkg/volume/azure_dd/azure_dd.go b/pkg/volume/azure_dd/azure_dd.go index 05e2f4fc46f..16669961b1c 100644 --- a/pkg/volume/azure_dd/azure_dd.go +++ b/pkg/volume/azure_dd/azure_dd.go @@ -35,7 +35,7 @@ type DiskController interface { CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int) (string, error) DeleteBlobDisk(diskUri string) error - CreateManagedDisk(diskName string, storageAccountType storage.SkuName, resourceGroup string, sizeGB int, tags map[string]string) (string, error) + CreateManagedDisk(options *azure.ManagedDiskOptions) (string, error) DeleteManagedDisk(diskURI string) error // Attaches the disk to the host machine. @@ -58,6 +58,9 @@ type DiskController interface { // Expand the disk to new size ResizeDisk(diskURI string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) + + // GetAzureDiskLabels gets availability zone labels for Azuredisk. + GetAzureDiskLabels(diskURI string) (map[string]string, error) } type azureDataDiskPlugin struct { diff --git a/pkg/volume/azure_dd/azure_provision.go b/pkg/volume/azure_dd/azure_provision.go index bca3e0365b4..9d1f8db3888 100644 --- a/pkg/volume/azure_dd/azure_provision.go +++ b/pkg/volume/azure_dd/azure_provision.go @@ -19,12 +19,14 @@ package azure_dd import ( "errors" "fmt" + "strconv" "strings" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/kubernetes/pkg/cloudprovider/providers/azure" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/util" @@ -68,6 +70,21 @@ func (d *azureDiskDeleter) Delete() error { return diskController.DeleteBlobDisk(volumeSource.DataDiskURI) } +// parseZoned parsed 'zoned' for storage class. If zoned is not specified (empty string), +// then it defaults to true for managed disks. +func parseZoned(zonedString string, kind v1.AzureDataDiskKind) (bool, error) { + if zonedString == "" { + return kind == v1.AzureManagedDisk, nil + } + + zoned, err := strconv.ParseBool(zonedString) + if err != nil { + return false, fmt.Errorf("failed to parse 'zoned': %v", err) + } + + return zoned, nil +} + func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) { if !util.AccessModesContainedInAll(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) { return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes()) @@ -85,7 +102,7 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie if len(p.options.PVC.Spec.AccessModes) == 1 { if p.options.PVC.Spec.AccessModes[0] != supportedModes[0] { - return nil, fmt.Errorf("AzureDisk - mode %s is not supporetd by AzureDisk plugin supported mode is %s", p.options.PVC.Spec.AccessModes[0], supportedModes) + return nil, fmt.Errorf("AzureDisk - mode %s is not supported by AzureDisk plugin (supported mode is %s)", p.options.PVC.Spec.AccessModes[0], supportedModes) } } @@ -96,6 +113,13 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie strKind string err error resourceGroup string + + zoned bool + zonePresent bool + zonesPresent bool + strZoned string + availabilityZone string + availabilityZones string ) // maxLength = 79 - (4 for ".vhd") = 75 name := util.GenerateVolumeName(p.options.ClusterName, p.options.PVName, 75) @@ -123,6 +147,14 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie fsType = strings.ToLower(v) case "resourcegroup": resourceGroup = v + case "zone": + zonePresent = true + availabilityZone = v + case "zones": + zonesPresent = true + availabilityZones = v + case "zoned": + strZoned = v default: return nil, fmt.Errorf("AzureDisk - invalid option %s in storage class", k) } @@ -139,6 +171,19 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie return nil, err } + zoned, err = parseZoned(strZoned, kind) + if err != nil { + return nil, err + } + + if !zoned && (zonePresent || zonesPresent) { + return nil, fmt.Errorf("zone or zones StorageClass parameters must be used together with zoned parameter") + } + + if zonePresent && zonesPresent { + return nil, fmt.Errorf("both zone and zones StorageClass parameters must not be used at the same time") + } + if cachingMode, err = normalizeCachingMode(cachingMode); err != nil { return nil, err } @@ -154,16 +199,39 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie // create disk diskURI := "" + labels := map[string]string{} if kind == v1.AzureManagedDisk { tags := make(map[string]string) if p.options.CloudTags != nil { tags = *(p.options.CloudTags) } - diskURI, err = diskController.CreateManagedDisk(name, skuName, resourceGroup, requestGiB, tags) + + volumeOptions := &azure.ManagedDiskOptions{ + DiskName: name, + StorageAccountType: skuName, + ResourceGroup: resourceGroup, + PVCName: p.options.PVC.Name, + SizeGB: requestGiB, + Tags: tags, + Zoned: zoned, + ZonePresent: zonePresent, + ZonesPresent: zonesPresent, + AvailabilityZone: availabilityZone, + AvailabilityZones: availabilityZones, + } + diskURI, err = diskController.CreateManagedDisk(volumeOptions) + if err != nil { + return nil, err + } + labels, err = diskController.GetAzureDiskLabels(diskURI) if err != nil { return nil, err } } else { + if zoned { + return nil, errors.New("zoned parameter is only supported for managed disks") + } + if kind == v1.AzureDedicatedBlobDisk { _, diskURI, _, err = diskController.CreateVolume(name, account, storageAccountType, location, requestGiB) if err != nil { @@ -189,7 +257,7 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie pv := &v1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: p.options.PVName, - Labels: map[string]string{}, + Labels: labels, Annotations: map[string]string{ "volumehelper.VolumeDynamicallyCreatedByKey": "azure-disk-dynamic-provisioner", },