mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 10:53:34 +00:00
vendor files
This commit is contained in:
66
vendor/k8s.io/kubernetes/pkg/volume/storageos/BUILD
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/pkg/volume/storageos/BUILD
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"storageos.go",
|
||||
"storageos_util.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/volume/storageos",
|
||||
deps = [
|
||||
"//pkg/util/mount:go_default_library",
|
||||
"//pkg/util/strings:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
"//pkg/volume/util/volumehelper:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/storageos/go-api:go_default_library",
|
||||
"//vendor/github.com/storageos/go-api/types:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"storageos_test.go",
|
||||
"storageos_util_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/volume/storageos",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/util/mount:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/testing:go_default_library",
|
||||
"//vendor/github.com/storageos/go-api/types:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
4
vendor/k8s.io/kubernetes/pkg/volume/storageos/OWNERS
generated
vendored
Normal file
4
vendor/k8s.io/kubernetes/pkg/volume/storageos/OWNERS
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
maintainers:
|
||||
- croomes
|
||||
- rusenask
|
||||
- chira001
|
19
vendor/k8s.io/kubernetes/pkg/volume/storageos/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/volume/storageos/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2017 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 storageos contains the internal representation of StorageOS
|
||||
// PersistentDisk volumes.
|
||||
package storageos // import "k8s.io/kubernetes/pkg/volume/storageos"
|
746
vendor/k8s.io/kubernetes/pkg/volume/storageos/storageos.go
generated
vendored
Normal file
746
vendor/k8s.io/kubernetes/pkg/volume/storageos/storageos.go
generated
vendored
Normal file
@ -0,0 +1,746 @@
|
||||
/*
|
||||
Copyright 2017 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 storageos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
kstrings "k8s.io/kubernetes/pkg/util/strings"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
)
|
||||
|
||||
// ProbeVolumePlugins is the primary entrypoint for volume plugins.
|
||||
func ProbeVolumePlugins() []volume.VolumePlugin {
|
||||
return []volume.VolumePlugin{&storageosPlugin{nil}}
|
||||
}
|
||||
|
||||
type storageosPlugin struct {
|
||||
host volume.VolumeHost
|
||||
}
|
||||
|
||||
var _ volume.VolumePlugin = &storageosPlugin{}
|
||||
var _ volume.PersistentVolumePlugin = &storageosPlugin{}
|
||||
var _ volume.DeletableVolumePlugin = &storageosPlugin{}
|
||||
var _ volume.ProvisionableVolumePlugin = &storageosPlugin{}
|
||||
|
||||
const (
|
||||
storageosPluginName = "kubernetes.io/storageos"
|
||||
storageosDevicePath = "/var/lib/storageos/volumes"
|
||||
defaultAPIAddress = "tcp://localhost:5705"
|
||||
defaultAPIUser = "storageos"
|
||||
defaultAPIPassword = "storageos"
|
||||
defaultAPIVersion = "1"
|
||||
defaultFSType = "ext4"
|
||||
defaultNamespace = "default"
|
||||
)
|
||||
|
||||
func getPath(uid types.UID, volNamespace string, volName string, pvName string, host volume.VolumeHost) string {
|
||||
if len(volNamespace) != 0 && len(volName) != 0 && strings.Count(volName, ".") == 0 {
|
||||
return host.GetPodVolumeDir(uid, kstrings.EscapeQualifiedNameForDisk(storageosPluginName), pvName+"."+volNamespace+"."+volName)
|
||||
}
|
||||
return host.GetPodVolumeDir(uid, kstrings.EscapeQualifiedNameForDisk(storageosPluginName), pvName)
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) Init(host volume.VolumeHost) error {
|
||||
plugin.host = host
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) GetPluginName() string {
|
||||
return storageosPluginName
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", volumeSource.VolumeNamespace, volumeSource.VolumeName), nil
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) CanSupport(spec *volume.Spec) bool {
|
||||
return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.StorageOS != nil) ||
|
||||
(spec.Volume != nil && spec.Volume.StorageOS != nil)
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) RequiresRemount() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
|
||||
return []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
}
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
|
||||
|
||||
apiCfg, err := getAPICfg(spec, pod, plugin.host.GetKubeClient())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plugin.newMounterInternal(spec, pod, apiCfg, &storageosUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName()))
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, apiCfg *storageosAPIConfig, manager storageosManager, mounter mount.Interface, exec mount.Exec) (volume.Mounter, error) {
|
||||
|
||||
volName, volNamespace, fsType, readOnly, err := getVolumeInfoFromSpec(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &storageosMounter{
|
||||
storageos: &storageos{
|
||||
podUID: pod.UID,
|
||||
podNamespace: pod.GetNamespace(),
|
||||
pvName: spec.Name(),
|
||||
volName: volName,
|
||||
volNamespace: volNamespace,
|
||||
fsType: fsType,
|
||||
readOnly: readOnly,
|
||||
apiCfg: apiCfg,
|
||||
manager: manager,
|
||||
mounter: mounter,
|
||||
exec: exec,
|
||||
plugin: plugin,
|
||||
MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, volNamespace, volName, spec.Name(), plugin.host)),
|
||||
},
|
||||
devicePath: storageosDevicePath,
|
||||
diskMounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) NewUnmounter(pvName string, podUID types.UID) (volume.Unmounter, error) {
|
||||
return plugin.newUnmounterInternal(pvName, podUID, &storageosUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName()))
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) newUnmounterInternal(pvName string, podUID types.UID, manager storageosManager, mounter mount.Interface, exec mount.Exec) (volume.Unmounter, error) {
|
||||
|
||||
// Parse volume namespace & name from mountpoint if mounted
|
||||
volNamespace, volName, err := getVolumeInfo(pvName, podUID, plugin.host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &storageosUnmounter{
|
||||
storageos: &storageos{
|
||||
podUID: podUID,
|
||||
pvName: pvName,
|
||||
volName: volName,
|
||||
volNamespace: volNamespace,
|
||||
manager: manager,
|
||||
mounter: mounter,
|
||||
exec: exec,
|
||||
plugin: plugin,
|
||||
MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volNamespace, volName, pvName, plugin.host)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
|
||||
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.StorageOS == nil {
|
||||
return nil, fmt.Errorf("spec.PersistentVolumeSource.StorageOS is nil")
|
||||
}
|
||||
|
||||
class, err := util.GetClassForVolume(plugin.host.GetKubeClient(), spec.PersistentVolume)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var adminSecretName, adminSecretNamespace string
|
||||
|
||||
for k, v := range class.Parameters {
|
||||
switch strings.ToLower(k) {
|
||||
case "adminsecretname":
|
||||
adminSecretName = v
|
||||
case "adminsecretnamespace":
|
||||
adminSecretNamespace = v
|
||||
}
|
||||
}
|
||||
|
||||
apiCfg, err := parsePVSecret(adminSecretNamespace, adminSecretName, plugin.host.GetKubeClient())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get admin secret from [%q/%q]: %v", adminSecretNamespace, adminSecretName, err)
|
||||
}
|
||||
|
||||
return plugin.newDeleterInternal(spec, apiCfg, &storageosUtil{})
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) newDeleterInternal(spec *volume.Spec, apiCfg *storageosAPIConfig, manager storageosManager) (volume.Deleter, error) {
|
||||
|
||||
return &storageosDeleter{
|
||||
storageosMounter: &storageosMounter{
|
||||
storageos: &storageos{
|
||||
pvName: spec.Name(),
|
||||
volName: spec.PersistentVolume.Spec.StorageOS.VolumeName,
|
||||
volNamespace: spec.PersistentVolume.Spec.StorageOS.VolumeNamespace,
|
||||
apiCfg: apiCfg,
|
||||
manager: manager,
|
||||
plugin: plugin,
|
||||
},
|
||||
},
|
||||
pvUID: spec.PersistentVolume.UID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
|
||||
return plugin.newProvisionerInternal(options, &storageosUtil{})
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) newProvisionerInternal(options volume.VolumeOptions, manager storageosManager) (volume.Provisioner, error) {
|
||||
return &storageosProvisioner{
|
||||
storageosMounter: &storageosMounter{
|
||||
storageos: &storageos{
|
||||
manager: manager,
|
||||
plugin: plugin,
|
||||
},
|
||||
},
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
|
||||
volNamespace, volName, err := getVolumeFromRef(volumeName)
|
||||
if err != nil {
|
||||
volNamespace = defaultNamespace
|
||||
volName = volumeName
|
||||
}
|
||||
storageosVolume := &v1.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
StorageOS: &v1.StorageOSVolumeSource{
|
||||
VolumeName: volName,
|
||||
VolumeNamespace: volNamespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
return volume.NewSpecFromVolume(storageosVolume), nil
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) SupportsMountOption() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *storageosPlugin) SupportsBulkVolumeVerification() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getVolumeSource(spec *volume.Spec) (*v1.StorageOSVolumeSource, bool, error) {
|
||||
if spec.Volume != nil && spec.Volume.StorageOS != nil {
|
||||
return spec.Volume.StorageOS, spec.Volume.StorageOS.ReadOnly, nil
|
||||
}
|
||||
return nil, false, fmt.Errorf("Spec does not reference a StorageOS volume type")
|
||||
}
|
||||
|
||||
func getPersistentVolumeSource(spec *volume.Spec) (*v1.StorageOSPersistentVolumeSource, bool, error) {
|
||||
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.StorageOS != nil {
|
||||
return spec.PersistentVolume.Spec.StorageOS, spec.ReadOnly, nil
|
||||
}
|
||||
return nil, false, fmt.Errorf("Spec does not reference a StorageOS persistent volume type")
|
||||
}
|
||||
|
||||
// storageosManager is the abstract interface to StorageOS volume ops.
|
||||
type storageosManager interface {
|
||||
// Connects to the StorageOS API using the supplied configuration.
|
||||
NewAPI(apiCfg *storageosAPIConfig) error
|
||||
// Creates a StorageOS volume.
|
||||
CreateVolume(provisioner *storageosProvisioner) (*storageosVolume, error)
|
||||
// Attaches the disk to the kubelet's host machine.
|
||||
AttachVolume(mounter *storageosMounter) (string, error)
|
||||
// Detaches the disk from the kubelet's host machine.
|
||||
DetachVolume(unmounter *storageosUnmounter, dir string) error
|
||||
// Mounts the disk on the Kubelet's host machine.
|
||||
MountVolume(mounter *storageosMounter, mnt, dir string) error
|
||||
// Unmounts the disk from the Kubelet's host machine.
|
||||
UnmountVolume(unounter *storageosUnmounter) error
|
||||
// Deletes the storageos volume. All data will be lost.
|
||||
DeleteVolume(deleter *storageosDeleter) error
|
||||
}
|
||||
|
||||
// storageos volumes represent a bare host directory mount of an StorageOS export.
|
||||
type storageos struct {
|
||||
podUID types.UID
|
||||
podNamespace string
|
||||
pvName string
|
||||
volName string
|
||||
volNamespace string
|
||||
secretName string
|
||||
readOnly bool
|
||||
description string
|
||||
pool string
|
||||
fsType string
|
||||
sizeGB int
|
||||
labels map[string]string
|
||||
apiCfg *storageosAPIConfig
|
||||
manager storageosManager
|
||||
mounter mount.Interface
|
||||
exec mount.Exec
|
||||
plugin *storageosPlugin
|
||||
volume.MetricsProvider
|
||||
}
|
||||
|
||||
type storageosMounter struct {
|
||||
*storageos
|
||||
devicePath string
|
||||
// Interface used to mount the file or block device
|
||||
diskMounter *mount.SafeFormatAndMount
|
||||
}
|
||||
|
||||
var _ volume.Mounter = &storageosMounter{}
|
||||
|
||||
func (b *storageosMounter) GetAttributes() volume.Attributes {
|
||||
return volume.Attributes{
|
||||
ReadOnly: b.readOnly,
|
||||
Managed: !b.readOnly,
|
||||
SupportsSELinux: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Checks prior to mount operations to verify that the required components (binaries, etc.)
|
||||
// to mount the volume are available on the underlying node.
|
||||
// If not, it returns an error
|
||||
func (b *storageosMounter) CanMount() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUp attaches the disk and bind mounts to the volume path.
|
||||
func (b *storageosMounter) SetUp(fsGroup *int64) error {
|
||||
// Need a namespace to find the volume, try pod's namespace if not set.
|
||||
if b.volNamespace == "" {
|
||||
glog.V(2).Infof("Setting StorageOS volume namespace to pod namespace: %s", b.podNamespace)
|
||||
b.volNamespace = b.podNamespace
|
||||
}
|
||||
|
||||
// Attach the StorageOS volume as a block device
|
||||
devicePath, err := b.manager.AttachVolume(b)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to attach StorageOS volume %s: %s", b.volName, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Mount the loop device into the plugin's disk global mount dir.
|
||||
globalPDPath := makeGlobalPDName(b.plugin.host, b.pvName, b.podNamespace, b.volName)
|
||||
err = b.manager.MountVolume(b, devicePath, globalPDPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
glog.V(4).Infof("Successfully mounted StorageOS volume %s into global mount directory", b.volName)
|
||||
|
||||
// Bind mount the volume into the pod
|
||||
return b.SetUpAt(b.GetPath(), fsGroup)
|
||||
}
|
||||
|
||||
// SetUp bind mounts the disk global mount to the give volume path.
|
||||
func (b *storageosMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||
notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
glog.V(4).Infof("StorageOS volume set up: %s %v %v", dir, !notMnt, err)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
glog.Errorf("Cannot validate mount point: %s %v", dir, err)
|
||||
return err
|
||||
}
|
||||
if !notMnt {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = os.MkdirAll(dir, 0750); err != nil {
|
||||
glog.Errorf("mkdir failed on disk %s (%v)", dir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform a bind mount to the full path to allow duplicate mounts of the same PD.
|
||||
options := []string{"bind"}
|
||||
if b.readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
|
||||
globalPDPath := makeGlobalPDName(b.plugin.host, b.pvName, b.volNamespace, b.volName)
|
||||
glog.V(4).Infof("Attempting to bind mount to pod volume at %s", dir)
|
||||
|
||||
err = b.mounter.Mount(globalPDPath, dir, "", options)
|
||||
if err != nil {
|
||||
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if !notMnt {
|
||||
if mntErr = b.mounter.Unmount(dir); mntErr != nil {
|
||||
glog.Errorf("Failed to unmount: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
glog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
|
||||
return err
|
||||
}
|
||||
if !notMnt {
|
||||
glog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
os.Remove(dir)
|
||||
glog.Errorf("Mount of disk %s failed: %v", dir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !b.readOnly {
|
||||
volume.SetVolumeOwnership(b, fsGroup)
|
||||
}
|
||||
glog.V(4).Infof("StorageOS volume setup complete on %s", dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeGlobalPDName(host volume.VolumeHost, pvName, volNamespace, volName string) string {
|
||||
return path.Join(host.GetPluginDir(kstrings.EscapeQualifiedNameForDisk(storageosPluginName)), mount.MountsInGlobalPDPath, pvName+"."+volNamespace+"."+volName)
|
||||
}
|
||||
|
||||
// Given the pod id and PV name, finds the volume's namespace and name from the
|
||||
// name or volume mount. We mount as volNamespace.pvName, but k8s will specify
|
||||
// only the pvName to unmount.
|
||||
// Will return empty volNamespace/pvName if the volume is not mounted.
|
||||
func getVolumeInfo(pvName string, podUID types.UID, host volume.VolumeHost) (string, string, error) {
|
||||
if volNamespace, volName, err := getVolumeFromRef(pvName); err == nil {
|
||||
return volNamespace, volName, nil
|
||||
}
|
||||
|
||||
volumeDir := filepath.Dir(host.GetPodVolumeDir(podUID, kstrings.EscapeQualifiedNameForDisk(storageosPluginName), pvName))
|
||||
files, err := ioutil.ReadDir(volumeDir)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("Could not read mounts from pod volume dir: %s", err)
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.Mode().IsDir() && strings.HasPrefix(f.Name(), pvName+".") {
|
||||
if volNamespace, volName, err := getVolumeFromRef(f.Name()); err == nil {
|
||||
return volNamespace, volName, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", "", fmt.Errorf("Could not get info from unmounted pv %q at %q", pvName, volumeDir)
|
||||
}
|
||||
|
||||
// Splits the volume ref on "." to return the volNamespace and pvName. Neither
|
||||
// namespaces nor service names allow "." in their names.
|
||||
func getVolumeFromRef(ref string) (volNamespace string, volName string, err error) {
|
||||
refParts := strings.Split(ref, ".")
|
||||
switch len(refParts) {
|
||||
case 2:
|
||||
return refParts[0], refParts[1], nil
|
||||
case 3:
|
||||
return refParts[1], refParts[2], nil
|
||||
}
|
||||
return "", "", fmt.Errorf("ref not in format volNamespace.volName or pvName.volNamespace.volName")
|
||||
}
|
||||
|
||||
// GetPath returns the path to the user specific mount of a StorageOS volume
|
||||
func (storageosVolume *storageos) GetPath() string {
|
||||
return getPath(storageosVolume.podUID, storageosVolume.volNamespace, storageosVolume.volName, storageosVolume.pvName, storageosVolume.plugin.host)
|
||||
}
|
||||
|
||||
type storageosUnmounter struct {
|
||||
*storageos
|
||||
}
|
||||
|
||||
var _ volume.Unmounter = &storageosUnmounter{}
|
||||
|
||||
func (b *storageosUnmounter) GetPath() string {
|
||||
return getPath(b.podUID, b.volNamespace, b.volName, b.pvName, b.plugin.host)
|
||||
}
|
||||
|
||||
// Unmounts the bind mount, and detaches the disk only if the PD
|
||||
// resource was the last reference to that disk on the kubelet.
|
||||
func (b *storageosUnmounter) TearDown() error {
|
||||
if len(b.volNamespace) == 0 || len(b.volName) == 0 {
|
||||
glog.Warningf("volNamespace: %q, volName: %q not set, skipping TearDown", b.volNamespace, b.volName)
|
||||
return fmt.Errorf("pvName not specified for TearDown, waiting for next sync loop")
|
||||
}
|
||||
// Unmount from pod
|
||||
mountPath := b.GetPath()
|
||||
|
||||
err := b.TearDownAt(mountPath)
|
||||
if err != nil {
|
||||
glog.Errorf("Unmount from pod failed: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Find device name from global mount
|
||||
globalPDPath := makeGlobalPDName(b.plugin.host, b.pvName, b.volNamespace, b.volName)
|
||||
devicePath, _, err := mount.GetDeviceNameFromMount(b.mounter, globalPDPath)
|
||||
if err != nil {
|
||||
glog.Errorf("Detach failed when getting device from global mount: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmount from plugin's disk global mount dir.
|
||||
err = b.TearDownAt(globalPDPath)
|
||||
if err != nil {
|
||||
glog.Errorf("Detach failed during unmount: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Detach loop device
|
||||
err = b.manager.DetachVolume(b, devicePath)
|
||||
if err != nil {
|
||||
glog.Errorf("Detach device %s failed for volume %s: %v", devicePath, b.pvName, err)
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Successfully unmounted StorageOS volume %s and detached devices", b.pvName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmounts the bind mount, and detaches the disk only if the PD
|
||||
// resource was the last reference to that disk on the kubelet.
|
||||
func (b *storageosUnmounter) TearDownAt(dir string) error {
|
||||
if err := util.UnmountPath(dir, b.mounter); err != nil {
|
||||
glog.V(4).Infof("Unmounted StorageOS volume %s failed with: %v", b.pvName, err)
|
||||
}
|
||||
if err := b.manager.UnmountVolume(b); err != nil {
|
||||
glog.V(4).Infof("Mount reference for volume %s could not be removed from StorageOS: %v", b.pvName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type storageosDeleter struct {
|
||||
*storageosMounter
|
||||
pvUID types.UID
|
||||
}
|
||||
|
||||
var _ volume.Deleter = &storageosDeleter{}
|
||||
|
||||
func (d *storageosDeleter) GetPath() string {
|
||||
return getPath(d.podUID, d.volNamespace, d.volName, d.pvName, d.plugin.host)
|
||||
}
|
||||
|
||||
func (d *storageosDeleter) Delete() error {
|
||||
return d.manager.DeleteVolume(d)
|
||||
}
|
||||
|
||||
type storageosProvisioner struct {
|
||||
*storageosMounter
|
||||
options volume.VolumeOptions
|
||||
}
|
||||
|
||||
var _ volume.Provisioner = &storageosProvisioner{}
|
||||
|
||||
func (c *storageosProvisioner) Provision() (*v1.PersistentVolume, error) {
|
||||
if !volume.AccessModesContainedInAll(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
||||
}
|
||||
|
||||
var adminSecretName, adminSecretNamespace string
|
||||
|
||||
// Apply ProvisionerParameters (case-insensitive). We leave validation of
|
||||
// the values to the cloud provider.
|
||||
for k, v := range c.options.Parameters {
|
||||
switch strings.ToLower(k) {
|
||||
case "adminsecretname":
|
||||
adminSecretName = v
|
||||
case "adminsecretnamespace":
|
||||
adminSecretNamespace = v
|
||||
case "volumenamespace":
|
||||
c.volNamespace = v
|
||||
case "description":
|
||||
c.description = v
|
||||
case "pool":
|
||||
c.pool = v
|
||||
case "fstype":
|
||||
c.fsType = v
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, c.plugin.GetPluginName())
|
||||
}
|
||||
}
|
||||
|
||||
// Set from PVC
|
||||
c.podNamespace = c.options.PVC.Namespace
|
||||
c.volName = c.options.PVName
|
||||
if c.volNamespace == "" {
|
||||
c.volNamespace = c.options.PVC.Namespace
|
||||
}
|
||||
c.labels = make(map[string]string)
|
||||
for k, v := range c.options.PVC.Labels {
|
||||
c.labels[k] = v
|
||||
}
|
||||
capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
|
||||
c.sizeGB = int(volume.RoundUpSize(capacity.Value(), 1024*1024*1024))
|
||||
|
||||
apiCfg, err := parsePVSecret(adminSecretNamespace, adminSecretName, c.plugin.host.GetKubeClient())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.apiCfg = apiCfg
|
||||
|
||||
vol, err := c.manager.CreateVolume(c)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to create volume: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if vol.FSType == "" {
|
||||
vol.FSType = defaultFSType
|
||||
}
|
||||
|
||||
pv := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: vol.Name,
|
||||
Labels: map[string]string{},
|
||||
Annotations: map[string]string{
|
||||
volumehelper.VolumeDynamicallyCreatedByKey: "storageos-dynamic-provisioner",
|
||||
},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
|
||||
AccessModes: c.options.PVC.Spec.AccessModes,
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", vol.SizeGB)),
|
||||
},
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
StorageOS: &v1.StorageOSPersistentVolumeSource{
|
||||
VolumeName: vol.Name,
|
||||
VolumeNamespace: vol.Namespace,
|
||||
FSType: vol.FSType,
|
||||
ReadOnly: false,
|
||||
SecretRef: &v1.ObjectReference{
|
||||
Name: adminSecretName,
|
||||
Namespace: adminSecretNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if len(c.options.PVC.Spec.AccessModes) == 0 {
|
||||
pv.Spec.AccessModes = c.plugin.GetAccessModes()
|
||||
}
|
||||
if len(vol.Labels) != 0 {
|
||||
if pv.Labels == nil {
|
||||
pv.Labels = make(map[string]string)
|
||||
}
|
||||
for k, v := range vol.Labels {
|
||||
pv.Labels[k] = v
|
||||
}
|
||||
}
|
||||
return pv, nil
|
||||
}
|
||||
|
||||
// Returns StorageOS volume name, namespace, fstype and readonly from spec
|
||||
func getVolumeInfoFromSpec(spec *volume.Spec) (string, string, string, bool, error) {
|
||||
if spec.PersistentVolume != nil {
|
||||
source, readOnly, err := getPersistentVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", "", "", false, err
|
||||
}
|
||||
return source.VolumeName, source.VolumeNamespace, source.FSType, readOnly, nil
|
||||
}
|
||||
|
||||
if spec.Volume != nil {
|
||||
source, readOnly, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", "", "", false, err
|
||||
}
|
||||
return source.VolumeName, source.VolumeNamespace, source.FSType, readOnly, nil
|
||||
}
|
||||
return "", "", "", false, fmt.Errorf("spec not Volume or PersistentVolume")
|
||||
}
|
||||
|
||||
// Returns API config if secret set, otherwise empty struct so defaults can be
|
||||
// attempted.
|
||||
func getAPICfg(spec *volume.Spec, pod *v1.Pod, kubeClient clientset.Interface) (*storageosAPIConfig, error) {
|
||||
if spec.PersistentVolume != nil {
|
||||
source, _, err := getPersistentVolumeSource(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if source.SecretRef == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return parsePVSecret(source.SecretRef.Namespace, source.SecretRef.Name, kubeClient)
|
||||
}
|
||||
|
||||
if spec.Volume != nil {
|
||||
source, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if source.SecretRef == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return parsePodSecret(pod, source.SecretRef.Name, kubeClient)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("spec not Volume or PersistentVolume")
|
||||
}
|
||||
|
||||
func parsePodSecret(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (*storageosAPIConfig, error) {
|
||||
secret, err := util.GetSecretForPod(pod, secretName, kubeClient)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to get secret from [%q/%q]", pod.Namespace, secretName)
|
||||
return nil, fmt.Errorf("failed to get secret from [%q/%q]", pod.Namespace, secretName)
|
||||
}
|
||||
return parseAPIConfig(secret)
|
||||
}
|
||||
|
||||
// Important: Only to be called with data from a PV to avoid secrets being
|
||||
// loaded from a user-suppler namespace.
|
||||
func parsePVSecret(namespace, secretName string, kubeClient clientset.Interface) (*storageosAPIConfig, error) {
|
||||
secret, err := util.GetSecretForPV(namespace, secretName, storageosPluginName, kubeClient)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to get secret from [%q/%q]", namespace, secretName)
|
||||
return nil, fmt.Errorf("failed to get secret from [%q/%q]", namespace, secretName)
|
||||
}
|
||||
return parseAPIConfig(secret)
|
||||
}
|
||||
|
||||
// Parse API configuration from parameters or secret
|
||||
func parseAPIConfig(params map[string]string) (*storageosAPIConfig, error) {
|
||||
|
||||
if len(params) == 0 {
|
||||
return nil, fmt.Errorf("empty API config")
|
||||
}
|
||||
|
||||
c := &storageosAPIConfig{}
|
||||
|
||||
for name, data := range params {
|
||||
switch strings.ToLower(name) {
|
||||
case "apiaddress":
|
||||
c.apiAddr = string(data)
|
||||
case "apiusername":
|
||||
c.apiUser = string(data)
|
||||
case "apipassword":
|
||||
c.apiPass = string(data)
|
||||
case "apiversion":
|
||||
c.apiVersion = string(data)
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
363
vendor/k8s.io/kubernetes/pkg/volume/storageos/storageos_test.go
generated
vendored
Normal file
363
vendor/k8s.io/kubernetes/pkg/volume/storageos/storageos_test.go
generated
vendored
Normal file
@ -0,0 +1,363 @@
|
||||
/*
|
||||
Copyright 2017 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 storageos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
)
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/storageos")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
if plug.GetPluginName() != "kubernetes.io/storageos" {
|
||||
t.Errorf("Wrong name: %s", plug.GetPluginName())
|
||||
}
|
||||
if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{StorageOS: &v1.StorageOSVolumeSource{}}}}) {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{StorageOS: &v1.StorageOSPersistentVolumeSource{}}}}}) {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccessModes(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/storageos")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) {
|
||||
t.Errorf("Expected two AccessModeTypes: %s and %s", v1.ReadWriteOnce, v1.ReadOnlyMany)
|
||||
}
|
||||
}
|
||||
|
||||
type fakePDManager struct {
|
||||
api apiImplementer
|
||||
attachCalled bool
|
||||
detachCalled bool
|
||||
mountCalled bool
|
||||
unmountCalled bool
|
||||
createCalled bool
|
||||
deleteCalled bool
|
||||
}
|
||||
|
||||
func (fake *fakePDManager) NewAPI(apiCfg *storageosAPIConfig) error {
|
||||
fake.api = fakeAPI{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fake *fakePDManager) CreateVolume(p *storageosProvisioner) (*storageosVolume, error) {
|
||||
fake.createCalled = true
|
||||
labels := make(map[string]string)
|
||||
labels["fakepdmanager"] = "yes"
|
||||
return &storageosVolume{
|
||||
Name: "test-storageos-name",
|
||||
Namespace: "test-storageos-namespace",
|
||||
Pool: "test-storageos-pool",
|
||||
SizeGB: 100,
|
||||
Labels: labels,
|
||||
FSType: "ext2",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (fake *fakePDManager) AttachVolume(b *storageosMounter) (string, error) {
|
||||
fake.attachCalled = true
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (fake *fakePDManager) DetachVolume(b *storageosUnmounter, loopDevice string) error {
|
||||
fake.detachCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fake *fakePDManager) MountVolume(b *storageosMounter, mntDevice, deviceMountPath string) error {
|
||||
fake.mountCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fake *fakePDManager) UnmountVolume(b *storageosUnmounter) error {
|
||||
fake.unmountCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fake *fakePDManager) DeleteVolume(d *storageosDeleter) error {
|
||||
fake.deleteCalled = true
|
||||
if d.volName != "test-storageos-name" {
|
||||
return fmt.Errorf("Deleter got unexpected volume name: %s", d.volName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
|
||||
if err != nil {
|
||||
t.Fatalf("can't make a temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/storageos")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
secretName := "very-secret"
|
||||
spec := &v1.Volume{
|
||||
Name: "vol1-pvname",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
StorageOS: &v1.StorageOSVolumeSource{
|
||||
VolumeName: "vol1",
|
||||
VolumeNamespace: "ns1",
|
||||
FSType: "ext3",
|
||||
SecretRef: &v1.LocalObjectReference{
|
||||
Name: secretName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
client := fake.NewSimpleClientset()
|
||||
|
||||
client.Core().Secrets("default").Create(&v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: "default",
|
||||
},
|
||||
Type: "kubernetes.io/storageos",
|
||||
Data: map[string][]byte{
|
||||
"apiUsername": []byte("storageos"),
|
||||
"apiPassword": []byte("storageos"),
|
||||
"apiAddr": []byte("tcp://localhost:5705"),
|
||||
}})
|
||||
|
||||
plug.(*storageosPlugin).host = volumetest.NewFakeVolumeHost(tmpDir, client, nil)
|
||||
|
||||
// Test Mounter
|
||||
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid"), Namespace: "default"}}
|
||||
fakeManager := &fakePDManager{}
|
||||
|
||||
apiCfg, err := parsePodSecret(pod, secretName, plug.(*storageosPlugin).host.GetKubeClient())
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't get secret from %v/%v", pod.Namespace, secretName)
|
||||
}
|
||||
|
||||
mounter, err := plug.(*storageosPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), pod, apiCfg, fakeManager, &mount.FakeMounter{}, mount.NewFakeExec(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to make a new Mounter: %v", err)
|
||||
}
|
||||
if mounter == nil {
|
||||
t.Fatalf("Got a nil Mounter")
|
||||
}
|
||||
|
||||
expectedPath := path.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~storageos/vol1-pvname.ns1.vol1")
|
||||
volPath := mounter.GetPath()
|
||||
if volPath != expectedPath {
|
||||
t.Errorf("Expected path: '%s' got: '%s'", expectedPath, volPath)
|
||||
}
|
||||
|
||||
if err := mounter.SetUp(nil); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(volPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Errorf("SetUp() failed, volume path not created: %s", volPath)
|
||||
} else {
|
||||
t.Errorf("SetUp() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !fakeManager.attachCalled {
|
||||
t.Errorf("Attach not called")
|
||||
}
|
||||
if !fakeManager.mountCalled {
|
||||
t.Errorf("Mount not called")
|
||||
}
|
||||
|
||||
// Test Unmounter
|
||||
fakeManager = &fakePDManager{}
|
||||
unmounter, err := plug.(*storageosPlugin).newUnmounterInternal("vol1-pvname", types.UID("poduid"), fakeManager, &mount.FakeMounter{}, mount.NewFakeExec(nil))
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Unmounter: %v", err)
|
||||
}
|
||||
if unmounter == nil {
|
||||
t.Errorf("Got a nil Unmounter")
|
||||
}
|
||||
|
||||
volPath = unmounter.GetPath()
|
||||
if volPath != expectedPath {
|
||||
t.Errorf("Expected path: '%s' got: '%s'", expectedPath, volPath)
|
||||
}
|
||||
|
||||
if err := unmounter.TearDown(); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(volPath); err == nil {
|
||||
t.Errorf("TearDown() failed, volume path still exists: %s", volPath)
|
||||
} else if !os.IsNotExist(err) {
|
||||
t.Errorf("TearDown() failed: %v", err)
|
||||
}
|
||||
|
||||
if !fakeManager.unmountCalled {
|
||||
t.Errorf("Unmount not called")
|
||||
}
|
||||
if !fakeManager.detachCalled {
|
||||
t.Errorf("Detach not called")
|
||||
}
|
||||
|
||||
// Test Provisioner
|
||||
fakeManager = &fakePDManager{}
|
||||
options := volume.VolumeOptions{
|
||||
PVC: volumetest.CreateTestPVC("100Mi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}),
|
||||
// PVName: "test-volume-name",
|
||||
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
|
||||
Parameters: map[string]string{
|
||||
"VolumeNamespace": "test-volume-namespace",
|
||||
"adminSecretName": secretName,
|
||||
},
|
||||
}
|
||||
provisioner, err := plug.(*storageosPlugin).newProvisionerInternal(options, fakeManager)
|
||||
if err != nil {
|
||||
t.Errorf("newProvisionerInternal() failed: %v", err)
|
||||
}
|
||||
|
||||
persistentSpec, err := provisioner.Provision()
|
||||
if err != nil {
|
||||
t.Fatalf("Provision() failed: %v", err)
|
||||
}
|
||||
|
||||
if persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeName != "test-storageos-name" {
|
||||
t.Errorf("Provision() returned unexpected volume Name: %s, expected test-storageos-name", persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeName)
|
||||
}
|
||||
if persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeNamespace != "test-storageos-namespace" {
|
||||
t.Errorf("Provision() returned unexpected volume Namespace: %s", persistentSpec.Spec.PersistentVolumeSource.StorageOS.VolumeNamespace)
|
||||
}
|
||||
cap := persistentSpec.Spec.Capacity[v1.ResourceStorage]
|
||||
size := cap.Value()
|
||||
if size != 100*1024*1024*1024 {
|
||||
t.Errorf("Provision() returned unexpected volume size: %v", size)
|
||||
}
|
||||
if persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType != "ext2" {
|
||||
t.Errorf("Provision() returned unexpected volume FSType: %s", persistentSpec.Spec.PersistentVolumeSource.StorageOS.FSType)
|
||||
}
|
||||
if persistentSpec.Labels["fakepdmanager"] != "yes" {
|
||||
t.Errorf("Provision() returned unexpected labels: %v", persistentSpec.Labels)
|
||||
}
|
||||
if !fakeManager.createCalled {
|
||||
t.Errorf("Create not called")
|
||||
}
|
||||
|
||||
// Test Deleter
|
||||
fakeManager = &fakePDManager{}
|
||||
volSpec := &volume.Spec{
|
||||
PersistentVolume: persistentSpec,
|
||||
}
|
||||
deleter, err := plug.(*storageosPlugin).newDeleterInternal(volSpec, apiCfg, fakeManager)
|
||||
if err != nil {
|
||||
t.Errorf("newDeleterInternal() failed: %v", err)
|
||||
}
|
||||
|
||||
err = deleter.Delete()
|
||||
if err != nil {
|
||||
t.Errorf("Deleter() failed: %v", err)
|
||||
}
|
||||
if !fakeManager.deleteCalled {
|
||||
t.Errorf("Delete not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistentClaimReadOnlyFlag(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
pv := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvA",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
StorageOS: &v1.StorageOSPersistentVolumeSource{VolumeName: "pvA", VolumeNamespace: "vnsA", ReadOnly: false},
|
||||
},
|
||||
ClaimRef: &v1.ObjectReference{
|
||||
Name: "claimA",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
claim := &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "claimA",
|
||||
Namespace: "nsA",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
VolumeName: "pvA",
|
||||
},
|
||||
Status: v1.PersistentVolumeClaimStatus{
|
||||
Phase: v1.ClaimBound,
|
||||
},
|
||||
}
|
||||
|
||||
client := fake.NewSimpleClientset(pv, claim)
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, client, nil))
|
||||
plug, _ := plugMgr.FindPluginByName(storageosPluginName)
|
||||
|
||||
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
|
||||
spec := volume.NewSpecFromPersistentVolume(pv, true)
|
||||
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "nsA", UID: types.UID("poduid")}}
|
||||
fakeManager := &fakePDManager{}
|
||||
fakeConfig := &fakeConfig{}
|
||||
apiCfg := fakeConfig.GetAPIConfig()
|
||||
mounter, err := plug.(*storageosPlugin).newMounterInternal(spec, pod, apiCfg, fakeManager, &mount.FakeMounter{}, mount.NewFakeExec(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("error creating a new internal mounter:%v", err)
|
||||
}
|
||||
if !mounter.GetAttributes().ReadOnly {
|
||||
t.Errorf("Expected true for mounter.IsReadOnly")
|
||||
}
|
||||
}
|
371
vendor/k8s.io/kubernetes/pkg/volume/storageos/storageos_util.go
generated
vendored
Normal file
371
vendor/k8s.io/kubernetes/pkg/volume/storageos/storageos_util.go
generated
vendored
Normal file
@ -0,0 +1,371 @@
|
||||
/*
|
||||
Copyright 2017 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 storageos
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
|
||||
"github.com/golang/glog"
|
||||
storageosapi "github.com/storageos/go-api"
|
||||
storageostypes "github.com/storageos/go-api/types"
|
||||
)
|
||||
|
||||
const (
|
||||
losetupPath = "losetup"
|
||||
|
||||
modeBlock deviceType = iota
|
||||
modeFile
|
||||
modeUnsupported
|
||||
|
||||
ErrDeviceNotFound = "device not found"
|
||||
ErrDeviceNotSupported = "device not supported"
|
||||
ErrNotAvailable = "not available"
|
||||
)
|
||||
|
||||
type deviceType int
|
||||
|
||||
// storageosVolume describes a provisioned volume
|
||||
type storageosVolume struct {
|
||||
ID string
|
||||
Name string
|
||||
Namespace string
|
||||
Description string
|
||||
Pool string
|
||||
SizeGB int
|
||||
Labels map[string]string
|
||||
FSType string
|
||||
}
|
||||
|
||||
type storageosAPIConfig struct {
|
||||
apiAddr string
|
||||
apiUser string
|
||||
apiPass string
|
||||
apiVersion string
|
||||
}
|
||||
|
||||
type apiImplementer interface {
|
||||
Volume(namespace string, ref string) (*storageostypes.Volume, error)
|
||||
VolumeCreate(opts storageostypes.VolumeCreateOptions) (*storageostypes.Volume, error)
|
||||
VolumeMount(opts storageostypes.VolumeMountOptions) error
|
||||
VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error
|
||||
VolumeDelete(opt storageostypes.DeleteOptions) error
|
||||
}
|
||||
|
||||
// storageosUtil is the utility structure to interact with the StorageOS API.
|
||||
type storageosUtil struct {
|
||||
api apiImplementer
|
||||
}
|
||||
|
||||
func (u *storageosUtil) NewAPI(apiCfg *storageosAPIConfig) error {
|
||||
if u.api != nil {
|
||||
return nil
|
||||
}
|
||||
if apiCfg == nil {
|
||||
apiCfg = &storageosAPIConfig{
|
||||
apiAddr: defaultAPIAddress,
|
||||
apiUser: defaultAPIUser,
|
||||
apiPass: defaultAPIPassword,
|
||||
apiVersion: defaultAPIVersion,
|
||||
}
|
||||
glog.V(4).Infof("Using default StorageOS API settings: addr %s, version: %s", apiCfg.apiAddr, defaultAPIVersion)
|
||||
}
|
||||
|
||||
api, err := storageosapi.NewVersionedClient(apiCfg.apiAddr, defaultAPIVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
api.SetAuth(apiCfg.apiUser, apiCfg.apiPass)
|
||||
u.api = api
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates a new StorageOS volume and makes it available as a device within
|
||||
// /var/lib/storageos/volumes.
|
||||
func (u *storageosUtil) CreateVolume(p *storageosProvisioner) (*storageosVolume, error) {
|
||||
if err := u.NewAPI(p.apiCfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.labels == nil {
|
||||
p.labels = make(map[string]string)
|
||||
}
|
||||
opts := storageostypes.VolumeCreateOptions{
|
||||
Name: p.volName,
|
||||
Size: p.sizeGB,
|
||||
Description: p.description,
|
||||
Pool: p.pool,
|
||||
FSType: p.fsType,
|
||||
Namespace: p.volNamespace,
|
||||
Labels: p.labels,
|
||||
}
|
||||
|
||||
vol, err := u.api.VolumeCreate(opts)
|
||||
if err != nil {
|
||||
glog.Errorf("volume create failed for volume %q (%v)", opts.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
return &storageosVolume{
|
||||
ID: vol.ID,
|
||||
Name: vol.Name,
|
||||
Namespace: vol.Namespace,
|
||||
Description: vol.Description,
|
||||
Pool: vol.Pool,
|
||||
FSType: vol.FSType,
|
||||
SizeGB: int(vol.Size),
|
||||
Labels: vol.Labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Attach exposes a volume on the host as a block device. StorageOS uses a
|
||||
// global namespace, so if the volume exists, it should already be available as
|
||||
// a device within `/var/lib/storageos/volumes/<id>`.
|
||||
//
|
||||
// Depending on the host capabilities, the device may be either a block device
|
||||
// or a file device. Block devices can be used directly, but file devices must
|
||||
// be made accessible as a block device before using.
|
||||
func (u *storageosUtil) AttachVolume(b *storageosMounter) (string, error) {
|
||||
if err := u.NewAPI(b.apiCfg); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
vol, err := u.api.Volume(b.volNamespace, b.volName)
|
||||
if err != nil {
|
||||
glog.Warningf("volume retrieve failed for volume %q with namespace %q (%v)", b.volName, b.volNamespace, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Clear any existing mount reference from the API. These may be leftover
|
||||
// from previous mounts where the unmount operation couldn't get access to
|
||||
// the API credentials.
|
||||
if vol.Mounted {
|
||||
opts := storageostypes.VolumeUnmountOptions{
|
||||
Name: vol.Name,
|
||||
Namespace: vol.Namespace,
|
||||
}
|
||||
if err := u.api.VolumeUnmount(opts); err != nil {
|
||||
glog.Warningf("Couldn't clear existing StorageOS mount reference: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
srcPath := path.Join(b.devicePath, vol.ID)
|
||||
dt, err := pathDeviceType(srcPath)
|
||||
if err != nil {
|
||||
glog.Warningf("volume source path %q for volume %q not ready (%v)", srcPath, b.volName, err)
|
||||
return "", err
|
||||
}
|
||||
switch dt {
|
||||
case modeBlock:
|
||||
return srcPath, nil
|
||||
case modeFile:
|
||||
return attachFileDevice(srcPath, b.exec)
|
||||
default:
|
||||
return "", fmt.Errorf(ErrDeviceNotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
// Detach detaches a volume from the host. This is only needed when NBD is not
|
||||
// enabled and loop devices are used to simulate a block device.
|
||||
func (u *storageosUtil) DetachVolume(b *storageosUnmounter, devicePath string) error {
|
||||
if !isLoopDevice(devicePath) {
|
||||
return nil
|
||||
}
|
||||
if _, err := os.Stat(devicePath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return removeLoopDevice(devicePath, b.exec)
|
||||
}
|
||||
|
||||
// Mount mounts the volume on the host.
|
||||
func (u *storageosUtil) MountVolume(b *storageosMounter, mntDevice, deviceMountPath string) error {
|
||||
notMnt, err := b.mounter.IsLikelyNotMountPoint(deviceMountPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(deviceMountPath, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
notMnt = true
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = os.MkdirAll(deviceMountPath, 0750); err != nil {
|
||||
glog.Errorf("mkdir failed on disk %s (%v)", deviceMountPath, err)
|
||||
return err
|
||||
}
|
||||
options := []string{}
|
||||
if b.readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
if notMnt {
|
||||
err = b.diskMounter.FormatAndMount(mntDevice, deviceMountPath, b.fsType, options)
|
||||
if err != nil {
|
||||
os.Remove(deviceMountPath)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := u.NewAPI(b.apiCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := storageostypes.VolumeMountOptions{
|
||||
Name: b.volName,
|
||||
Namespace: b.volNamespace,
|
||||
FsType: b.fsType,
|
||||
Mountpoint: deviceMountPath,
|
||||
Client: b.plugin.host.GetHostName(),
|
||||
}
|
||||
return u.api.VolumeMount(opts)
|
||||
}
|
||||
|
||||
// Unmount removes the mount reference from the volume allowing it to be
|
||||
// re-mounted elsewhere.
|
||||
func (u *storageosUtil) UnmountVolume(b *storageosUnmounter) error {
|
||||
if err := u.NewAPI(b.apiCfg); err != nil {
|
||||
// We can't always get the config we need, so allow the unmount to
|
||||
// succeed even if we can't remove the mount reference from the API.
|
||||
glog.V(4).Infof("Could not remove mount reference in the StorageOS API as no credentials available to the unmount operation")
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := storageostypes.VolumeUnmountOptions{
|
||||
Name: b.volName,
|
||||
Namespace: b.volNamespace,
|
||||
Client: b.plugin.host.GetHostName(),
|
||||
}
|
||||
return u.api.VolumeUnmount(opts)
|
||||
}
|
||||
|
||||
// Deletes a StorageOS volume. Assumes it has already been unmounted and detached.
|
||||
func (u *storageosUtil) DeleteVolume(d *storageosDeleter) error {
|
||||
if err := u.NewAPI(d.apiCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deletes must be forced as the StorageOS API will not normally delete
|
||||
// volumes that it thinks are mounted. We can't be sure the unmount was
|
||||
// registered via the API so we trust k8s to only delete volumes it knows
|
||||
// are unmounted.
|
||||
opts := storageostypes.DeleteOptions{
|
||||
Name: d.volName,
|
||||
Namespace: d.volNamespace,
|
||||
Force: true,
|
||||
}
|
||||
return u.api.VolumeDelete(opts)
|
||||
}
|
||||
|
||||
// pathMode returns the FileMode for a path.
|
||||
func pathDeviceType(path string) (deviceType, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return modeUnsupported, err
|
||||
}
|
||||
switch mode := fi.Mode(); {
|
||||
case mode&os.ModeDevice != 0:
|
||||
return modeBlock, nil
|
||||
case mode.IsRegular():
|
||||
return modeFile, nil
|
||||
default:
|
||||
return modeUnsupported, nil
|
||||
}
|
||||
}
|
||||
|
||||
// attachFileDevice takes a path to a regular file and makes it available as an
|
||||
// attached block device.
|
||||
func attachFileDevice(path string, exec mount.Exec) (string, error) {
|
||||
blockDevicePath, err := getLoopDevice(path, exec)
|
||||
if err != nil && err.Error() != ErrDeviceNotFound {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If no existing loop device for the path, create one
|
||||
if blockDevicePath == "" {
|
||||
glog.V(4).Infof("Creating device for path: %s", path)
|
||||
blockDevicePath, err = makeLoopDevice(path, exec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return blockDevicePath, nil
|
||||
}
|
||||
|
||||
// Returns the full path to the loop device associated with the given path.
|
||||
func getLoopDevice(path string, exec mount.Exec) (string, error) {
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return "", errors.New(ErrNotAvailable)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("not attachable: %v", err)
|
||||
}
|
||||
|
||||
args := []string{"-j", path}
|
||||
out, err := exec.Run(losetupPath, args...)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Failed device discover command for path %s: %v", path, err)
|
||||
return "", err
|
||||
}
|
||||
return parseLosetupOutputForDevice(out)
|
||||
}
|
||||
|
||||
func makeLoopDevice(path string, exec mount.Exec) (string, error) {
|
||||
args := []string{"-f", "--show", path}
|
||||
out, err := exec.Run(losetupPath, args...)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Failed device create command for path %s: %v", path, err)
|
||||
return "", err
|
||||
}
|
||||
return parseLosetupOutputForDevice(out)
|
||||
}
|
||||
|
||||
func removeLoopDevice(device string, exec mount.Exec) error {
|
||||
args := []string{"-d", device}
|
||||
out, err := exec.Run(losetupPath, args...)
|
||||
if err != nil {
|
||||
if !strings.Contains(string(out), "No such device or address") {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLoopDevice(device string) bool {
|
||||
return strings.HasPrefix(device, "/dev/loop")
|
||||
}
|
||||
|
||||
func parseLosetupOutputForDevice(output []byte) (string, error) {
|
||||
if len(output) == 0 {
|
||||
return "", errors.New(ErrDeviceNotFound)
|
||||
}
|
||||
|
||||
// losetup returns device in the format:
|
||||
// /dev/loop1: [0073]:148662 (/var/lib/storageos/volumes/308f14af-cf0a-08ff-c9c3-b48104318e05)
|
||||
device := strings.TrimSpace(strings.SplitN(string(output), ":", 2)[0])
|
||||
if len(device) == 0 {
|
||||
return "", errors.New(ErrDeviceNotFound)
|
||||
}
|
||||
return device, nil
|
||||
}
|
235
vendor/k8s.io/kubernetes/pkg/volume/storageos/storageos_util_test.go
generated
vendored
Normal file
235
vendor/k8s.io/kubernetes/pkg/volume/storageos/storageos_util_test.go
generated
vendored
Normal file
@ -0,0 +1,235 @@
|
||||
/*
|
||||
Copyright 2017 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 storageos
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
storageostypes "github.com/storageos/go-api/types"
|
||||
"k8s.io/api/core/v1"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubernetes/pkg/util/mount"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testApiSecretName = "storageos-api"
|
||||
var testVolName = "storageos-test-vol"
|
||||
var testPVName = "storageos-test-pv"
|
||||
var testNamespace = "storageos-test-namespace"
|
||||
var testSize = 1
|
||||
var testDesc = "testdescription"
|
||||
var testPool = "testpool"
|
||||
var testFSType = "ext2"
|
||||
var testVolUUID = "01c43d34-89f8-83d3-422b-43536a0f25e6"
|
||||
|
||||
type fakeConfig struct {
|
||||
apiAddr string
|
||||
apiUser string
|
||||
apiPass string
|
||||
apiVersion string
|
||||
}
|
||||
|
||||
func (c fakeConfig) GetAPIConfig() *storageosAPIConfig {
|
||||
return &storageosAPIConfig{
|
||||
apiAddr: "http://5.6.7.8:9999",
|
||||
apiUser: "abc",
|
||||
apiPass: "123",
|
||||
apiVersion: "10",
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
util := storageosUtil{}
|
||||
cfg := fakeConfig{}
|
||||
err := util.NewAPI(cfg.GetAPIConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("error getting api config: %v", err)
|
||||
}
|
||||
if util.api == nil {
|
||||
t.Errorf("client() unexpectedly returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeAPI struct{}
|
||||
|
||||
func (f fakeAPI) Volume(namespace string, ref string) (*storageostypes.Volume, error) {
|
||||
if namespace == testNamespace && ref == testVolName {
|
||||
return &storageostypes.Volume{
|
||||
ID: "01c43d34-89f8-83d3-422b-43536a0f25e6",
|
||||
Name: ref,
|
||||
Pool: "default",
|
||||
Namespace: namespace,
|
||||
Size: 5,
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
func (f fakeAPI) VolumeCreate(opts storageostypes.VolumeCreateOptions) (*storageostypes.Volume, error) {
|
||||
|
||||
// Append a label from the api
|
||||
labels := opts.Labels
|
||||
labels["labelfromapi"] = "apilabel"
|
||||
|
||||
return &storageostypes.Volume{
|
||||
ID: testVolUUID,
|
||||
Name: opts.Name,
|
||||
Namespace: opts.Namespace,
|
||||
Description: opts.Description,
|
||||
Pool: opts.Pool,
|
||||
Size: opts.Size,
|
||||
FSType: opts.FSType,
|
||||
Labels: labels,
|
||||
}, nil
|
||||
}
|
||||
func (f fakeAPI) VolumeMount(opts storageostypes.VolumeMountOptions) error {
|
||||
return nil
|
||||
}
|
||||
func (f fakeAPI) VolumeUnmount(opts storageostypes.VolumeUnmountOptions) error {
|
||||
return nil
|
||||
}
|
||||
func (f fakeAPI) VolumeDelete(opts storageostypes.DeleteOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCreateVolume(t *testing.T) {
|
||||
|
||||
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
|
||||
if err != nil {
|
||||
t.Fatalf("can't make a temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||
plug, _ := plugMgr.FindPluginByName("kubernetes.io/storageos")
|
||||
|
||||
// Use real util with stubbed api
|
||||
util := &storageosUtil{}
|
||||
util.api = fakeAPI{}
|
||||
|
||||
labels := map[string]string{
|
||||
"labelA": "valueA",
|
||||
"labelB": "valueB",
|
||||
}
|
||||
|
||||
options := volume.VolumeOptions{
|
||||
PVName: testPVName,
|
||||
PVC: volumetest.CreateTestPVC(fmt.Sprintf("%dGi", testSize), []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}),
|
||||
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
|
||||
}
|
||||
|
||||
provisioner := &storageosProvisioner{
|
||||
storageosMounter: &storageosMounter{
|
||||
storageos: &storageos{
|
||||
pvName: testPVName,
|
||||
volName: testVolName,
|
||||
volNamespace: testNamespace,
|
||||
sizeGB: testSize,
|
||||
pool: testPool,
|
||||
description: testDesc,
|
||||
fsType: testFSType,
|
||||
labels: labels,
|
||||
manager: util,
|
||||
plugin: plug.(*storageosPlugin),
|
||||
},
|
||||
},
|
||||
options: options,
|
||||
}
|
||||
|
||||
vol, err := util.CreateVolume(provisioner)
|
||||
if err != nil {
|
||||
t.Errorf("CreateVolume() returned error: %v", err)
|
||||
}
|
||||
if vol == nil {
|
||||
t.Fatalf("CreateVolume() vol is empty")
|
||||
}
|
||||
if vol.ID == "" {
|
||||
t.Error("CreateVolume() vol ID is empty")
|
||||
}
|
||||
if vol.Name != testVolName {
|
||||
t.Errorf("CreateVolume() returned unexpected Name %s", vol.Name)
|
||||
}
|
||||
if vol.Namespace != testNamespace {
|
||||
t.Errorf("CreateVolume() returned unexpected Namespace %s", vol.Namespace)
|
||||
}
|
||||
if vol.Pool != testPool {
|
||||
t.Errorf("CreateVolume() returned unexpected Pool %s", vol.Pool)
|
||||
}
|
||||
if vol.FSType != testFSType {
|
||||
t.Errorf("CreateVolume() returned unexpected FSType %s", vol.FSType)
|
||||
}
|
||||
if vol.SizeGB != testSize {
|
||||
t.Errorf("CreateVolume() returned unexpected Size %d", vol.SizeGB)
|
||||
}
|
||||
if len(vol.Labels) == 0 {
|
||||
t.Error("CreateVolume() Labels are empty")
|
||||
} else {
|
||||
for k, v := range labels {
|
||||
var val string
|
||||
var ok bool
|
||||
if val, ok = vol.Labels[k]; !ok {
|
||||
t.Errorf("CreateVolume() Label %s not set", k)
|
||||
}
|
||||
if val != v {
|
||||
t.Errorf("CreateVolume() returned unexpected Label value %s", val)
|
||||
}
|
||||
}
|
||||
var val string
|
||||
var ok bool
|
||||
if val, ok = vol.Labels["labelfromapi"]; !ok {
|
||||
t.Error("CreateVolume() Label from api not set")
|
||||
}
|
||||
if val != "apilabel" {
|
||||
t.Errorf("CreateVolume() returned unexpected Label value %s", val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachVolume(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("storageos_test")
|
||||
if err != nil {
|
||||
t.Fatalf("can't make a temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||
plug, _ := plugMgr.FindPluginByName("kubernetes.io/storageos")
|
||||
|
||||
// Use real util with stubbed api
|
||||
util := &storageosUtil{}
|
||||
util.api = fakeAPI{}
|
||||
|
||||
mounter := &storageosMounter{
|
||||
storageos: &storageos{
|
||||
volName: testVolName,
|
||||
volNamespace: testNamespace,
|
||||
manager: util,
|
||||
mounter: &mount.FakeMounter{},
|
||||
plugin: plug.(*storageosPlugin),
|
||||
},
|
||||
devicePath: tmpDir,
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Mounter: %v", err)
|
||||
}
|
||||
if mounter == nil {
|
||||
t.Errorf("Got a nil Mounter")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user