From 3fb3cc712205de883abb8247de931d8869b3030c Mon Sep 17 00:00:00 2001 From: Vladimir Vivien Date: Fri, 13 Oct 2017 22:57:37 -0400 Subject: [PATCH] ScaleIO - API source code update This commit tracks all human-generated code for API source updates. --- pkg/api/fuzzer/fuzzer.go | 14 +-- pkg/api/persistentvolume/util.go | 10 +- pkg/api/persistentvolume/util_test.go | 12 ++- pkg/api/types.go | 44 +++++++- pkg/api/v1/defaults.go | 11 +- pkg/api/validation/validation.go | 16 ++- pkg/printers/internalversion/describe.go | 22 +++- pkg/volume/scaleio/sio_mgr_test.go | 2 +- pkg/volume/scaleio/sio_plugin.go | 87 ++++++++++------ pkg/volume/scaleio/sio_util.go | 125 ++++++++++++++++++----- pkg/volume/scaleio/sio_util_test.go | 20 ++-- pkg/volume/scaleio/sio_volume.go | 79 +++++++------- pkg/volume/scaleio/sio_volume_test.go | 124 +++++++++++++++++----- staging/src/k8s.io/api/core/v1/types.go | 43 +++++++- 14 files changed, 455 insertions(+), 154 deletions(-) diff --git a/pkg/api/fuzzer/fuzzer.go b/pkg/api/fuzzer/fuzzer.go index 45575fc90e6..9163cee41e7 100644 --- a/pkg/api/fuzzer/fuzzer.go +++ b/pkg/api/fuzzer/fuzzer.go @@ -410,14 +410,16 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { } }, func(sio *api.ScaleIOVolumeSource, c fuzz.Continue) { - sio.ProtectionDomain = c.RandString() - if sio.ProtectionDomain == "" { - sio.ProtectionDomain = "default" + sio.StorageMode = c.RandString() + if sio.StorageMode == "" { + sio.StorageMode = "ThinProvisioned" } - sio.StoragePool = c.RandString() - if sio.StoragePool == "" { - sio.StoragePool = "default" + sio.FSType = c.RandString() + if sio.FSType == "" { + sio.FSType = "xfs" } + }, + func(sio *api.ScaleIOPersistentVolumeSource, c fuzz.Continue) { sio.StorageMode = c.RandString() if sio.StorageMode == "" { sio.StorageMode = "ThinProvisioned" diff --git a/pkg/api/persistentvolume/util.go b/pkg/api/persistentvolume/util.go index f05d8cf1b92..c1156156137 100644 --- a/pkg/api/persistentvolume/util.go +++ b/pkg/api/persistentvolume/util.go @@ -76,8 +76,14 @@ func VisitPVSecretNames(pv *api.PersistentVolume, visitor Visitor) bool { } } case source.ScaleIO != nil: - if source.ScaleIO.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.ScaleIO.SecretRef.Name) { - return false + if source.ScaleIO.SecretRef != nil { + ns := getClaimRefNamespace(pv) + if source.ScaleIO.SecretRef != nil && len(source.ScaleIO.SecretRef.Namespace) > 0 { + ns = source.ScaleIO.SecretRef.Namespace + } + if !visitor(ns, source.ScaleIO.SecretRef.Name) { + return false + } } case source.ISCSI != nil: if source.ISCSI.SecretRef != nil && !visitor(getClaimRefNamespace(pv), source.ISCSI.SecretRef.Name) { diff --git a/pkg/api/persistentvolume/util_test.go b/pkg/api/persistentvolume/util_test.go index b4e0a736212..d3554a96c51 100644 --- a/pkg/api/persistentvolume/util_test.go +++ b/pkg/api/persistentvolume/util_test.go @@ -78,9 +78,16 @@ func TestPVSecrets(t *testing.T) { {Spec: api.PersistentVolumeSpec{ ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, PersistentVolumeSource: api.PersistentVolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{ - SecretRef: &api.LocalObjectReference{ + ScaleIO: &api.ScaleIOPersistentVolumeSource{ + SecretRef: &api.SecretReference{ Name: "Spec.PersistentVolumeSource.ScaleIO.SecretRef"}}}}}, + {Spec: api.PersistentVolumeSpec{ + ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, + PersistentVolumeSource: api.PersistentVolumeSource{ + ScaleIO: &api.ScaleIOPersistentVolumeSource{ + SecretRef: &api.SecretReference{ + Name: "Spec.PersistentVolumeSource.ScaleIO.SecretRef", + Namespace: "scaleions"}}}}}, {Spec: api.PersistentVolumeSpec{ ClaimRef: &api.ObjectReference{Namespace: "claimrefns", Name: "claimrefname"}, PersistentVolumeSource: api.PersistentVolumeSource{ @@ -150,6 +157,7 @@ func TestPVSecrets(t *testing.T) { "claimrefns/Spec.PersistentVolumeSource.RBD.SecretRef", "rbdns/Spec.PersistentVolumeSource.RBD.SecretRef", "claimrefns/Spec.PersistentVolumeSource.ScaleIO.SecretRef", + "scaleions/Spec.PersistentVolumeSource.ScaleIO.SecretRef", "claimrefns/Spec.PersistentVolumeSource.ISCSI.SecretRef", "storageosns/Spec.PersistentVolumeSource.StorageOS.SecretRef", ) diff --git a/pkg/api/types.go b/pkg/api/types.go index 2fbcf5d37ba..f6d7aa89e80 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -383,7 +383,7 @@ type PersistentVolumeSource struct { PortworxVolume *PortworxVolumeSource // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // +optional - ScaleIO *ScaleIOVolumeSource + ScaleIO *ScaleIOPersistentVolumeSource // Local represents directly-attached storage with node affinity // +optional Local *LocalVolumeSource @@ -1285,13 +1285,13 @@ type ScaleIOVolumeSource struct { // Flag to enable/disable SSL communication with Gateway, default false // +optional SSLEnabled bool - // The name of the Protection Domain for the configured storage (defaults to "default"). + // The name of the ScaleIO Protection Domain for the configured storage. // +optional ProtectionDomain string - // The Storage Pool associated with the protection domain (defaults to "default"). + // The ScaleIO Storage Pool associated with the protection domain. // +optional StoragePool string - // Indicates whether the storage for a volume should be thick or thin (defaults to "thin"). + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. // +optional StorageMode string // The name of a volume already created in the ScaleIO system @@ -1308,6 +1308,42 @@ type ScaleIOVolumeSource struct { ReadOnly bool } +// ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume that can be defined +// by a an admin via a storage class, for instance. +type ScaleIOPersistentVolumeSource struct { + // The host address of the ScaleIO API Gateway. + Gateway string + // The name of the storage system as configured in ScaleIO. + System string + // SecretRef references to the secret for ScaleIO user and other + // sensitive information. If this is not provided, Login operation will fail. + SecretRef *SecretReference + // Flag to enable/disable SSL communication with Gateway, default false + // +optional + SSLEnabled bool + // The name of the ScaleIO Protection Domain for the configured storage. + // +optional + ProtectionDomain string + // The ScaleIO Storage Pool associated with the protection domain. + // +optional + StoragePool string + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + // +optional + StorageMode string + // The name of a volume created in the ScaleIO system + // that is associated with this volume source. + VolumeName string + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool +} + // Represents a StorageOS persistent volume resource. type StorageOSVolumeSource struct { // VolumeName is the human-readable name of the StorageOS volume. Volume diff --git a/pkg/api/v1/defaults.go b/pkg/api/v1/defaults.go index 8b57b76f1c2..02b1832d63d 100644 --- a/pkg/api/v1/defaults.go +++ b/pkg/api/v1/defaults.go @@ -381,12 +381,15 @@ func SetDefaults_RBDPersistentVolumeSource(obj *v1.RBDPersistentVolumeSource) { } func SetDefaults_ScaleIOVolumeSource(obj *v1.ScaleIOVolumeSource) { - if obj.ProtectionDomain == "" { - obj.ProtectionDomain = "default" + if obj.StorageMode == "" { + obj.StorageMode = "ThinProvisioned" } - if obj.StoragePool == "" { - obj.StoragePool = "default" + if obj.FSType == "" { + obj.FSType = "xfs" } +} + +func SetDefaults_ScaleIOPersistentVolumeSource(obj *v1.ScaleIOPersistentVolumeSource) { if obj.StorageMode == "" { obj.StorageMode = "ThinProvisioned" } diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 73645c8db19..f8cb15e6f06 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -1245,6 +1245,20 @@ func validateScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, fldPath *field.Pa return allErrs } +func validateScaleIOPersistentVolumeSource(sio *api.ScaleIOPersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if sio.Gateway == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("gateway"), "")) + } + if sio.System == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("system"), "")) + } + if sio.VolumeName == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeName"), "")) + } + return allErrs +} + func validateLocalVolumeSource(ls *api.LocalVolumeSource, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if ls.Path == "" { @@ -1489,7 +1503,7 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList { allErrs = append(allErrs, field.Forbidden(specPath.Child("scaleIO"), "may not specify more than 1 volume type")) } else { numVolumes++ - allErrs = append(allErrs, validateScaleIOVolumeSource(pv.Spec.ScaleIO, specPath.Child("scaleIO"))...) + allErrs = append(allErrs, validateScaleIOPersistentVolumeSource(pv.Spec.ScaleIO, specPath.Child("scaleIO"))...) } } if pv.Spec.Local != nil { diff --git a/pkg/printers/internalversion/describe.go b/pkg/printers/internalversion/describe.go index 18d54e9cb3b..c6f7e274866 100644 --- a/pkg/printers/internalversion/describe.go +++ b/pkg/printers/internalversion/describe.go @@ -970,6 +970,26 @@ func printScaleIOVolumeSource(sio *api.ScaleIOVolumeSource, w PrefixWriter) { sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, sio.FSType, sio.ReadOnly) } +func printScaleIOPersistentVolumeSource(sio *api.ScaleIOPersistentVolumeSource, w PrefixWriter) { + var secretNS, secretName string + if sio.SecretRef != nil { + secretName = sio.SecretRef.Name + secretNS = sio.SecretRef.Namespace + } + w.Write(LEVEL_2, "Type:\tScaleIO (a persistent volume backed by a block device in ScaleIO)\n"+ + " Gateway:\t%v\n"+ + " System:\t%v\n"+ + " Protection Domain:\t%v\n"+ + " Storage Pool:\t%v\n"+ + " Storage Mode:\t%v\n"+ + " VolumeName:\t%v\n"+ + " SecretName:\t%v\n"+ + " SecretNamespace:\t%v\n"+ + " FSType:\t%v\n"+ + " ReadOnly:\t%v\n", + sio.Gateway, sio.System, sio.ProtectionDomain, sio.StoragePool, sio.StorageMode, sio.VolumeName, secretName, secretNS, sio.FSType, sio.ReadOnly) +} + func printLocalVolumeSource(ls *api.LocalVolumeSource, w PrefixWriter) { w.Write(LEVEL_2, "Type:\tLocalVolume (a persistent volume backed by local storage on a node)\n"+ " Path:\t%v\n", @@ -1135,7 +1155,7 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) ( case pv.Spec.PortworxVolume != nil: printPortworxVolumeSource(pv.Spec.PortworxVolume, w) case pv.Spec.ScaleIO != nil: - printScaleIOVolumeSource(pv.Spec.ScaleIO, w) + printScaleIOPersistentVolumeSource(pv.Spec.ScaleIO, w) case pv.Spec.Local != nil: printLocalVolumeSource(pv.Spec.Local, w) case pv.Spec.CephFS != nil: diff --git a/pkg/volume/scaleio/sio_mgr_test.go b/pkg/volume/scaleio/sio_mgr_test.go index e2fe5c2002b..49ff5d05a81 100644 --- a/pkg/volume/scaleio/sio_mgr_test.go +++ b/pkg/volume/scaleio/sio_mgr_test.go @@ -37,7 +37,7 @@ var ( confKey.sslEnabled: "false", confKey.system: "scaleio", confKey.volumeName: "sio-0001", - confKey.secretRef: "sio-secret", + confKey.secretName: "sio-secret", confKey.username: "c2lvdXNlcgo=", // siouser confKey.password: "c2lvcGFzc3dvcmQK", // siopassword } diff --git a/pkg/volume/scaleio/sio_plugin.go b/pkg/volume/scaleio/sio_plugin.go index d7758756216..babd840c6b7 100644 --- a/pkg/volume/scaleio/sio_plugin.go +++ b/pkg/volume/scaleio/sio_plugin.go @@ -60,12 +60,11 @@ func (p *sioPlugin) GetPluginName() string { } func (p *sioPlugin) GetVolumeName(spec *volume.Spec) (string, error) { - source, err := getVolumeSourceFromSpec(spec) + attribs, err := getVolumeSourceAttribs(spec) if err != nil { return "", err } - - return source.VolumeName, nil + return attribs.volName, nil } func (p *sioPlugin) CanSupport(spec *volume.Spec) bool { @@ -81,29 +80,33 @@ func (p *sioPlugin) NewMounter( spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Mounter, error) { - sioSource, err := getVolumeSourceFromSpec(spec) + + // extract source info from either ScaleIOVolumeSource or ScaleIOPersistentVolumeSource type + attribs, err := getVolumeSourceAttribs(spec) if err != nil { - glog.Error(log("failed to extract ScaleIOVolumeSource from spec: %v", err)) - return nil, err + return nil, errors.New(log("mounter failed to extract volume attributes from spec: %v", err)) + } + + secretName, secretNS, err := getSecretAndNamespaceFromSpec(spec, pod) + if err != nil { + return nil, errors.New(log("failed to get secret name or secretNamespace: %v", err)) } return &sioVolume{ - pod: pod, - spec: spec, - source: sioSource, - namespace: pod.Namespace, - volSpecName: spec.Name(), - volName: sioSource.VolumeName, - podUID: pod.UID, - readOnly: sioSource.ReadOnly, - fsType: sioSource.FSType, - plugin: p, + pod: pod, + spec: spec, + secretName: secretName, + secretNamespace: secretNS, + volSpecName: spec.Name(), + volName: attribs.volName, + podUID: pod.UID, + readOnly: attribs.readOnly, + fsType: attribs.fsType, + plugin: p, }, nil } // NewUnmounter creates a representation of the volume to unmount -// The specName param can be used to carry the namespace value (if needed) using format: -// specName = [nsSep] where the specname is pre-pended with the namespace func (p *sioPlugin) NewUnmounter(specName string, podUID types.UID) (volume.Unmounter, error) { glog.V(4).Info(log("Unmounter for %s", specName)) @@ -156,22 +159,25 @@ func (p *sioPlugin) GetAccessModes() []api.PersistentVolumeAccessMode { var _ volume.DeletableVolumePlugin = &sioPlugin{} func (p *sioPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { - sioSource, err := getVolumeSourceFromSpec(spec) + attribs, err := getVolumeSourceAttribs(spec) if err != nil { - glog.Error(log("deleter failed to extract source from spec: %v", err)) + glog.Error(log("deleter failed to extract volume attributes from spec: %v", err)) return nil, err } - namespace := spec.PersistentVolume.Spec.ClaimRef.Namespace + secretName, secretNS, err := getSecretAndNamespaceFromSpec(spec, nil) + if err != nil { + return nil, errors.New(log("failed to get secret name or secretNamespace: %v", err)) + } return &sioVolume{ - spec: spec, - source: sioSource, - namespace: namespace, - volSpecName: spec.Name(), - volName: sioSource.VolumeName, - plugin: p, - readOnly: sioSource.ReadOnly, + spec: spec, + secretName: secretName, + secretNamespace: secretNS, + volSpecName: spec.Name(), + volName: attribs.volName, + plugin: p, + readOnly: attribs.readOnly, }, nil } @@ -189,13 +195,26 @@ func (p *sioPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisi return nil, errors.New("option parameters missing") } - namespace := options.PVC.Namespace + // Supports ref of name of secret a couple of ways: + // options.Parameters["secretRef"] for backward compat, or + // options.Parameters["secretName"] + secretName := configData[confKey.secretName] + if secretName == "" { + secretName = configData["secretName"] + configData[confKey.secretName] = secretName + } + + secretNS := configData[confKey.secretNamespace] + if secretNS == "" { + secretNS = options.PVC.Namespace + } return &sioVolume{ - configData: configData, - plugin: p, - options: options, - namespace: namespace, - volSpecName: options.PVName, + configData: configData, + plugin: p, + options: options, + secretName: secretName, + secretNamespace: secretNS, + volSpecName: options.PVName, }, nil } diff --git a/pkg/volume/scaleio/sio_util.go b/pkg/volume/scaleio/sio_util.go index 3c2f13a3405..de0c5811665 100644 --- a/pkg/volume/scaleio/sio_util.go +++ b/pkg/volume/scaleio/sio_util.go @@ -31,11 +31,17 @@ import ( volutil "k8s.io/kubernetes/pkg/volume/util" ) +type volSourceAttribs struct { + volName, + fsType string + readOnly bool +} + var ( confKey = struct { gateway, sslEnabled, - secretRef, + secretName, system, protectionDomain, storagePool, @@ -47,12 +53,13 @@ var ( readOnly, username, password, - namespace, + secretNamespace, sdcGuid string }{ gateway: "gateway", sslEnabled: "sslEnabled", - secretRef: "secretRef", + secretName: "secretRef", + secretNamespace: "secretNamespace", system: "system", protectionDomain: "protectionDomain", storagePool: "storagePool", @@ -64,7 +71,6 @@ var ( readOnly: "readOnly", username: "username", password: "password", - namespace: "namespace", sdcGuid: "sdcGuid", } sdcGuidLabelName = "scaleio.sdcGuid" @@ -79,23 +85,32 @@ var ( protectionDomainNotProvidedErr = errors.New("ScaleIO protection domain not provided") ) -// mapScaleIOVolumeSource maps attributes from a ScaleIOVolumeSource to config -func mapVolumeSource(config map[string]string, source *api.ScaleIOVolumeSource) { - config[confKey.gateway] = source.Gateway - config[confKey.secretRef] = func() string { - if source.SecretRef != nil { - return string(source.SecretRef.Name) - } - return "" - }() - config[confKey.system] = source.System - config[confKey.volumeName] = source.VolumeName - config[confKey.sslEnabled] = strconv.FormatBool(source.SSLEnabled) - config[confKey.protectionDomain] = source.ProtectionDomain - config[confKey.storagePool] = source.StoragePool - config[confKey.storageMode] = source.StorageMode - config[confKey.fsType] = source.FSType - config[confKey.readOnly] = strconv.FormatBool(source.ReadOnly) +// mapVolumeSpec maps attributes from either ScaleIOVolumeSource or ScaleIOPersistentVolumeSource to config +func mapVolumeSpec(config map[string]string, spec *volume.Spec) { + + if source, err := getScaleIOPersistentVolumeSourceFromSpec(spec); err == nil { + config[confKey.gateway] = source.Gateway + config[confKey.system] = source.System + config[confKey.volumeName] = source.VolumeName + config[confKey.sslEnabled] = strconv.FormatBool(source.SSLEnabled) + config[confKey.protectionDomain] = source.ProtectionDomain + config[confKey.storagePool] = source.StoragePool + config[confKey.storageMode] = source.StorageMode + config[confKey.fsType] = source.FSType + config[confKey.readOnly] = strconv.FormatBool(source.ReadOnly) + } + + if source, err := getScaleIOVolumeSourceFromSpec(spec); err == nil { + config[confKey.gateway] = source.Gateway + config[confKey.system] = source.System + config[confKey.volumeName] = source.VolumeName + config[confKey.sslEnabled] = strconv.FormatBool(source.SSLEnabled) + config[confKey.protectionDomain] = source.ProtectionDomain + config[confKey.storagePool] = source.StoragePool + config[confKey.storageMode] = source.StorageMode + config[confKey.fsType] = source.FSType + config[confKey.readOnly] = strconv.FormatBool(source.ReadOnly) + } //optionals applyConfigDefaults(config) @@ -105,7 +120,7 @@ func validateConfigs(config map[string]string) error { if config[confKey.gateway] == "" { return gatewayNotProvidedErr } - if config[confKey.secretRef] == "" { + if config[confKey.secretName] == "" { return secretRefNotProvidedErr } if config[confKey.system] == "" { @@ -202,7 +217,7 @@ func saveConfig(configName string, data map[string]string) error { // attachSecret loads secret object and attaches to configData func attachSecret(plug *sioPlugin, namespace string, configData map[string]string) error { // load secret - secretRefName := configData[confKey.secretRef] + secretRefName := configData[confKey.secretName] kubeClient := plug.host.GetKubeClient() secretMap, err := volutil.GetSecretForPV(namespace, secretRefName, sioPluginName, kubeClient) if err != nil { @@ -244,8 +259,8 @@ func getSdcGuidLabel(plug *sioPlugin) (string, error) { return label, nil } -// getVolumeSourceFromSpec safely extracts ScaleIOVolumeSource from spec -func getVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOVolumeSource, error) { +// getVolumeSourceFromSpec safely extracts ScaleIOVolumeSource or ScaleIOPersistentVolumeSource from spec +func getVolumeSourceFromSpec(spec *volume.Spec) (interface{}, error) { if spec.Volume != nil && spec.Volume.ScaleIO != nil { return spec.Volume.ScaleIO, nil } @@ -257,6 +272,66 @@ func getVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOVolumeSource, error return nil, fmt.Errorf("ScaleIO not defined in spec") } +func getVolumeSourceAttribs(spec *volume.Spec) (*volSourceAttribs, error) { + attribs := new(volSourceAttribs) + if pvSource, err := getScaleIOPersistentVolumeSourceFromSpec(spec); err == nil { + attribs.volName = pvSource.VolumeName + attribs.fsType = pvSource.FSType + attribs.readOnly = pvSource.ReadOnly + } else if pSource, err := getScaleIOVolumeSourceFromSpec(spec); err == nil { + attribs.volName = pSource.VolumeName + attribs.fsType = pSource.FSType + attribs.readOnly = pSource.ReadOnly + } else { + msg := log("failed to get ScaleIOVolumeSource or ScaleIOPersistentVolumeSource from spec") + glog.Error(msg) + return nil, errors.New(msg) + } + return attribs, nil +} + +func getScaleIOPersistentVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOPersistentVolumeSource, error) { + source, err := getVolumeSourceFromSpec(spec) + if err != nil { + return nil, err + } + if val, ok := source.(*api.ScaleIOPersistentVolumeSource); ok { + return val, nil + } + return nil, fmt.Errorf("spec is not a valid ScaleIOPersistentVolume type") +} + +func getScaleIOVolumeSourceFromSpec(spec *volume.Spec) (*api.ScaleIOVolumeSource, error) { + source, err := getVolumeSourceFromSpec(spec) + if err != nil { + return nil, err + } + if val, ok := source.(*api.ScaleIOVolumeSource); ok { + return val, nil + } + return nil, fmt.Errorf("spec is not a valid ScaleIOVolume type") +} + +func getSecretAndNamespaceFromSpec(spec *volume.Spec, pod *api.Pod) (secretName string, secretNS string, err error) { + if source, err := getScaleIOVolumeSourceFromSpec(spec); err == nil { + secretName = source.SecretRef.Name + if pod != nil { + secretNS = pod.Namespace + } + } else if source, err := getScaleIOPersistentVolumeSourceFromSpec(spec); err == nil { + if source.SecretRef != nil { + secretName = source.SecretRef.Name + secretNS = source.SecretRef.Namespace + if secretNS == "" && pod != nil { + secretNS = pod.Namespace + } + } + } else { + return "", "", errors.New("failed to get ScaleIOVolumeSource or ScaleIOPersistentVolumeSource") + } + return secretName, secretNS, nil +} + func log(msg string, parts ...interface{}) string { return fmt.Sprintf(fmt.Sprintf("scaleio: %s", msg), parts...) } diff --git a/pkg/volume/scaleio/sio_util_test.go b/pkg/volume/scaleio/sio_util_test.go index 360b67e3fac..4be4bd75fe9 100644 --- a/pkg/volume/scaleio/sio_util_test.go +++ b/pkg/volume/scaleio/sio_util_test.go @@ -25,6 +25,7 @@ import ( api "k8s.io/api/core/v1" utiltesting "k8s.io/client-go/util/testing" + "k8s.io/kubernetes/pkg/volume" ) var ( @@ -48,7 +49,7 @@ var ( confKey.gateway: "http://sio/", confKey.volSpecName: testSioVolName, confKey.volumeName: "sio-vol", - confKey.secretRef: "sio-secret", + confKey.secretName: "sio-secret", confKey.protectionDomain: "defaultPD", confKey.storagePool: "deraultSP", confKey.fsType: "xfs", @@ -60,7 +61,7 @@ var ( func TestUtilMapVolumeSource(t *testing.T) { data := make(map[string]string) - mapVolumeSource(data, vol.VolumeSource.ScaleIO) + mapVolumeSpec(data, volume.NewSpecFromVolume(vol)) if data[confKey.gateway] != "http://test.scaleio:1111" { t.Error("Unexpected gateway value") } @@ -79,9 +80,6 @@ func TestUtilMapVolumeSource(t *testing.T) { if data[confKey.fsType] != "ext4" { t.Error("Unexpected fstype value") } - if data[confKey.secretRef] != "test-secret" { - t.Error("Unexpected secret ref value") - } if data[confKey.sslEnabled] != "false" { t.Error("Unexpected sslEnabled value") } @@ -92,8 +90,8 @@ func TestUtilMapVolumeSource(t *testing.T) { func TestUtilValidateConfigs(t *testing.T) { data := map[string]string{ - confKey.secretRef: "sio-secret", - confKey.system: "sio", + confKey.secretName: "sio-secret", + confKey.system: "sio", } if err := validateConfigs(data); err != gatewayNotProvidedErr { t.Error("Expecting error for missing gateway, but did not get it") @@ -105,7 +103,7 @@ func TestUtilApplyConfigDefaults(t *testing.T) { confKey.system: "sio", confKey.gateway: "http://sio/", confKey.volumeName: "sio-vol", - confKey.secretRef: "test-secret", + confKey.secretName: "test-secret", } applyConfigDefaults(data) @@ -130,7 +128,7 @@ func TestUtilApplyConfigDefaults(t *testing.T) { if data[confKey.storageMode] != "ThinProvisioned" { t.Error("Unexpected storage mode value") } - if data[confKey.secretRef] != "test-secret" { + if data[confKey.secretName] != "test-secret" { t.Error("Unexpected secret ref value") } if data[confKey.sslEnabled] != "false" { @@ -157,7 +155,7 @@ func TestUtilSaveConfig(t *testing.T) { config := path.Join(tmpDir, testConfigFile) data := map[string]string{ confKey.gateway: "https://test-gateway/", - confKey.secretRef: "sio-secret", + confKey.secretName: "sio-secret", confKey.sslEnabled: "false", } if err := saveConfig(config, data); err != nil { @@ -178,7 +176,7 @@ func TestUtilSaveConfig(t *testing.T) { } func TestUtilAttachSecret(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) diff --git a/pkg/volume/scaleio/sio_volume.go b/pkg/volume/scaleio/sio_volume.go index 85e7fb0b43f..0d0d35608bd 100644 --- a/pkg/volume/scaleio/sio_volume.go +++ b/pkg/volume/scaleio/sio_volume.go @@ -37,19 +37,19 @@ import ( ) type sioVolume struct { - sioMgr *sioMgr - plugin *sioPlugin - pod *api.Pod - podUID types.UID - spec *volume.Spec - source *api.ScaleIOVolumeSource - namespace string - volSpecName string - volName string - readOnly bool - fsType string - options volume.VolumeOptions - configData map[string]string + sioMgr *sioMgr + plugin *sioPlugin + pod *api.Pod + podUID types.UID + spec *volume.Spec + secretName string + secretNamespace string + volSpecName string + volName string + readOnly bool + fsType string + options volume.VolumeOptions + configData map[string]string volume.MetricsNil } @@ -59,7 +59,6 @@ type sioVolume struct { var _ volume.Volume = &sioVolume{} // GetPath returns the path where the volume will be mounted. -// The volumeName is prefixed with the pod's namespace a - func (v *sioVolume) GetPath() string { return v.plugin.host.GetPodVolumeDir( v.podUID, @@ -128,11 +127,11 @@ func (v *sioVolume) SetUpAt(dir string, fsGroup *int64) error { switch { default: options = append(options, "rw") - case isROM && !v.source.ReadOnly: + case isROM && !v.readOnly: options = append(options, "rw") case isROM: options = append(options, "ro") - case v.source.ReadOnly: + case v.readOnly: options = append(options, "ro") } @@ -327,10 +326,10 @@ func (v *sioVolume) Provision() (*api.PersistentVolume, error) { ), }, PersistentVolumeSource: api.PersistentVolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{ + ScaleIO: &api.ScaleIOPersistentVolumeSource{ Gateway: v.configData[confKey.gateway], SSLEnabled: sslEnabled, - SecretRef: &api.LocalObjectReference{Name: v.configData[confKey.secretRef]}, + SecretRef: &api.SecretReference{Name: v.secretName, Namespace: v.secretNamespace}, System: v.configData[confKey.system], ProtectionDomain: v.configData[confKey.protectionDomain], StoragePool: v.configData[confKey.storagePool], @@ -366,13 +365,17 @@ func (v *sioVolume) setSioMgr() error { glog.V(4).Info(log("previous config file not found, creating new one")) // prepare config data configData = make(map[string]string) - mapVolumeSource(configData, v.source) + mapVolumeSpec(configData, v.spec) + + // additional config data + configData[confKey.secretNamespace] = v.secretNamespace + configData[confKey.secretName] = v.secretName + configData[confKey.volSpecName] = v.volSpecName + if err := validateConfigs(configData); err != nil { glog.Error(log("config setup failed: %s", err)) return err } - configData[confKey.namespace] = v.namespace - configData[confKey.volSpecName] = v.volSpecName // persist config if err := saveConfig(configName, configData); err != nil { @@ -381,7 +384,7 @@ func (v *sioVolume) setSioMgr() error { } } // merge in secret - if err := attachSecret(v.plugin, v.namespace, configData); err != nil { + if err := attachSecret(v.plugin, v.secretNamespace, configData); err != nil { glog.Error(log("failed to load secret: %v", err)) return err } @@ -414,12 +417,13 @@ func (v *sioVolume) resetSioMgr() error { glog.Error(log("failed to load config data: %v", err)) return err } - v.namespace = configData[confKey.namespace] + v.secretName = configData[confKey.secretName] + v.secretNamespace = configData[confKey.secretNamespace] v.volName = configData[confKey.volumeName] v.volSpecName = configData[confKey.volSpecName] // attach secret - if err := attachSecret(v.plugin, v.namespace, configData); err != nil { + if err := attachSecret(v.plugin, v.secretNamespace, configData); err != nil { glog.Error(log("failed to load secret: %v", err)) return err } @@ -446,21 +450,22 @@ func (v *sioVolume) resetSioMgr() error { func (v *sioVolume) setSioMgrFromConfig() error { glog.V(4).Info(log("setting scaleio mgr from available config")) if v.sioMgr == nil { - configData := v.configData - applyConfigDefaults(configData) - if err := validateConfigs(configData); err != nil { + applyConfigDefaults(v.configData) + + v.configData[confKey.volSpecName] = v.volSpecName + + if err := validateConfigs(v.configData); err != nil { glog.Error(log("config data setup failed: %s", err)) return err } - configData[confKey.namespace] = v.namespace - configData[confKey.volSpecName] = v.volSpecName // copy config and attach secret data := map[string]string{} - for k, v := range configData { + for k, v := range v.configData { data[k] = v } - if err := attachSecret(v.plugin, v.namespace, data); err != nil { + + if err := attachSecret(v.plugin, v.secretNamespace, data); err != nil { glog.Error(log("failed to load secret: %v", err)) return err } @@ -483,16 +488,20 @@ func (v *sioVolume) setSioMgrFromSpec() error { if v.sioMgr == nil { // get config data form spec volume source configData := map[string]string{} - mapVolumeSource(configData, v.source) + mapVolumeSpec(configData, v.spec) + + // additional config + configData[confKey.secretNamespace] = v.secretNamespace + configData[confKey.secretName] = v.secretName + configData[confKey.volSpecName] = v.volSpecName + if err := validateConfigs(configData); err != nil { glog.Error(log("config setup failed: %s", err)) return err } - configData[confKey.namespace] = v.namespace - configData[confKey.volSpecName] = v.volSpecName // attach secret object to config data - if err := attachSecret(v.plugin, v.namespace, configData); err != nil { + if err := attachSecret(v.plugin, v.secretNamespace, configData); err != nil { glog.Error(log("failed to load secret: %v", err)) return err } diff --git a/pkg/volume/scaleio/sio_volume_test.go b/pkg/volume/scaleio/sio_volume_test.go index ad2f75475b8..8912ae870e0 100644 --- a/pkg/volume/scaleio/sio_volume_test.go +++ b/pkg/volume/scaleio/sio_volume_test.go @@ -27,6 +27,7 @@ import ( api "k8s.io/api/core/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" fakeclient "k8s.io/client-go/kubernetes/fake" utiltesting "k8s.io/client-go/util/testing" @@ -39,29 +40,18 @@ var ( testSioPD = "default" testSioVol = "vol-0001" testns = "default" + testSecret = "sio-secret" testSioVolName = fmt.Sprintf("%s%s%s", testns, "-", testSioVol) podUID = types.UID("sio-pod") ) -func newPluginMgr(t *testing.T) (*volume.VolumePluginMgr, string) { +func newPluginMgr(t *testing.T, apiObject runtime.Object) (*volume.VolumePluginMgr, string) { tmpDir, err := utiltesting.MkTmpdir("scaleio-test") if err != nil { t.Fatalf("can't make a temp dir: %v", err) } - config := &api.Secret{ - ObjectMeta: meta.ObjectMeta{ - Name: "sio-secret", - Namespace: testns, - UID: "1234567890", - }, - Type: api.SecretType("kubernetes.io/scaleio"), - Data: map[string][]byte{ - "username": []byte("username"), - "password": []byte("password"), - }, - } - fakeClient := fakeclient.NewSimpleClientset(config) + fakeClient := fakeclient.NewSimpleClientset(apiObject) host := volumetest.NewFakeVolumeHostWithNodeLabels( tmpDir, fakeClient, @@ -74,8 +64,23 @@ func newPluginMgr(t *testing.T) (*volume.VolumePluginMgr, string) { return plugMgr, tmpDir } +func makeScaleIOSecret(name, namespace string) *api.Secret { + return &api.Secret{ + ObjectMeta: meta.ObjectMeta{ + Name: name, + Namespace: namespace, + UID: "1234567890", + }, + Type: api.SecretType("kubernetes.io/scaleio"), + Data: map[string][]byte{ + "username": []byte("username"), + "password": []byte("password"), + }, + } +} + func TestVolumeCanSupport(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) if err != nil { @@ -100,7 +105,7 @@ func TestVolumeCanSupport(t *testing.T) { PersistentVolume: &api.PersistentVolume{ Spec: api.PersistentVolumeSpec{ PersistentVolumeSource: api.PersistentVolumeSource{ - ScaleIO: &api.ScaleIOVolumeSource{}, + ScaleIO: &api.ScaleIOPersistentVolumeSource{}, }, }, }, @@ -111,7 +116,7 @@ func TestVolumeCanSupport(t *testing.T) { } func TestVolumeGetAccessModes(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPersistentPluginByName(sioPluginName) if err != nil { @@ -131,7 +136,7 @@ func containsMode(modes []api.PersistentVolumeAccessMode, mode api.PersistentVol } func TestVolumeMounterUnmounter(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) @@ -153,7 +158,7 @@ func TestVolumeMounterUnmounter(t *testing.T) { StoragePool: "default", VolumeName: testSioVol, FSType: "ext4", - SecretRef: &api.LocalObjectReference{Name: "sio-secret"}, + SecretRef: &api.LocalObjectReference{Name: testSecret}, ReadOnly: false, }, }, @@ -245,7 +250,7 @@ func TestVolumeMounterUnmounter(t *testing.T) { } func TestVolumeProvisioner(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) @@ -274,7 +279,7 @@ func TestVolumeProvisioner(t *testing.T) { confKey.system: "sio", confKey.protectionDomain: testSioPD, confKey.storagePool: "default", - confKey.secretRef: "sio-secret", + confKey.secretName: testSecret, } provisioner, err := sioPlug.NewProvisioner(options) @@ -296,6 +301,17 @@ func TestVolumeProvisioner(t *testing.T) { t.Fatalf("call to Provision() failed: %v", err) } + if spec.Namespace != testns { + t.Fatalf("unexpected namespace %v", spec.Namespace) + } + if spec.Spec.ScaleIO.SecretRef == nil { + t.Fatalf("unexpected nil value for spec.SecretRef") + } + if spec.Spec.ScaleIO.SecretRef.Name != testSecret || + spec.Spec.ScaleIO.SecretRef.Namespace != testns { + t.Fatalf("spec.SecretRef is not being set properly") + } + spec.Spec.ClaimRef = &api.ObjectReference{Namespace: testns} // validate provision @@ -379,7 +395,7 @@ func TestVolumeProvisioner(t *testing.T) { } func TestVolumeProvisionerWithIncompleteConfig(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) @@ -411,7 +427,7 @@ func TestVolumeProvisionerWithIncompleteConfig(t *testing.T) { } func TestVolumeProvisionerWithZeroCapacity(t *testing.T) { - plugMgr, tmpDir := newPluginMgr(t) + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret(testSecret, testns)) defer os.RemoveAll(tmpDir) plug, err := plugMgr.FindPluginByName(sioPluginName) @@ -440,7 +456,7 @@ func TestVolumeProvisionerWithZeroCapacity(t *testing.T) { confKey.system: "sio", confKey.protectionDomain: testSioPD, confKey.storagePool: "default", - confKey.secretRef: "sio-secret", + confKey.secretName: "sio-secret", } provisioner, _ := sioPlug.NewProvisioner(options) @@ -457,3 +473,63 @@ func TestVolumeProvisionerWithZeroCapacity(t *testing.T) { } } + +func TestVolumeProvisionerWithSecretNamespace(t *testing.T) { + plugMgr, tmpDir := newPluginMgr(t, makeScaleIOSecret("sio-sec", "sio-ns")) + defer os.RemoveAll(tmpDir) + + plug, err := plugMgr.FindPluginByName(sioPluginName) + if err != nil { + t.Fatalf("Can't find the plugin %v", sioPluginName) + } + sioPlug, ok := plug.(*sioPlugin) + if !ok { + t.Fatal("Cannot assert plugin to be type sioPlugin") + } + + options := volume.VolumeOptions{ + ClusterName: "testcluster", + PVName: "pvc-sio-dynamic-vol", + PVC: volumetest.CreateTestPVC("100Mi", []api.PersistentVolumeAccessMode{api.ReadWriteOnce}), + PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete, + } + + options.PVC.Spec.AccessModes = []api.PersistentVolumeAccessMode{ + api.ReadWriteOnce, + } + + options.PVC.Namespace = "pvc-ns" + options.Parameters = map[string]string{ + confKey.gateway: "http://test.scaleio:11111", + confKey.system: "sio", + confKey.protectionDomain: testSioPD, + confKey.storagePool: "default", + confKey.secretName: "sio-sec", + confKey.secretNamespace: "sio-ns", + } + + provisioner, _ := sioPlug.NewProvisioner(options) + sio := newFakeSio() + sioVol := provisioner.(*sioVolume) + if err := sioVol.setSioMgrFromConfig(); err != nil { + t.Fatalf("failed to create scaleio mgr from config: %v", err) + } + sioVol.sioMgr.client = sio + + spec, err := sioVol.Provision() + if err != nil { + t.Fatalf("call to Provision() failed: %v", err) + } + + if spec.GetObjectMeta().GetNamespace() != "pvc-ns" { + t.Fatalf("unexpected spec.namespace %s", spec.GetObjectMeta().GetNamespace()) + } + + if spec.Spec.ScaleIO.SecretRef.Name != "sio-sec" { + t.Fatalf("unexpected spec.ScaleIOPersistentVolume.SecretRef.Name %v", spec.Spec.ScaleIO.SecretRef.Name) + } + + if spec.Spec.ScaleIO.SecretRef.Namespace != "sio-ns" { + t.Fatalf("unexpected spec.ScaleIOPersistentVolume.SecretRef.Namespace %v", spec.Spec.ScaleIO.SecretRef.Namespace) + } +} diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index 02088d9c4cf..8e3e433de83 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -440,7 +440,7 @@ type PersistentVolumeSource struct { PortworxVolume *PortworxVolumeSource `json:"portworxVolume,omitempty" protobuf:"bytes,18,opt,name=portworxVolume"` // ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. // +optional - ScaleIO *ScaleIOVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,19,opt,name=scaleIO"` + ScaleIO *ScaleIOPersistentVolumeSource `json:"scaleIO,omitempty" protobuf:"bytes,19,opt,name=scaleIO"` // Local represents directly-attached storage with node affinity // +optional Local *LocalVolumeSource `json:"local,omitempty" protobuf:"bytes,20,opt,name=local"` @@ -1396,13 +1396,48 @@ type ScaleIOVolumeSource struct { // Flag to enable/disable SSL communication with Gateway, default false // +optional SSLEnabled bool `json:"sslEnabled,omitempty" protobuf:"varint,4,opt,name=sslEnabled"` - // The name of the Protection Domain for the configured storage (defaults to "default"). + // The name of the ScaleIO Protection Domain for the configured storage. // +optional ProtectionDomain string `json:"protectionDomain,omitempty" protobuf:"bytes,5,opt,name=protectionDomain"` - // The Storage Pool associated with the protection domain (defaults to "default"). + // The ScaleIO Storage Pool associated with the protection domain. // +optional StoragePool string `json:"storagePool,omitempty" protobuf:"bytes,6,opt,name=storagePool"` - // Indicates whether the storage for a volume should be thick or thin (defaults to "thin"). + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + // +optional + StorageMode string `json:"storageMode,omitempty" protobuf:"bytes,7,opt,name=storageMode"` + // The name of a volume already created in the ScaleIO system + // that is associated with this volume source. + VolumeName string `json:"volumeName,omitempty" protobuf:"bytes,8,opt,name=volumeName"` + // Filesystem type to mount. + // Must be a filesystem type supported by the host operating system. + // Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + // +optional + FSType string `json:"fsType,omitempty" protobuf:"bytes,9,opt,name=fsType"` + // Defaults to false (read/write). ReadOnly here will force + // the ReadOnly setting in VolumeMounts. + // +optional + ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,10,opt,name=readOnly"` +} + +// ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume +type ScaleIOPersistentVolumeSource struct { + // The host address of the ScaleIO API Gateway. + Gateway string `json:"gateway" protobuf:"bytes,1,opt,name=gateway"` + // The name of the storage system as configured in ScaleIO. + System string `json:"system" protobuf:"bytes,2,opt,name=system"` + // SecretRef references to the secret for ScaleIO user and other + // sensitive information. If this is not provided, Login operation will fail. + SecretRef *SecretReference `json:"secretRef" protobuf:"bytes,3,opt,name=secretRef"` + // Flag to enable/disable SSL communication with Gateway, default false + // +optional + SSLEnabled bool `json:"sslEnabled,omitempty" protobuf:"varint,4,opt,name=sslEnabled"` + // The name of the ScaleIO Protection Domain for the configured storage. + // +optional + ProtectionDomain string `json:"protectionDomain,omitempty" protobuf:"bytes,5,opt,name=protectionDomain"` + // The ScaleIO Storage Pool associated with the protection domain. + // +optional + StoragePool string `json:"storagePool,omitempty" protobuf:"bytes,6,opt,name=storagePool"` + // Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. // +optional StorageMode string `json:"storageMode,omitempty" protobuf:"bytes,7,opt,name=storageMode"` // The name of a volume already created in the ScaleIO system