Commit 74813d0d authored by Pengfei Ni's avatar Pengfei Ni

Add availability zone support for dynamic provisioning Azure managed disks

parent b258bbad
...@@ -32,6 +32,7 @@ import ( ...@@ -32,6 +32,7 @@ import (
"github.com/Azure/go-autorest/autorest/to" "github.com/Azure/go-autorest/autorest/to"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/rubiojr/go-vhd/vhd" "github.com/rubiojr/go-vhd/vhd"
kwait "k8s.io/apimachinery/pkg/util/wait" kwait "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
) )
......
...@@ -40,22 +40,55 @@ type ManagedDiskController struct { ...@@ -40,22 +40,55 @@ type ManagedDiskController struct {
common *controllerCommon 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) { func newManagedDiskController(common *controllerCommon) (*ManagedDiskController, error) {
return &ManagedDiskController{common: common}, nil return &ManagedDiskController{common: common}, nil
} }
//CreateManagedDisk : create managed disk //CreateManagedDisk : create managed disk
func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccountType storage.SkuName, resourceGroup string, func (c *ManagedDiskController) CreateManagedDisk(options *ManagedDiskOptions) (string, error) {
sizeGB int, tags map[string]string) (string, error) { glog.V(4).Infof("azureDisk - creating new managed Name:%s StorageAccountType:%s Size:%v", options.DiskName, options.StorageAccountType, options.SizeGB)
glog.V(4).Infof("azureDisk - creating new managed Name:%s StorageAccountType:%s Size:%v", diskName, storageAccountType, 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) newTags := make(map[string]*string)
azureDDTag := "kubernetes-azure-dd" azureDDTag := "kubernetes-azure-dd"
newTags["created-by"] = &azureDDTag newTags["created-by"] = &azureDDTag
if options.Tags != nil {
// insert original tags to newTags for k, v := range options.Tags {
if tags != nil {
for k, v := range tags {
// Azure won't allow / (forward slash) in tags // Azure won't allow / (forward slash) in tags
newKey := strings.Replace(k, "/", "-", -1) newKey := strings.Replace(k, "/", "-", -1)
newValue := strings.Replace(v, "/", "-", -1) newValue := strings.Replace(v, "/", "-", -1)
...@@ -63,25 +96,30 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun ...@@ -63,25 +96,30 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun
} }
} }
diskSizeGB := int32(sizeGB) diskSizeGB := int32(options.SizeGB)
model := compute.Disk{ model := compute.Disk{
Location: &c.common.location, Location: &c.common.location,
Tags: newTags, Tags: newTags,
Sku: &compute.DiskSku{ Sku: &compute.DiskSku{
Name: compute.StorageAccountTypes(storageAccountType), Name: compute.StorageAccountTypes(options.StorageAccountType),
}, },
DiskProperties: &compute.DiskProperties{ DiskProperties: &compute.DiskProperties{
DiskSizeGB: &diskSizeGB, DiskSizeGB: &diskSizeGB,
CreationData: &compute.CreationData{CreateOption: compute.Empty}, CreationData: &compute.CreationData{CreateOption: compute.Empty},
}} },
}
if resourceGroup == "" { if options.ResourceGroup == "" {
resourceGroup = c.common.resourceGroup options.ResourceGroup = c.common.resourceGroup
}
if createAZ != "" {
createZones := []string{createAZ}
model.Zones = &createZones
} }
ctx, cancel := getContextWithCancel() ctx, cancel := getContextWithCancel()
defer cancel() 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 { if err != nil {
return "", err return "", err
} }
...@@ -89,7 +127,7 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun ...@@ -89,7 +127,7 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun
diskID := "" diskID := ""
err = kwait.ExponentialBackoff(defaultBackOff, func() (bool, error) { 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 diskID = id
// We are waiting for provisioningState==Succeeded // We are waiting for provisioningState==Succeeded
// We don't want to hand-off managed disks to k8s while they are // 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 ...@@ -104,9 +142,9 @@ func (c *ManagedDiskController) CreateManagedDisk(diskName string, storageAccoun
}) })
if err != nil { 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 { } 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 return diskID, nil
......
...@@ -41,6 +41,20 @@ func (az *Cloud) makeZone(zoneID int) string { ...@@ -41,6 +41,20 @@ func (az *Cloud) makeZone(zoneID int) string {
return fmt.Sprintf("%s-%d", strings.ToLower(az.Location), zoneID) return fmt.Sprintf("%s-%d", strings.ToLower(az.Location), zoneID)
} }
// isAvailabilityZone returns true if the zone is in format of <region>-<zone-id>.
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. // 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. // 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) { func (az *Cloud) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
......
...@@ -35,7 +35,7 @@ type DiskController interface { ...@@ -35,7 +35,7 @@ type DiskController interface {
CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int) (string, error) CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int) (string, error)
DeleteBlobDisk(diskUri 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 DeleteManagedDisk(diskURI string) error
// Attaches the disk to the host machine. // Attaches the disk to the host machine.
...@@ -58,6 +58,9 @@ type DiskController interface { ...@@ -58,6 +58,9 @@ type DiskController interface {
// Expand the disk to new size // Expand the disk to new size
ResizeDisk(diskURI string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) 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 { type azureDataDiskPlugin struct {
......
...@@ -19,12 +19,14 @@ package azure_dd ...@@ -19,12 +19,14 @@ package azure_dd
import ( import (
"errors" "errors"
"fmt" "fmt"
"strconv"
"strings" "strings"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util"
...@@ -68,6 +70,21 @@ func (d *azureDiskDeleter) Delete() error { ...@@ -68,6 +70,21 @@ func (d *azureDiskDeleter) Delete() error {
return diskController.DeleteBlobDisk(volumeSource.DataDiskURI) 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) { func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
if !util.AccessModesContainedInAll(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) { 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()) 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 ...@@ -85,7 +102,7 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
if len(p.options.PVC.Spec.AccessModes) == 1 { if len(p.options.PVC.Spec.AccessModes) == 1 {
if p.options.PVC.Spec.AccessModes[0] != supportedModes[0] { 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 ...@@ -96,6 +113,13 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
strKind string strKind string
err error err error
resourceGroup string resourceGroup string
zoned bool
zonePresent bool
zonesPresent bool
strZoned string
availabilityZone string
availabilityZones string
) )
// maxLength = 79 - (4 for ".vhd") = 75 // maxLength = 79 - (4 for ".vhd") = 75
name := util.GenerateVolumeName(p.options.ClusterName, p.options.PVName, 75) name := util.GenerateVolumeName(p.options.ClusterName, p.options.PVName, 75)
...@@ -123,6 +147,14 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie ...@@ -123,6 +147,14 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
fsType = strings.ToLower(v) fsType = strings.ToLower(v)
case "resourcegroup": case "resourcegroup":
resourceGroup = v resourceGroup = v
case "zone":
zonePresent = true
availabilityZone = v
case "zones":
zonesPresent = true
availabilityZones = v
case "zoned":
strZoned = v
default: default:
return nil, fmt.Errorf("AzureDisk - invalid option %s in storage class", k) return nil, fmt.Errorf("AzureDisk - invalid option %s in storage class", k)
} }
...@@ -139,6 +171,19 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie ...@@ -139,6 +171,19 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
return nil, err 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 { if cachingMode, err = normalizeCachingMode(cachingMode); err != nil {
return nil, err return nil, err
} }
...@@ -154,16 +199,39 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie ...@@ -154,16 +199,39 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
// create disk // create disk
diskURI := "" diskURI := ""
labels := map[string]string{}
if kind == v1.AzureManagedDisk { if kind == v1.AzureManagedDisk {
tags := make(map[string]string) tags := make(map[string]string)
if p.options.CloudTags != nil { if p.options.CloudTags != nil {
tags = *(p.options.CloudTags) 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 { if err != nil {
return nil, err return nil, err
} }
} else { } else {
if zoned {
return nil, errors.New("zoned parameter is only supported for managed disks")
}
if kind == v1.AzureDedicatedBlobDisk { if kind == v1.AzureDedicatedBlobDisk {
_, diskURI, _, err = diskController.CreateVolume(name, account, storageAccountType, location, requestGiB) _, diskURI, _, err = diskController.CreateVolume(name, account, storageAccountType, location, requestGiB)
if err != nil { if err != nil {
...@@ -189,7 +257,7 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie ...@@ -189,7 +257,7 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
pv := &v1.PersistentVolume{ pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: p.options.PVName, Name: p.options.PVName,
Labels: map[string]string{}, Labels: labels,
Annotations: map[string]string{ Annotations: map[string]string{
"volumehelper.VolumeDynamicallyCreatedByKey": "azure-disk-dynamic-provisioner", "volumehelper.VolumeDynamicallyCreatedByKey": "azure-disk-dynamic-provisioner",
}, },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment