kubernetes/pkg/cloudprovider/providers/azure/azure_storage.go
Kubernetes Submit Queue 3d33b45e43 Merge pull request #30091 from rootfs/azure-storage
Automatic merge from submit-queue

support Azure disk dynamic provisioning

azure disk dynamic provisioning

A screen shot 

``` console
$ kubectl create -f examples/experimental/persistent-volume-provisioning/azure-dd.yaml
storageclass "slow" created
$ kubectl create -f examples/experimental/persistent-volume-provisioning/claim1.json
persistentvolumeclaim "claim1" created
$ kubectl describe pvc
Name:       claim1
Namespace:  default
Status:     Bound
Volume:     pvc-de7150d1-6a37-11e6-aec9-000d3a12e034
Labels:     <none>
Capacity:   3Gi
Access Modes:   RWO
$ kubectl create -f pod.yaml
replicationcontroller "nfs-server" created
$ kubectl describe pod
Name:       nfs-server-b9w6x
Namespace:  default
Node:       rootfs-dev/172.24.0.4
Start Time: Wed, 24 Aug 2016 19:46:21 +0000
Labels:     role=nfs-server
Status:     Running
IP:     172.17.0.2
Controllers:    ReplicationController/nfs-server
Containers:
  nfs-server:
    Container ID:   docker://be6f8c0e26dc896d4c53ef0d21c9414982f0b39a10facd6b93a255f9e1c3806c
    Image:      nginx
    Image ID:       docker://bfdd4ced794ed276a28cf56b233ea58dec544e9ca329d796cf30b8bcf6d39b3f
    Port:       
    State:      Running
      Started:      Wed, 24 Aug 2016 19:49:19 +0000
    Ready:      True
    Restart Count:  0
    Volume Mounts:
      /exports from mypvc (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-9o0fj (ro)
    Environment Variables:  <none>
Conditions:
  Type      Status
  Initialized   True 
  Ready     True 
  PodScheduled  True 
Volumes:
  mypvc:
    Type:   PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  claim1
    ReadOnly:   false
  default-token-9o0fj:
    Type:   Secret (a volume populated by a Secret)
    SecretName: default-token-9o0fj
QoS Class:  BestEffort
Tolerations:    <none>
Events:
  FirstSeen LastSeen    Count   From            SubobjectPath           Type        Reason      Message
  --------- --------    -----   ----            -------------           --------    ------      -------
  11m       11m     1   {default-scheduler }                    Normal      Scheduled   Successfully assigned nfs-server-b9w6x to rootfs-dev
  9m        9m      1   {kubelet rootfs-dev}                    Warning     FailedMount Unable to mount volumes for pod "nfs-server-b9w6x_default(6eb7fd98-6a33-11e6-aec9-000d3a12e034)": timeout expired waiting for volumes to attach/mount for pod "nfs-server-b9w6x"/"default". list of unattached/unmounted volumes=[mypvc]
  9m        9m      1   {kubelet rootfs-dev}                    Warning     FailedSync  Error syncing pod, skipping: timeout expired waiting for volumes to attach/mount for pod "nfs-server-b9w6x"/"default". list of unattached/unmounted volumes=[mypvc]
  8m        8m      1   {kubelet rootfs-dev}    spec.containers{nfs-server} Normal      Pulling     pulling image "nginx"
  8m        8m      1   {kubelet rootfs-dev}    spec.containers{nfs-server} Normal      Pulled      Successfully pulled image "nginx"
  8m        8m      1   {kubelet rootfs-dev}    spec.containers{nfs-server} Normal      Created     Created container with docker id be6f8c0e26dc
  8m        8m      1   {kubelet rootfs-dev}    spec.containers{nfs-server} Normal      Started     Started container with docker id be6f8c0e26dc

```

@colemickens @brendandburns
2016-11-01 17:27:14 -07:00

244 lines
7.9 KiB
Go

/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package azure
import (
"fmt"
"strings"
"github.com/Azure/azure-sdk-for-go/arm/compute"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/types"
)
const (
maxLUN = 64 // max number of LUNs per VM
)
// AttachDisk attaches a vhd to vm
// the vhd must exist, can be identified by diskName, diskURI, and lun.
func (az *Cloud) AttachDisk(diskName, diskURI string, nodeName types.NodeName, lun int32, cachingMode compute.CachingTypes) error {
vm, exists, err := az.getVirtualMachine(nodeName)
if err != nil {
return err
} else if !exists {
return cloudprovider.InstanceNotFound
}
disks := *vm.Properties.StorageProfile.DataDisks
disks = append(disks,
compute.DataDisk{
Name: &diskName,
Vhd: &compute.VirtualHardDisk{
URI: &diskURI,
},
Lun: &lun,
Caching: cachingMode,
CreateOption: "attach",
})
newVM := compute.VirtualMachine{
Location: vm.Location,
Properties: &compute.VirtualMachineProperties{
StorageProfile: &compute.StorageProfile{
DataDisks: &disks,
},
},
}
vmName := mapNodeNameToVMName(nodeName)
_, err = az.VirtualMachinesClient.CreateOrUpdate(az.ResourceGroup, vmName, newVM, nil)
if err != nil {
glog.Errorf("azure attach failed, err: %v", err)
detail := err.Error()
if strings.Contains(detail, "Code=\"AcquireDiskLeaseFailed\"") {
// if lease cannot be acquired, immediately detach the disk and return the original error
glog.Infof("failed to acquire disk lease, try detach")
az.DetachDiskByName(diskName, diskURI, nodeName)
}
} else {
glog.V(4).Infof("azure attach succeeded")
}
return err
}
// DisksAreAttached checks if a list of volumes are attached to the node with the specified NodeName
func (az *Cloud) DisksAreAttached(diskNames []string, nodeName types.NodeName) (map[string]bool, error) {
attached := make(map[string]bool)
for _, diskName := range diskNames {
attached[diskName] = false
}
vm, exists, err := az.getVirtualMachine(nodeName)
if !exists {
// if host doesn't exist, no need to detach
glog.Warningf("Cannot find node %q, DisksAreAttached will assume disks %v are not attached to it.",
nodeName, diskNames)
return attached, nil
} else if err != nil {
return attached, err
}
disks := *vm.Properties.StorageProfile.DataDisks
for _, disk := range disks {
for _, diskName := range diskNames {
if disk.Name != nil && diskName != "" && *disk.Name == diskName {
attached[diskName] = true
}
}
}
return attached, nil
}
// DetachDiskByName detaches a vhd from host
// the vhd can be identified by diskName or diskURI
func (az *Cloud) DetachDiskByName(diskName, diskURI string, nodeName types.NodeName) error {
vm, exists, err := az.getVirtualMachine(nodeName)
if err != nil || !exists {
// if host doesn't exist, no need to detach
glog.Warningf("cannot find node %s, skip detaching disk %s", nodeName, diskName)
return nil
}
disks := *vm.Properties.StorageProfile.DataDisks
for i, disk := range disks {
if (disk.Name != nil && diskName != "" && *disk.Name == diskName) || (disk.Vhd.URI != nil && diskURI != "" && *disk.Vhd.URI == diskURI) {
// found the disk
glog.V(4).Infof("detach disk: name %q uri %q", diskName, diskURI)
disks = append(disks[:i], disks[i+1:]...)
break
}
}
newVM := compute.VirtualMachine{
Location: vm.Location,
Properties: &compute.VirtualMachineProperties{
StorageProfile: &compute.StorageProfile{
DataDisks: &disks,
},
},
}
vmName := mapNodeNameToVMName(nodeName)
_, err = az.VirtualMachinesClient.CreateOrUpdate(az.ResourceGroup, vmName, newVM, nil)
if err != nil {
glog.Errorf("azure disk detach failed, err: %v", err)
} else {
glog.V(4).Infof("azure disk detach succeeded")
}
return err
}
// GetDiskLun finds the lun on the host that the vhd is attached to, given a vhd's diskName and diskURI
func (az *Cloud) GetDiskLun(diskName, diskURI string, nodeName types.NodeName) (int32, error) {
vm, exists, err := az.getVirtualMachine(nodeName)
if err != nil {
return -1, err
} else if !exists {
return -1, cloudprovider.InstanceNotFound
}
disks := *vm.Properties.StorageProfile.DataDisks
for _, disk := range disks {
if disk.Lun != nil && (disk.Name != nil && diskName != "" && *disk.Name == diskName) || (disk.Vhd.URI != nil && diskURI != "" && *disk.Vhd.URI == diskURI) {
// found the disk
glog.V(4).Infof("find disk: lun %d name %q uri %q", *disk.Lun, diskName, diskURI)
return *disk.Lun, nil
}
}
return -1, fmt.Errorf("Cannot find Lun for disk %s", diskName)
}
// GetNextDiskLun searches all vhd attachment on the host and find unused lun
// return -1 if all luns are used
func (az *Cloud) GetNextDiskLun(nodeName types.NodeName) (int32, error) {
vm, exists, err := az.getVirtualMachine(nodeName)
if err != nil {
return -1, err
} else if !exists {
return -1, cloudprovider.InstanceNotFound
}
used := make([]bool, maxLUN)
disks := *vm.Properties.StorageProfile.DataDisks
for _, disk := range disks {
if disk.Lun != nil {
used[*disk.Lun] = true
}
}
for k, v := range used {
if !v {
return int32(k), nil
}
}
return -1, fmt.Errorf("All Luns are used")
}
// CreateVolume creates a VHD blob in a storage account that has storageType and location using the given storage account.
// If no storage account is given, search all the storage accounts associated with the resource group and pick one that
// fits storage type and location.
func (az *Cloud) CreateVolume(name, storageAccount, storageType, location string, requestGB int) (string, string, int, error) {
var err error
accounts := []accountWithLocation{}
if len(storageAccount) > 0 {
accounts = append(accounts, accountWithLocation{Name: storageAccount})
} else {
// find a storage account
accounts, err = az.getStorageAccounts()
if err != nil {
// TODO: create a storage account and container
return "", "", 0, err
}
}
for _, account := range accounts {
glog.V(4).Infof("account %s type %s location %s", account.Name, account.StorageType, account.Location)
if ((storageType == "" || account.StorageType == storageType) && (location == "" || account.Location == location)) || len(storageAccount) > 0 {
// find the access key with this account
key, err := az.getStorageAccesskey(account.Name)
if err != nil {
glog.V(2).Infof("no key found for storage account %s", account.Name)
continue
}
// create a page blob in this account's vhd container
name, uri, err := az.createVhdBlob(account.Name, key, name, int64(requestGB), nil)
if err != nil {
glog.V(2).Infof("failed to create vhd in account %s: %v", account.Name, err)
continue
}
glog.V(4).Infof("created vhd blob uri: %s", uri)
return name, uri, requestGB, err
}
}
return "", "", 0, fmt.Errorf("failed to find a matching storage account")
}
// DeleteVolume deletes a VHD blob
func (az *Cloud) DeleteVolume(name, uri string) error {
accountName, blob, err := az.getBlobNameAndAccountFromURI(uri)
if err != nil {
return fmt.Errorf("failed to parse vhd URI %v", err)
}
key, err := az.getStorageAccesskey(accountName)
if err != nil {
return fmt.Errorf("no key for storage account %s, err %v", accountName, err)
}
err = az.deleteVhdBlob(accountName, key, blob)
if err != nil {
glog.Warningf("failed to delete blob %s err: %v", uri, err)
return fmt.Errorf("failed to delete vhd %v, account %s, blob %s, err: %v", uri, accountName, blob, err)
}
glog.V(4).Infof("blob %s deleted", uri)
return nil
}