New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support Azure disk dynamic provisioning #30091

Merged
merged 3 commits into from Nov 2, 2016
Jump to file or symbol
Failed to load files and symbols.
+6,400 −121
Diff settings

Always

Just for now

View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -35,6 +35,7 @@ go_library(
"//pkg/cloudprovider:go_default_library",
"//pkg/cloudprovider/providers:go_default_library",
"//pkg/cloudprovider/providers/aws:go_default_library",
"//pkg/cloudprovider/providers/azure:go_default_library",
"//pkg/cloudprovider/providers/gce:go_default_library",
"//pkg/cloudprovider/providers/openstack:go_default_library",
"//pkg/cloudprovider/providers/vsphere:go_default_library",
@@ -31,6 +31,7 @@ import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
"k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
"k8s.io/kubernetes/pkg/cloudprovider/providers/openstack"
"k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere"
@@ -121,6 +122,8 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componen
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
case vsphere.ProviderName == cloud.ProviderName():
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
case azure.CloudProviderName == cloud.ProviderName():
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
}
}
@@ -149,6 +152,8 @@ func NewAlphaVolumeProvisioner(cloud cloudprovider.Interface, config componentco
return getProvisionablePluginFromVolumePlugins(cinder.ProbeVolumePlugins())
case cloud != nil && vsphere.ProviderName == cloud.ProviderName():
return getProvisionablePluginFromVolumePlugins(vsphere_volume.ProbeVolumePlugins())
case cloud != nil && azure.CloudProviderName == cloud.ProviderName():
return getProvisionablePluginFromVolumePlugins(azure_dd.ProbeVolumePlugins())
}
return nil, nil
}
@@ -224,6 +224,23 @@ Create a Pod to use the PVC:
$ kubectl create -f examples/experimental/persistent-volume-provisioning/quobyte/example-pod.yaml
```
#### Azure Disk
```yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
name: slow
provisioner: kubernetes.io/azure-disk
parameters:
skuName: Standard_LRS
location: eastus
storageAccount: azure_storage_account_name
```
* `skuName`: Azure storage account Sku tier. Default is empty.
* `location`: Azure storage account location. Default is empty.
* `storageAccount`: Azure storage account name. If storage account is not provided, all storage accounts associated with the resource group are searched to find one that matches `skuName` and `location`. If storage account is provided, `skuName` and `location` are ignored.
### User provisioning requests
@@ -14,13 +14,16 @@ go_library(
name = "go_default_library",
srcs = [
"azure.go",
"azure_blob.go",
"azure_instances.go",
"azure_loadbalancer.go",
"azure_routes.go",
"azure_storage.go",
"azure_storageaccount.go",
"azure_util.go",
"azure_wrap.go",
"azure_zones.go",
"vhd.go",
],
tags = ["automanaged"],
deps = [
@@ -30,11 +33,14 @@ go_library(
"//pkg/util/errors:go_default_library",
"//vendor:github.com/Azure/azure-sdk-for-go/arm/compute",
"//vendor:github.com/Azure/azure-sdk-for-go/arm/network",
"//vendor:github.com/Azure/azure-sdk-for-go/arm/storage",
"//vendor:github.com/Azure/azure-sdk-for-go/storage",
"//vendor:github.com/Azure/go-autorest/autorest",
"//vendor:github.com/Azure/go-autorest/autorest/azure",
"//vendor:github.com/Azure/go-autorest/autorest/to",
"//vendor:github.com/ghodss/yaml",
"//vendor:github.com/golang/glog",
"//vendor:github.com/rubiojr/go-vhd/vhd",
],
)
@@ -24,6 +24,7 @@ import (
"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/storage"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/ghodss/yaml"
)
@@ -61,6 +62,7 @@ type Cloud struct {
PublicIPAddressesClient network.PublicIPAddressesClient
SecurityGroupsClient network.SecurityGroupsClient
VirtualMachinesClient compute.VirtualMachinesClient
StorageAccountClient storage.AccountsClient
}
func init() {
@@ -135,6 +137,8 @@ func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) {
az.SecurityGroupsClient.BaseURI = az.Environment.ResourceManagerEndpoint
az.SecurityGroupsClient.Authorizer = servicePrincipalToken
az.StorageAccountClient = storage.NewAccountsClientWithBaseURI(az.Environment.ResourceManagerEndpoint, az.SubscriptionID)
az.StorageAccountClient.Authorizer = servicePrincipalToken
return &az, nil
}
@@ -0,0 +1,99 @@
/*
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"
"regexp"
azs "github.com/Azure/azure-sdk-for-go/storage"
)
const (
vhdContainerName = "vhds"
useHTTPS = true
blobServiceName = "blob"
)
// create page blob
func (az *Cloud) createVhdBlob(accountName, accountKey, name string, sizeGB int64, tags map[string]string) (string, string, error) {
blobClient, err := az.getBlobClient(accountName, accountKey)
if err != nil {
return "", "", err
}
size := 1024 * 1024 * 1024 * sizeGB
vhdSize := size + vhdHeaderSize /* header size */
// Blob name in URL must end with '.vhd' extension.
name = name + ".vhd"
err = blobClient.PutPageBlob(vhdContainerName, name, vhdSize, tags)
if err != nil {
return "", "", fmt.Errorf("failed to put page blob: %v", err)
}
// add VHD signature to the blob
h, err := createVHDHeader(uint64(size))
if err != nil {
az.deleteVhdBlob(accountName, accountKey, name)
return "", "", fmt.Errorf("failed to create vhd header, err: %v", err)
}
if err = blobClient.PutPage(vhdContainerName, name, size, vhdSize-1, azs.PageWriteTypeUpdate, h[:vhdHeaderSize], nil); err != nil {
az.deleteVhdBlob(accountName, accountKey, name)
return "", "", fmt.Errorf("failed to update vhd header, err: %v", err)
}
scheme := "http"
if useHTTPS {
scheme = "https"
}
host := fmt.Sprintf("%s://%s.%s.%s", scheme, accountName, blobServiceName, az.Environment.StorageEndpointSuffix)
uri := fmt.Sprintf("%s/%s/%s", host, vhdContainerName, name)
return name, uri, nil
}
// delete a vhd blob
func (az *Cloud) deleteVhdBlob(accountName, accountKey, blobName string) error {
blobClient, err := az.getBlobClient(accountName, accountKey)
if err == nil {
return blobClient.DeleteBlob(vhdContainerName, blobName, nil)
}
return err
}
func (az *Cloud) getBlobClient(accountName, accountKey string) (*azs.BlobStorageClient, error) {
client, err := azs.NewClient(accountName, accountKey, az.Environment.StorageEndpointSuffix, azs.DefaultAPIVersion, useHTTPS)
if err != nil {
return nil, fmt.Errorf("error creating azure client: %v", err)
}
b := client.GetBlobService()
return &b, nil
}
// get uri https://foo.blob.core.windows.net/vhds/bar.vhd and return foo (account) and bar.vhd (blob name)
func (az *Cloud) getBlobNameAndAccountFromURI(uri string) (string, string, error) {
scheme := "http"
if useHTTPS {
scheme = "https"
}
host := fmt.Sprintf("%s://(.*).%s.%s", scheme, blobServiceName, az.Environment.StorageEndpointSuffix)
reStr := fmt.Sprintf("%s/%s/(.*)", host, vhdContainerName)
re := regexp.MustCompile(reStr)
res := re.FindSubmatch([]byte(uri))
if len(res) < 3 {
return "", "", fmt.Errorf("invalid vhd URI for regex %s: %s", reStr, uri)
}
return string(res[1]), string(res[2]), nil
}
@@ -154,3 +154,62 @@ func (az *Cloud) GetNextDiskLun(nodeName types.NodeName) (int32, error) {
}
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
}
@@ -0,0 +1,77 @@
/*
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"
)
type accountWithLocation struct {
Name, StorageType, Location string
}
// getStorageAccounts gets the storage accounts' name, type, location in a resource group
func (az *Cloud) getStorageAccounts() ([]accountWithLocation, error) {
result, err := az.StorageAccountClient.ListByResourceGroup(az.ResourceGroup)
if err != nil {
return nil, err
}
if result.Value == nil {
return nil, fmt.Errorf("no storage accounts from resource group %s", az.ResourceGroup)
}
accounts := []accountWithLocation{}
for _, acct := range *result.Value {
if acct.Name != nil {
name := *acct.Name
loc := ""
if acct.Location != nil {
loc = *acct.Location
}
storageType := ""
if acct.Sku != nil {
storageType = string((*acct.Sku).Name)
}
accounts = append(accounts, accountWithLocation{Name: name, StorageType: storageType, Location: loc})
}
}
return accounts, nil
}
// getStorageAccesskey gets the storage account access key
func (az *Cloud) getStorageAccesskey(account string) (string, error) {
result, err := az.StorageAccountClient.ListKeys(az.ResourceGroup, account)
if err != nil {
return "", err
}
if result.Keys == nil {
return "", fmt.Errorf("empty keys")
}
for _, k := range *result.Keys {
if k.Value != nil && *k.Value != "" {
v := *k.Value
if ind := strings.LastIndex(v, " "); ind >= 0 {
v = v[(ind + 1):]
}
return v, nil
}
}
return "", fmt.Errorf("no valid keys")
}
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.