Merge pull request #92825 from ZeroMagic/azurefile-tag

Add tags support for Azure File Driver
This commit is contained in:
Kubernetes Prow Robot 2020-07-11 20:55:50 -07:00 committed by GitHub
commit 23903c7f7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 243 additions and 140 deletions

View File

@ -35,11 +35,6 @@ import (
"k8s.io/legacy-cloud-providers/azure" "k8s.io/legacy-cloud-providers/azure"
) )
const (
TagsDelimiter = ","
TagKeyValueDelimiter = "="
)
type azureDiskProvisioner struct { type azureDiskProvisioner struct {
plugin *azureDataDiskPlugin plugin *azureDataDiskPlugin
options volume.VolumeOptions options volume.VolumeOptions
@ -269,7 +264,7 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
diskURI := "" diskURI := ""
labels := map[string]string{} labels := map[string]string{}
if kind == v1.AzureManagedDisk { if kind == v1.AzureManagedDisk {
tags, err := ConvertTagsToMap(customTags) tags, err := azure.ConvertTagsToMap(customTags)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -399,28 +394,3 @@ func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
return pv, nil return pv, nil
} }
// ConvertTagsToMap convert the tags from string to map
// the valid tags fomat is "key1=value1,key2=value2", which could be converted to
// {"key1": "value1", "key2": "value2"}
func ConvertTagsToMap(tags string) (map[string]string, error) {
m := make(map[string]string)
if tags == "" {
return m, nil
}
s := strings.Split(tags, TagsDelimiter)
for _, tag := range s {
kv := strings.Split(tag, TagKeyValueDelimiter)
if len(kv) != 2 {
return nil, fmt.Errorf("Tags '%s' are invalid, the format should like: 'key1=value1,key2=value2'", tags)
}
key := strings.TrimSpace(kv[0])
if key == "" {
return nil, fmt.Errorf("Tags '%s' are invalid, the format should like: 'key1=value1,key2=value2'", tags)
}
value := strings.TrimSpace(kv[1])
m[key] = value
}
return m, nil
}

View File

@ -20,7 +20,6 @@ package azure_dd
import ( import (
"fmt" "fmt"
"reflect"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -97,69 +96,3 @@ func TestParseZoned(t *testing.T) {
} }
} }
} }
func TestConvertTagsToMap(t *testing.T) {
testCases := []struct {
desc string
tags string
expectedOutput map[string]string
expectedError bool
}{
{
desc: "should return empty map when tag is empty",
tags: "",
expectedOutput: map[string]string{},
expectedError: false,
},
{
desc: "sing valid tag should be converted",
tags: "key=value",
expectedOutput: map[string]string{
"key": "value",
},
expectedError: false,
},
{
desc: "multiple valid tags should be converted",
tags: "key1=value1,key2=value2",
expectedOutput: map[string]string{
"key1": "value1",
"key2": "value2",
},
expectedError: false,
},
{
desc: "whitespaces should be trimmed",
tags: "key1=value1, key2=value2",
expectedOutput: map[string]string{
"key1": "value1",
"key2": "value2",
},
expectedError: false,
},
{
desc: "should return error for invalid format",
tags: "foo,bar",
expectedOutput: nil,
expectedError: true,
},
{
desc: "should return error for when key is missed",
tags: "key1=value1,=bar",
expectedOutput: nil,
expectedError: true,
},
}
for i, c := range testCases {
m, err := ConvertTagsToMap(c.tags)
if c.expectedError {
assert.NotNil(t, err, "TestCase[%d]: %s", i, c.desc)
} else {
assert.Nil(t, err, "TestCase[%d]: %s", i, c.desc)
if !reflect.DeepEqual(m, c.expectedOutput) {
t.Errorf("got: %v, expected: %v, desc: %v", m, c.expectedOutput, c.desc)
}
}
}
}

View File

@ -22,6 +22,7 @@ go_library(
"//staging/src/k8s.io/cloud-provider:go_default_library", "//staging/src/k8s.io/cloud-provider:go_default_library",
"//staging/src/k8s.io/cloud-provider/volume/helpers:go_default_library", "//staging/src/k8s.io/cloud-provider/volume/helpers:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/fileclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage:go_default_library", "//vendor/github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage:go_default_library",
"//vendor/k8s.io/klog/v2:go_default_library", "//vendor/k8s.io/klog/v2:go_default_library",
"//vendor/k8s.io/utils/mount:go_default_library", "//vendor/k8s.io/utils/mount:go_default_library",

View File

@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util"
"k8s.io/legacy-cloud-providers/azure" "k8s.io/legacy-cloud-providers/azure"
"k8s.io/legacy-cloud-providers/azure/clients/fileclient"
utilstrings "k8s.io/utils/strings" utilstrings "k8s.io/utils/strings"
) )
@ -47,7 +48,7 @@ var (
// azure cloud provider should implement it // azure cloud provider should implement it
type azureCloudProvider interface { type azureCloudProvider interface {
// create a file share // create a file share
CreateFileShare(shareName, accountName, accountType, accountKind, resourceGroup, location string, protocol storage.EnabledProtocols, requestGiB int) (string, string, error) CreateFileShare(account *azure.AccountOptions, fileShare *fileclient.ShareOptions) (string, string, error)
// delete a file share // delete a file share
DeleteFileShare(resourceGroup, accountName, shareName string) error DeleteFileShare(resourceGroup, accountName, shareName string) error
// resize a file share // resize a file share
@ -155,7 +156,7 @@ func (a *azureFileProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
return nil, fmt.Errorf("%s does not support block volume provisioning", a.plugin.GetPluginName()) return nil, fmt.Errorf("%s does not support block volume provisioning", a.plugin.GetPluginName())
} }
var sku, resourceGroup, location, account, shareName string var sku, resourceGroup, location, account, shareName, customTags string
capacity := a.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)] capacity := a.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
requestGiB, err := volumehelpers.RoundUpToGiBInt(capacity) requestGiB, err := volumehelpers.RoundUpToGiBInt(capacity)
@ -180,6 +181,8 @@ func (a *azureFileProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
resourceGroup = v resourceGroup = v
case "sharename": case "sharename":
shareName = v shareName = v
case "tags":
customTags = v
default: default:
return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, a.plugin.GetPluginName()) return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, a.plugin.GetPluginName())
} }
@ -189,6 +192,11 @@ func (a *azureFileProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
return nil, fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on Azure file") return nil, fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on Azure file")
} }
tags, err := azure.ConvertTagsToMap(customTags)
if err != nil {
return nil, err
}
if shareName == "" { if shareName == "" {
// File share name has a length limit of 63, and it cannot contain two consecutive '-'s. // File share name has a length limit of 63, and it cannot contain two consecutive '-'s.
name := util.GenerateVolumeName(a.options.ClusterName, a.options.PVName, 63) name := util.GenerateVolumeName(a.options.ClusterName, a.options.PVName, 63)
@ -204,7 +212,23 @@ func (a *azureFileProvisioner) Provision(selectedNode *v1.Node, allowedTopologie
if strings.HasPrefix(strings.ToLower(sku), "premium") { if strings.HasPrefix(strings.ToLower(sku), "premium") {
accountKind = string(storage.FileStorage) accountKind = string(storage.FileStorage)
} }
account, key, err := a.azureProvider.CreateFileShare(shareName, account, sku, accountKind, resourceGroup, location, storage.SMB, requestGiB)
accountOptions := &azure.AccountOptions{
Name: account,
Type: sku,
Kind: accountKind,
ResourceGroup: resourceGroup,
Location: location,
Tags: tags,
}
shareOptions := &fileclient.ShareOptions{
Name: shareName,
Protocol: storage.SMB,
RequestGiB: requestGiB,
}
account, key, err := a.azureProvider.CreateFileShare(accountOptions, shareOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -151,6 +151,7 @@ go_test(
"//staging/src/k8s.io/legacy-cloud-providers/azure/cache:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure/cache:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/diskclient/mockdiskclient:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure/clients/diskclient/mockdiskclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/fileclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/fileclient/mockfileclient:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure/clients/fileclient/mockfileclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/interfaceclient/mockinterfaceclient:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure/clients/interfaceclient/mockinterfaceclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient/mockloadbalancerclient:go_default_library", "//staging/src/k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient/mockloadbalancerclient:go_default_library",

View File

@ -84,7 +84,15 @@ func (c *BlobDiskController) initStorageAccounts() {
// If no storage account is given, search all the storage accounts associated with the resource group and pick one that // 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. // fits storage type and location.
func (c *BlobDiskController) CreateVolume(blobName, accountName, accountType, location string, requestGB int) (string, string, int, error) { func (c *BlobDiskController) CreateVolume(blobName, accountName, accountType, location string, requestGB int) (string, string, int, error) {
account, key, err := c.common.cloud.EnsureStorageAccount(accountName, accountType, string(defaultStorageAccountKind), c.common.resourceGroup, location, dedicatedDiskAccountNamePrefix, true) accountOptions := &AccountOptions{
Name: accountName,
Type: accountType,
Kind: string(defaultStorageAccountKind),
ResourceGroup: c.common.resourceGroup,
Location: location,
EnableHTTPSTrafficOnly: true,
}
account, key, err := c.common.cloud.EnsureStorageAccount(accountOptions, dedicatedDiskAccountNamePrefix)
if err != nil { if err != nil {
return "", "", 0, fmt.Errorf("could not get storage key for storage account %s: %v", accountName, err) return "", "", 0, fmt.Errorf("could not get storage key for storage account %s: %v", accountName, err)
} }

View File

@ -20,11 +20,12 @@ package azure
import ( import (
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
"k8s.io/legacy-cloud-providers/azure/clients/fileclient"
) )
// create file share // create file share
func (az *Cloud) createFileShare(resourceGroupName, accountName, name string, protocol storage.EnabledProtocols, sizeGiB int) error { func (az *Cloud) createFileShare(resourceGroupName, accountName string, shareOptions *fileclient.ShareOptions) error {
return az.FileClient.CreateFileShare(resourceGroupName, accountName, name, protocol, sizeGiB) return az.FileClient.CreateFileShare(resourceGroupName, accountName, shareOptions)
} }
func (az *Cloud) deleteFileShare(resourceGroupName, accountName, name string) error { func (az *Cloud) deleteFileShare(resourceGroupName, accountName, name string) error {

View File

@ -24,6 +24,7 @@ import (
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/legacy-cloud-providers/azure/clients/fileclient"
) )
const ( const (
@ -36,25 +37,32 @@ const (
// CreateFileShare creates a file share, using a matching storage account type, account kind, etc. // CreateFileShare creates a file share, using a matching storage account type, account kind, etc.
// storage account will be created if specified account is not found // storage account will be created if specified account is not found
func (az *Cloud) CreateFileShare(shareName, accountName, accountType, accountKind, resourceGroup, location string, protocol storage.EnabledProtocols, requestGiB int) (string, string, error) { func (az *Cloud) CreateFileShare(accountOptions *AccountOptions, shareOptions *fileclient.ShareOptions) (string, string, error) {
if resourceGroup == "" { if accountOptions == nil {
resourceGroup = az.resourceGroup return "", "", fmt.Errorf("account options is nil")
}
if shareOptions == nil {
return "", "", fmt.Errorf("share options is nil")
}
if accountOptions.ResourceGroup == "" {
accountOptions.ResourceGroup = az.resourceGroup
} }
enableHTTPSTrafficOnly := true accountOptions.EnableHTTPSTrafficOnly = true
if protocol == storage.NFS { if shareOptions.Protocol == storage.NFS {
enableHTTPSTrafficOnly = false accountOptions.EnableHTTPSTrafficOnly = false
} }
account, key, err := az.EnsureStorageAccount(accountName, accountType, accountKind, resourceGroup, location, fileShareAccountNamePrefix, enableHTTPSTrafficOnly)
accountName, accountKey, err := az.EnsureStorageAccount(accountOptions, fileShareAccountNamePrefix)
if err != nil { if err != nil {
return "", "", fmt.Errorf("could not get storage key for storage account %s: %v", accountName, err) return "", "", fmt.Errorf("could not get storage key for storage account %s: %v", accountOptions.Name, err)
} }
if err := az.createFileShare(resourceGroup, account, shareName, protocol, requestGiB); err != nil { if err := az.createFileShare(accountOptions.ResourceGroup, accountName, shareOptions); err != nil {
return "", "", fmt.Errorf("failed to create share %s in account %s: %v", shareName, account, err) return "", "", fmt.Errorf("failed to create share %s in account %s: %v", shareOptions.Name, accountName, err)
} }
klog.V(4).Infof("created share %s in account %s", shareName, account) klog.V(4).Infof("created share %s in account %s", shareOptions.Name, accountOptions.Name)
return account, key, nil return accountName, accountKey, nil
} }
// DeleteFileShare deletes a file share using storage account name and key // DeleteFileShare deletes a file share using storage account name and key

View File

@ -25,6 +25,7 @@ import (
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"k8s.io/legacy-cloud-providers/azure/clients/fileclient"
"k8s.io/legacy-cloud-providers/azure/clients/fileclient/mockfileclient" "k8s.io/legacy-cloud-providers/azure/clients/fileclient/mockfileclient"
"k8s.io/legacy-cloud-providers/azure/clients/storageaccountclient/mockstorageaccountclient" "k8s.io/legacy-cloud-providers/azure/clients/storageaccountclient/mockstorageaccountclient"
) )
@ -141,7 +142,7 @@ func TestCreateFileShare(t *testing.T) {
for _, test := range tests { for _, test := range tests {
mockFileClient := mockfileclient.NewMockInterface(ctrl) mockFileClient := mockfileclient.NewMockInterface(ctrl)
cloud.FileClient = mockFileClient cloud.FileClient = mockFileClient
mockFileClient.EXPECT().CreateFileShare(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(test.err).AnyTimes() mockFileClient.EXPECT().CreateFileShare(gomock.Any(), gomock.Any(), gomock.Any()).Return(test.err).AnyTimes()
mockStorageAccountsClient := mockstorageaccountclient.NewMockInterface(ctrl) mockStorageAccountsClient := mockstorageaccountclient.NewMockInterface(ctrl)
cloud.StorageAccountClient = mockStorageAccountsClient cloud.StorageAccountClient = mockStorageAccountsClient
@ -149,7 +150,21 @@ func TestCreateFileShare(t *testing.T) {
mockStorageAccountsClient.EXPECT().ListByResourceGroup(gomock.Any(), "rg").Return(test.accounts, nil).AnyTimes() mockStorageAccountsClient.EXPECT().ListByResourceGroup(gomock.Any(), "rg").Return(test.accounts, nil).AnyTimes()
mockStorageAccountsClient.EXPECT().Create(gomock.Any(), "rg", gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockStorageAccountsClient.EXPECT().Create(gomock.Any(), "rg", gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
account, key, err := cloud.CreateFileShare(test.name, test.acct, test.acctType, test.acctKind, test.rg, test.loc, storage.SMB, test.gb) mockAccount := &AccountOptions{
Name: test.acct,
Type: test.acctType,
Kind: test.acctKind,
ResourceGroup: test.rg,
Location: test.loc,
}
mockFileShare := &fileclient.ShareOptions{
Name: test.name,
Protocol: storage.SMB,
RequestGiB: test.gb,
}
account, key, err := cloud.CreateFileShare(mockAccount, mockFileShare)
if test.expectErr && err == nil { if test.expectErr && err == nil {
t.Errorf("unexpected non-error") t.Errorf("unexpected non-error")
continue continue

View File

@ -23,11 +23,17 @@ import (
"strings" "strings"
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/klog/v2" "k8s.io/klog/v2"
) )
// AccountOptions contains the fields which are used to create storage account.
type AccountOptions struct {
Name, Type, Kind, ResourceGroup, Location string
EnableHTTPSTrafficOnly bool
Tags map[string]string
}
type accountWithLocation struct { type accountWithLocation struct {
Name, StorageType, Location string Name, StorageType, Location string
} }
@ -90,7 +96,16 @@ func (az *Cloud) GetStorageAccesskey(account, resourceGroup string) (string, err
} }
// EnsureStorageAccount search storage account, create one storage account(with genAccountNamePrefix) if not found, return accountName, accountKey // EnsureStorageAccount search storage account, create one storage account(with genAccountNamePrefix) if not found, return accountName, accountKey
func (az *Cloud) EnsureStorageAccount(accountName, accountType, accountKind, resourceGroup, location, genAccountNamePrefix string, enableHTTPSTrafficOnly bool) (string, string, error) { func (az *Cloud) EnsureStorageAccount(accountOptions *AccountOptions, genAccountNamePrefix string) (string, string, error) {
if accountOptions == nil {
return "", "", fmt.Errorf("account options is nil")
}
accountName := accountOptions.Name
accountType := accountOptions.Type
accountKind := accountOptions.Kind
resourceGroup := accountOptions.ResourceGroup
location := accountOptions.Location
enableHTTPSTrafficOnly := accountOptions.EnableHTTPSTrafficOnly
if len(accountName) == 0 { if len(accountName) == 0 {
// find a storage account that matches accountType // find a storage account that matches accountType
accounts, err := az.getStorageAccounts(accountType, accountKind, resourceGroup, location) accounts, err := az.getStorageAccounts(accountType, accountKind, resourceGroup, location)
@ -118,13 +133,20 @@ func (az *Cloud) EnsureStorageAccount(accountName, accountType, accountKind, res
if accountKind != "" { if accountKind != "" {
kind = storage.Kind(accountKind) kind = storage.Kind(accountKind)
} }
klog.V(2).Infof("azure - no matching account found, begin to create a new account %s in resource group %s, location: %s, accountType: %s, accountKind: %s", if len(accountOptions.Tags) == 0 {
accountName, resourceGroup, location, accountType, kind) accountOptions.Tags = make(map[string]string)
}
accountOptions.Tags["created-by"] = "azure"
tags := convertMaptoMapPointer(accountOptions.Tags)
klog.V(2).Infof("azure - no matching account found, begin to create a new account %s in resource group %s, location: %s, accountType: %s, accountKind: %s, tags: %+v",
accountName, resourceGroup, location, accountType, kind, accountOptions.Tags)
cp := storage.AccountCreateParameters{ cp := storage.AccountCreateParameters{
Sku: &storage.Sku{Name: storage.SkuName(accountType)}, Sku: &storage.Sku{Name: storage.SkuName(accountType)},
Kind: kind, Kind: kind,
AccountPropertiesCreateParameters: &storage.AccountPropertiesCreateParameters{EnableHTTPSTrafficOnly: &enableHTTPSTrafficOnly}, AccountPropertiesCreateParameters: &storage.AccountPropertiesCreateParameters{EnableHTTPSTrafficOnly: &enableHTTPSTrafficOnly},
Tags: map[string]*string{"created-by": to.StringPtr("azure")}, Tags: tags,
Location: &location} Location: &location}
ctx, cancel := getContextWithCancel() ctx, cancel := getContextWithCancel()

View File

@ -20,9 +20,16 @@ package azure
import ( import (
"context" "context"
"fmt"
"strings"
"sync" "sync"
) )
const (
tagsDelimiter = ","
tagKeyValueDelimiter = "="
)
// lockMap used to lock on entries // lockMap used to lock on entries
type lockMap struct { type lockMap struct {
sync.Mutex sync.Mutex
@ -74,3 +81,36 @@ func (lm *lockMap) unlockEntry(entry string) {
func getContextWithCancel() (context.Context, context.CancelFunc) { func getContextWithCancel() (context.Context, context.CancelFunc) {
return context.WithCancel(context.Background()) return context.WithCancel(context.Background())
} }
// ConvertTagsToMap convert the tags from string to map
// the valid tags fomat is "key1=value1,key2=value2", which could be converted to
// {"key1": "value1", "key2": "value2"}
func ConvertTagsToMap(tags string) (map[string]string, error) {
m := make(map[string]string)
if tags == "" {
return m, nil
}
s := strings.Split(tags, tagsDelimiter)
for _, tag := range s {
kv := strings.Split(tag, tagKeyValueDelimiter)
if len(kv) != 2 {
return nil, fmt.Errorf("Tags '%s' are invalid, the format should like: 'key1=value1,key2=value2'", tags)
}
key := strings.TrimSpace(kv[0])
if key == "" {
return nil, fmt.Errorf("Tags '%s' are invalid, the format should like: 'key1=value1,key2=value2'", tags)
}
value := strings.TrimSpace(kv[1])
m[key] = value
}
return m, nil
}
func convertMaptoMapPointer(origin map[string]string) map[string]*string {
newly := make(map[string]*string)
for k, v := range origin {
newly[k] = &v
}
return newly
}

View File

@ -19,8 +19,11 @@ limitations under the License.
package azure package azure
import ( import (
"reflect"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
) )
func TestSimpleLockEntry(t *testing.T) { func TestSimpleLockEntry(t *testing.T) {
@ -83,3 +86,69 @@ func ensureNoCallback(t *testing.T, callbackChan <-chan interface{}) bool {
return true return true
} }
} }
func TestConvertTagsToMap(t *testing.T) {
testCases := []struct {
desc string
tags string
expectedOutput map[string]string
expectedError bool
}{
{
desc: "should return empty map when tag is empty",
tags: "",
expectedOutput: map[string]string{},
expectedError: false,
},
{
desc: "sing valid tag should be converted",
tags: "key=value",
expectedOutput: map[string]string{
"key": "value",
},
expectedError: false,
},
{
desc: "multiple valid tags should be converted",
tags: "key1=value1,key2=value2",
expectedOutput: map[string]string{
"key1": "value1",
"key2": "value2",
},
expectedError: false,
},
{
desc: "whitespaces should be trimmed",
tags: "key1=value1, key2=value2",
expectedOutput: map[string]string{
"key1": "value1",
"key2": "value2",
},
expectedError: false,
},
{
desc: "should return error for invalid format",
tags: "foo,bar",
expectedOutput: nil,
expectedError: true,
},
{
desc: "should return error for when key is missed",
tags: "key1=value1,=bar",
expectedOutput: nil,
expectedError: true,
},
}
for i, c := range testCases {
m, err := ConvertTagsToMap(c.tags)
if c.expectedError {
assert.NotNil(t, err, "TestCase[%d]: %s", i, c.desc)
} else {
assert.Nil(t, err, "TestCase[%d]: %s", i, c.desc)
if !reflect.DeepEqual(m, c.expectedOutput) {
t.Errorf("got: %v, expected: %v, desc: %v", m, c.expectedOutput, c.desc)
}
}
}
}

View File

@ -35,6 +35,13 @@ type Client struct {
fileSharesClient storage.FileSharesClient fileSharesClient storage.FileSharesClient
} }
// ShareOptions contains the fields which are used to create file share.
type ShareOptions struct {
Name string
Protocol storage.EnabledProtocols
RequestGiB int
}
// New creates a azure file client // New creates a azure file client
func New(config *azclients.ClientConfig) *Client { func New(config *azclients.ClientConfig) *Client {
client := storage.NewFileSharesClientWithBaseURI(config.ResourceManagerEndpoint, config.SubscriptionID) client := storage.NewFileSharesClientWithBaseURI(config.ResourceManagerEndpoint, config.SubscriptionID)
@ -46,27 +53,30 @@ func New(config *azclients.ClientConfig) *Client {
} }
// CreateFileShare creates a file share // CreateFileShare creates a file share
func (c *Client) CreateFileShare(resourceGroupName, accountName, name string, protocol storage.EnabledProtocols, sizeGiB int) error { func (c *Client) CreateFileShare(resourceGroupName, accountName string, shareOptions *ShareOptions) error {
result, err := c.GetFileShare(resourceGroupName, accountName, name) if shareOptions == nil {
return fmt.Errorf("share options is nil")
}
result, err := c.GetFileShare(resourceGroupName, accountName, shareOptions.Name)
if err == nil { if err == nil {
klog.V(2).Infof("file share(%s) under account(%s) rg(%s) already exists", name, accountName, resourceGroupName) klog.V(2).Infof("file share(%s) under account(%s) rg(%s) already exists", shareOptions.Name, accountName, resourceGroupName)
return nil return nil
} else if err != nil && result.Response.StatusCode != http.StatusNotFound && !strings.Contains(err.Error(), "ShareNotFound") { } else if result.Response.Response == nil || (err != nil && result.Response.Response.StatusCode != http.StatusNotFound && !strings.Contains(err.Error(), "ShareNotFound")) {
return fmt.Errorf("failed to get file share(%s), err: %v", name, err) return fmt.Errorf("failed to get file share(%s), err: %v", shareOptions.Name, err)
} }
quota := int32(sizeGiB) quota := int32(shareOptions.RequestGiB)
fileShareProperties := &storage.FileShareProperties{ fileShareProperties := &storage.FileShareProperties{
ShareQuota: &quota, ShareQuota: &quota,
} }
if protocol == storage.NFS { if shareOptions.Protocol == storage.NFS {
fileShareProperties.EnabledProtocols = protocol fileShareProperties.EnabledProtocols = shareOptions.Protocol
} }
fileShare := storage.FileShare{ fileShare := storage.FileShare{
Name: &name, Name: &shareOptions.Name,
FileShareProperties: fileShareProperties, FileShareProperties: fileShareProperties,
} }
_, err = c.fileSharesClient.Create(context.Background(), resourceGroupName, accountName, name, fileShare) _, err = c.fileSharesClient.Create(context.Background(), resourceGroupName, accountName, shareOptions.Name, fileShare)
return err return err
} }

View File

@ -25,7 +25,7 @@ import (
// Interface is the client interface for creating file shares, interface for test injection. // Interface is the client interface for creating file shares, interface for test injection.
// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/fileclient/interface.go -package=mockfileclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/fileclient/mockfileclient/interface.go // mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/fileclient/interface.go -package=mockfileclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/fileclient/mockfileclient/interface.go
type Interface interface { type Interface interface {
CreateFileShare(resourceGroupName, accountName, name string, protocol storage.EnabledProtocols, sizeGiB int) error CreateFileShare(resourceGroupName, accountName string, shareOptions *ShareOptions) error
DeleteFileShare(resourceGroupName, accountName, name string) error DeleteFileShare(resourceGroupName, accountName, name string) error
ResizeFileShare(resourceGroupName, accountName, name string, sizeGiB int) error ResizeFileShare(resourceGroupName, accountName, name string, sizeGiB int) error
GetFileShare(resourceGroupName, accountName, name string) (storage.FileShare, error) GetFileShare(resourceGroupName, accountName, name string) (storage.FileShare, error)

View File

@ -10,6 +10,7 @@ go_library(
importpath = "k8s.io/legacy-cloud-providers/azure/clients/fileclient/mockfileclient", importpath = "k8s.io/legacy-cloud-providers/azure/clients/fileclient/mockfileclient",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/fileclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage:go_default_library", "//vendor/github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library", "//vendor/github.com/golang/mock/gomock:go_default_library",
], ],

View File

@ -19,10 +19,10 @@ limitations under the License.
package mockfileclient package mockfileclient
import ( import (
reflect "reflect"
storage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" storage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
fileclient "k8s.io/legacy-cloud-providers/azure/clients/fileclient"
reflect "reflect"
) )
// MockInterface is a mock of Interface interface // MockInterface is a mock of Interface interface
@ -49,17 +49,17 @@ func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
} }
// CreateFileShare mocks base method // CreateFileShare mocks base method
func (m *MockInterface) CreateFileShare(resourceGroupName, accountName, name string, protocol storage.EnabledProtocols, sizeGiB int) error { func (m *MockInterface) CreateFileShare(resourceGroupName, accountName string, shareOptions *fileclient.ShareOptions) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateFileShare", resourceGroupName, accountName, name, protocol, sizeGiB) ret := m.ctrl.Call(m, "CreateFileShare", resourceGroupName, accountName, shareOptions)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// CreateFileShare indicates an expected call of CreateFileShare // CreateFileShare indicates an expected call of CreateFileShare
func (mr *MockInterfaceMockRecorder) CreateFileShare(resourceGroupName, accountName, name, protocol, sizeGiB interface{}) *gomock.Call { func (mr *MockInterfaceMockRecorder) CreateFileShare(resourceGroupName, accountName, shareOptions interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFileShare", reflect.TypeOf((*MockInterface)(nil).CreateFileShare), resourceGroupName, accountName, name, protocol, sizeGiB) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFileShare", reflect.TypeOf((*MockInterface)(nil).CreateFileShare), resourceGroupName, accountName, shareOptions)
} }
// DeleteFileShare mocks base method // DeleteFileShare mocks base method