Skip to content
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

Remove PV annotations for Gluster provisioner. #35022

Merged
merged 1 commit into from
Oct 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
150 changes: 61 additions & 89 deletions pkg/volume/glusterfs/glusterfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,21 +403,16 @@ func (d *glusterfsVolumeDeleter) Delete() error {
volumeName := d.glusterfsMounter.path
volumeId := dstrings.TrimPrefix(volumeName, volprefix)

err = d.annotationsToParam(d.spec)
class, err := volutil.GetClassForVolume(d.plugin.host.GetKubeClient(), d.spec)
if err != nil {
return err
}
if len(d.secretName) > 0 {
d.secretValue, err = parseSecret(d.secretNamespace, d.secretName, d.plugin.host.GetKubeClient())
if err != nil {
glog.Errorf("glusterfs: failed to read secret: %v", err)
return err
}
} else if len(d.userKey) > 0 {
d.secretValue = d.userKey
} else {
d.secretValue = ""

cfg, err := parseClassParameters(class.Parameters, d.plugin.host.GetKubeClient())
if err != nil {
return err
}
d.provisioningConfig = *cfg

glog.V(4).Infof("glusterfs: deleting volume %q with configuration %+v", volumeId, d.provisioningConfig)

Expand All @@ -444,56 +439,11 @@ func (r *glusterfsVolumeProvisioner) Provision() (*api.PersistentVolume, error)
}
glog.V(4).Infof("glusterfs: Provison VolumeOptions %v", r.options)

authEnabled := true
for k, v := range r.options.Parameters {
switch dstrings.ToLower(k) {
case "endpoint":
r.endpoint = v
case "resturl":
r.url = v
case "restuser":
r.user = v
case "restuserkey":
r.userKey = v
case "secretname":
r.secretName = v
case "secretnamespace":
r.secretNamespace = v
case "restauthenabled":
authEnabled = dstrings.ToLower(v) == "true"
default:
return nil, fmt.Errorf("glusterfs: invalid option %q for volume plugin %s", k, r.plugin.GetPluginName())
}
}

if len(r.url) == 0 {
return nil, fmt.Errorf("StorageClass for provisioner %q must contain 'resturl' parameter", r.plugin.GetPluginName())
}
if len(r.endpoint) == 0 {
return nil, fmt.Errorf("StorageClass for provisioner %q must contain 'endpoint' parameter", r.plugin.GetPluginName())
}

if !authEnabled {
r.user = ""
r.secretName = ""
r.secretNamespace = ""
r.userKey = ""
r.secretValue = ""
}

if len(r.secretName) != 0 || len(r.secretNamespace) != 0 {
// secretName + Namespace has precedence over userKey
if len(r.secretName) != 0 && len(r.secretNamespace) != 0 {
r.secretValue, err = parseSecret(r.secretNamespace, r.secretName, r.plugin.host.GetKubeClient())
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("StorageClass for provisioner %q must have secretNamespace and secretName either both set or both empty", r.plugin.GetPluginName())
}
} else {
r.secretValue = r.userKey
cfg, err := parseClassParameters(r.options.Parameters, r.plugin.host.GetKubeClient())
if err != nil {
return nil, err
}
r.provisioningConfig = *cfg

glog.V(4).Infof("glusterfs: creating volume with configuration %+v", r.provisioningConfig)
glusterfs, sizeGB, err := r.CreateVolume()
Expand All @@ -511,7 +461,6 @@ func (r *glusterfsVolumeProvisioner) Provision() (*api.PersistentVolume, error)
pv.Spec.Capacity = api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
}
r.paramToAnnotations(pv)
return pv, nil
}

Expand Down Expand Up @@ -564,37 +513,60 @@ func parseSecret(namespace, secretName string, kubeClient clientset.Interface) (
return secret, nil
}

// paramToAnnotations stores parameters needed to delete the volume in the PV
// annotations.
func (p *glusterfsVolumeProvisioner) paramToAnnotations(pv *api.PersistentVolume) {
ann := map[string]string{
annGlusterURL: p.url,
annGlusterUser: p.user,
annGlusterSecretName: p.secretName,
annGlusterSecretNamespace: p.secretNamespace,
annGlusterUserKey: p.userKey,
// parseClassParameters parses StorageClass.Parameters
func parseClassParameters(params map[string]string, kubeClient clientset.Interface) (*provisioningConfig, error) {
var cfg provisioningConfig
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a unit test for this bit of code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testcase added

var err error

authEnabled := true
for k, v := range params {
switch dstrings.ToLower(k) {
case "endpoint":
cfg.endpoint = v
case "resturl":
cfg.url = v
case "restuser":
cfg.user = v
case "restuserkey":
cfg.userKey = v
case "secretname":
cfg.secretName = v
case "secretnamespace":
cfg.secretNamespace = v
case "restauthenabled":
authEnabled = dstrings.ToLower(v) == "true"
default:
return nil, fmt.Errorf("glusterfs: invalid option %q for volume plugin %s", k, glusterfsPluginName)
}
}
volutil.AddVolumeAnnotations(pv, ann)
}

// annotationsToParam parses annotations stored by paramToAnnotations
func (d *glusterfsVolumeDeleter) annotationsToParam(pv *api.PersistentVolume) error {
annKeys := []string{
annGlusterSecretName,
annGlusterSecretNamespace,
annGlusterURL,
annGlusterUser,
annGlusterUserKey,
if len(cfg.url) == 0 {
return nil, fmt.Errorf("StorageClass for provisioner %s must contain 'resturl' parameter", glusterfsPluginName)
}
params, err := volutil.ParseVolumeAnnotations(pv, annKeys)
if err != nil {
return err
if len(cfg.endpoint) == 0 {
return nil, fmt.Errorf("StorageClass for provisioner %s must contain 'endpoint' parameter", glusterfsPluginName)
}

d.url = params[annGlusterURL]
d.user = params[annGlusterUser]
d.userKey = params[annGlusterUserKey]
d.secretName = params[annGlusterSecretName]
d.secretNamespace = params[annGlusterSecretNamespace]
return nil
if !authEnabled {
cfg.user = ""
cfg.secretName = ""
cfg.secretNamespace = ""
cfg.userKey = ""
cfg.secretValue = ""
}

if len(cfg.secretName) != 0 || len(cfg.secretNamespace) != 0 {
// secretName + Namespace has precedence over userKey
if len(cfg.secretName) != 0 && len(cfg.secretNamespace) != 0 {
cfg.secretValue, err = parseSecret(cfg.secretNamespace, cfg.secretName, kubeClient)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("StorageClass for provisioner %q must have secretNamespace and secretName either both set or both empty", glusterfsPluginName)
}
} else {
cfg.secretValue = cfg.userKey
}
return &cfg, nil
}
182 changes: 138 additions & 44 deletions pkg/volume/glusterfs/glusterfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@ package glusterfs
import (
"fmt"
"os"
"reflect"
"testing"

"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/mount"
Expand Down Expand Up @@ -237,62 +240,153 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
}
}

func TestAnnotations(t *testing.T) {
// Pass a provisioningConfigs through paramToAnnotations and back through
// annotationsToParam and check it did not change in the process.
tests := []provisioningConfig{
func TestParseClassParameters(t *testing.T) {
secret := api.Secret{
Data: map[string][]byte{
"data": []byte("mypassword"),
},
}

tests := []struct {
name string
parameters map[string]string
secret *api.Secret
expectError bool
expectConfig *provisioningConfig
}{
{
"password",
map[string]string{
"endpoint": "endpointname",
"resturl": "https://localhost:8080",
"restuser": "admin",
"restuserkey": "password",
},
nil, // secret
false, // expect error
&provisioningConfig{
endpoint: "endpointname",
url: "https://localhost:8080",
user: "admin",
userKey: "password",
secretValue: "password",
},
},
{
"secret",
map[string]string{
"endpoint": "endpointname",
"resturl": "https://localhost:8080",
"restuser": "admin",
"secretname": "mysecret",
"secretnamespace": "default",
},
&secret,
false, // expect error
&provisioningConfig{
endpoint: "endpointname",
url: "https://localhost:8080",
user: "admin",
secretName: "mysecret",
secretNamespace: "default",
secretValue: "mypassword",
},
},
{
// Everything empty
"no authentication",
map[string]string{
"endpoint": "endpointname",
"resturl": "https://localhost:8080",
"restauthenabled": "false",
},
&secret,
false, // expect error
&provisioningConfig{
endpoint: "endpointname",
url: "https://localhost:8080",
},
},
{
// Everything with a value
url: "http://localhost",
user: "admin",
secretNamespace: "default",
secretName: "gluster-secret",
userKey: "mykey",
"missing secret",
map[string]string{
"endpoint": "endpointname",
"resturl": "https://localhost:8080",
"secretname": "mysecret",
"secretnamespace": "default",
},
nil, // secret
true, // expect error
nil,
},
{
// No secret
url: "http://localhost",
user: "admin",
secretNamespace: "",
secretName: "",
userKey: "",
"secret with no namespace",
map[string]string{
"endpoint": "endpointname",
"resturl": "https://localhost:8080",
"secretname": "mysecret",
},
&secret,
true, // expect error
nil,
},
{
"missing url",
map[string]string{
"endpoint": "endpointname",
"restuser": "admin",
"restuserkey": "password",
},
nil, // secret
true, // expect error
nil,
},
{
"missing endpoint",
map[string]string{
"resturl": "https://localhost:8080",
"restuser": "admin",
"restuserkey": "password",
},
nil, // secret
true, // expect error
nil,
},
{
"unknown parameter",
map[string]string{
"unknown": "yes",
"resturl": "https://localhost:8080",
"restuser": "admin",
"restuserkey": "password",
},
nil, // secret
true, // expect error
nil,
},
}

for i, test := range tests {
provisioner := &glusterfsVolumeProvisioner{
provisioningConfig: test,
}
deleter := &glusterfsVolumeDeleter{}
for _, test := range tests {

pv := &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
Name: "pv",
},
}
client := &fake.Clientset{}
client.AddReactor("get", "secrets", func(action core.Action) (handled bool, ret runtime.Object, err error) {
if test.secret != nil {
return true, test.secret, nil
}
return true, nil, fmt.Errorf("Test %s did not set a secret", test.name)
})

provisioner.paramToAnnotations(pv)
err := deleter.annotationsToParam(pv)
if err != nil {
t.Errorf("test %d failed: %v", i, err)
}
if test.url != deleter.url {
t.Errorf("test %d failed: expected url %q, got %q", i, test.url, deleter.url)
}
if test.user != deleter.user {
t.Errorf("test %d failed: expected user %q, got %q", i, test.user, deleter.user)
}
if test.userKey != deleter.userKey {
t.Errorf("test %d failed: expected userKey %q, got %q", i, test.userKey, deleter.userKey)
cfg, err := parseClassParameters(test.parameters, client)

if err != nil && !test.expectError {
t.Errorf("Test %s got unexpected error %v", test.name, err)
}
if test.secretNamespace != deleter.secretNamespace {
t.Errorf("test %d failed: expected secretNamespace %q, got %q", i, test.secretNamespace, deleter.secretNamespace)
if err == nil && test.expectError {
t.Errorf("test %s expected error and got none", test.name)
}
if test.secretName != deleter.secretName {
t.Errorf("test %d failed: expected secretName %q, got %q", i, test.secretName, deleter.secretName)
if test.expectConfig != nil {
if !reflect.DeepEqual(cfg, test.expectConfig) {
t.Errorf("Test %s returned unexpected data, expected: %+v, got: %+v", test.name, test.expectConfig, cfg)
}
}
}
}