Commit 0e6cdd48 authored by Darren Shepherd's avatar Darren Shepherd

Delete most volume drivers

parent bcd6a488
......@@ -28,19 +28,11 @@ import (
// Volume plugins
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/csi"
"k8s.io/kubernetes/pkg/volume/fc"
"k8s.io/kubernetes/pkg/volume/flexvolume"
"k8s.io/kubernetes/pkg/volume/flocker"
"k8s.io/kubernetes/pkg/volume/glusterfs"
"k8s.io/kubernetes/pkg/volume/host_path"
"k8s.io/kubernetes/pkg/volume/iscsi"
"k8s.io/kubernetes/pkg/volume/local"
"k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/portworx"
"k8s.io/kubernetes/pkg/volume/quobyte"
"k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/scaleio"
"k8s.io/kubernetes/pkg/volume/storageos"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
utilfeature "k8s.io/apiserver/pkg/util/feature"
......@@ -56,12 +48,7 @@ import (
func ProbeAttachableVolumePlugins() []volume.VolumePlugin {
allPlugins := []volume.VolumePlugin{}
allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) {
allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...)
}
......@@ -79,12 +66,6 @@ func GetDynamicPluginProber(config kubectrlmgrconfig.VolumeConfiguration) volume
func ProbeExpandableVolumePlugins(config kubectrlmgrconfig.VolumeConfiguration) []volume.VolumePlugin {
allPlugins := []volume.VolumePlugin{}
allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...)
return allPlugins
}
......@@ -123,16 +104,7 @@ func ProbeControllerVolumePlugins(config kubectrlmgrconfig.VolumeConfiguration)
klog.Fatalf("Could not create NFS recycler pod from file %s: %+v", config.PersistentVolumeRecyclerConfiguration.PodTemplateFilePathNFS, err)
}
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins(nfsConfig)...)
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
// add rbd provisioner
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, quobyte.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, flocker.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, local.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
if utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...)
......
......@@ -21,27 +21,17 @@ import (
"k8s.io/utils/exec"
// Volume plugins
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/cephfs"
"k8s.io/kubernetes/pkg/volume/configmap"
"k8s.io/kubernetes/pkg/volume/csi"
"k8s.io/kubernetes/pkg/volume/downwardapi"
"k8s.io/kubernetes/pkg/volume/emptydir"
"k8s.io/kubernetes/pkg/volume/fc"
"k8s.io/kubernetes/pkg/volume/flexvolume"
"k8s.io/kubernetes/pkg/volume/flocker"
"k8s.io/kubernetes/pkg/volume/git_repo"
"k8s.io/kubernetes/pkg/volume/glusterfs"
"k8s.io/kubernetes/pkg/volume/host_path"
"k8s.io/kubernetes/pkg/volume/iscsi"
"k8s.io/kubernetes/pkg/volume/local"
"k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/portworx"
"k8s.io/kubernetes/pkg/volume/projected"
"k8s.io/kubernetes/pkg/volume/quobyte"
"k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/scaleio"
"k8s.io/kubernetes/pkg/volume/secret"
"k8s.io/kubernetes/pkg/volume/storageos"
// features check
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
......@@ -58,24 +48,14 @@ func ProbeVolumePlugins() []volume.VolumePlugin {
// Kubelet does not currently need to configure volume plugins.
// If/when it does, see kube-controller-manager/app/plugins.go for example of using volume.VolumeConfig
allPlugins = append(allPlugins, emptydir.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, git_repo.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, host_path.ProbeVolumePlugins(volume.VolumeConfig{})...)
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins(volume.VolumeConfig{})...)
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, quobyte.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, cephfs.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, downwardapi.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, flocker.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, configmap.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, projected.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, local.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
if utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) {
allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...)
}
......
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"cephfs.go",
"doc.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/cephfs",
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["cephfs_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/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"],
)
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- rootfs
- jsafrane
- saad-ali
reviewers:
- rootfs
- saad-ali
- jsafrane
- jingxu97
- msau42
- cofyc
/*
Copyright 2015 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 cephfs
import (
"os"
"path"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
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("cephTest")
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/cephfs")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.GetPluginName() != "kubernetes.io/cephfs" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) {
t.Errorf("Expected false")
}
if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{CephFS: &v1.CephFSVolumeSource{}}}}) {
t.Errorf("Expected true")
}
}
func TestPlugin(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("cephTest")
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/cephfs")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
spec := &v1.Volume{
Name: "vol1",
VolumeSource: v1.VolumeSource{
CephFS: &v1.CephFSVolumeSource{
Monitors: []string{"a", "b"},
User: "user",
SecretRef: nil,
SecretFile: "/etc/ceph/user.secret",
},
},
}
mounter, err := plug.(*cephfsPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), &mount.FakeMounter{}, "secrets")
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Errorf("Got a nil Mounter")
}
volumePath := mounter.GetPath()
volpath := path.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~cephfs/vol1")
if volumePath != volpath {
t.Errorf("Got unexpected path: %s", volumePath)
}
if err := mounter.SetUp(nil); err != nil {
t.Errorf("Expected success, got: %v", err)
}
if _, err := os.Stat(volumePath); err != nil {
if os.IsNotExist(err) {
t.Errorf("SetUp() failed, volume path not created: %s", volumePath)
} else {
t.Errorf("SetUp() failed: %v", err)
}
}
unmounter, err := plug.(*cephfsPlugin).newUnmounterInternal("vol1", types.UID("poduid"), &mount.FakeMounter{})
if err != nil {
t.Errorf("Failed to make a new Unmounter: %v", err)
}
if unmounter == nil {
t.Errorf("Got a nil Unmounter")
}
if err := unmounter.TearDown(); err != nil {
t.Errorf("Expected success, got: %v", err)
}
if _, err := os.Stat(volumePath); err == nil {
t.Errorf("TearDown() failed, volume path still exists: %s", volumePath)
} else if !os.IsNotExist(err) {
t.Errorf("TearDown() failed: %v", err)
}
}
func TestConstructVolumeSpec(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("cephTest")
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/cephfs")
if err != nil {
t.Errorf("can't find cephfs plugin by name")
}
cephfsSpec, err := plug.(*cephfsPlugin).ConstructVolumeSpec("cephfsVolume", "/cephfsVolume/")
if cephfsSpec.Name() != "cephfsVolume" {
t.Errorf("Get wrong cephfs spec name, got: %s", cephfsSpec.Name())
}
}
type testcase struct {
name string
defaultNs string
spec *volume.Spec
// Expected return of the test
expectedName string
expectedNs string
expectedError error
}
func TestGetSecretNameAndNamespaceForPV(t *testing.T) {
tests := []testcase{
{
name: "persistent volume source",
defaultNs: "default",
spec: &volume.Spec{
PersistentVolume: &v1.PersistentVolume{
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
CephFS: &v1.CephFSPersistentVolumeSource{
Monitors: []string{"a", "b"},
User: "user",
SecretRef: &v1.SecretReference{
Name: "name",
Namespace: "ns",
},
SecretFile: "/etc/ceph/user.secret",
},
},
},
},
},
expectedName: "name",
expectedNs: "ns",
expectedError: nil,
},
{
name: "persistent volume source without namespace",
defaultNs: "default",
spec: &volume.Spec{
PersistentVolume: &v1.PersistentVolume{
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
CephFS: &v1.CephFSPersistentVolumeSource{
Monitors: []string{"a", "b"},
User: "user",
SecretRef: &v1.SecretReference{
Name: "name",
},
SecretFile: "/etc/ceph/user.secret",
},
},
},
},
},
expectedName: "name",
expectedNs: "default",
expectedError: nil,
},
{
name: "pod volume source",
defaultNs: "default",
spec: &volume.Spec{
Volume: &v1.Volume{
VolumeSource: v1.VolumeSource{
CephFS: &v1.CephFSVolumeSource{
Monitors: []string{"a", "b"},
User: "user",
SecretRef: &v1.LocalObjectReference{
Name: "name",
},
SecretFile: "/etc/ceph/user.secret",
},
},
},
},
expectedName: "name",
expectedNs: "default",
expectedError: nil,
},
}
for _, testcase := range tests {
resultName, resultNs, err := getSecretNameAndNamespace(testcase.spec, testcase.defaultNs)
if err != testcase.expectedError || resultName != testcase.expectedName || resultNs != testcase.expectedNs {
t.Errorf("%s failed: expected err=%v ns=%q name=%q, got %v/%q/%q", testcase.name, testcase.expectedError, testcase.expectedNs, testcase.expectedName,
err, resultNs, resultName)
}
}
}
func TestGetAccessModes(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("cephfs_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/cephfs")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
modes := plug.GetAccessModes()
for _, v := range modes {
if !volumetest.ContainsAccessMode(modes, v) {
t.Errorf("Expected AccessModeTypes: %s", v)
}
}
}
/*
Copyright 2015 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 cephfs contains the internal representation of Ceph file system
// (CephFS) volumes.
package cephfs // import "k8s.io/kubernetes/pkg/volume/cephfs"
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"attacher.go",
"disk_manager.go",
"doc.go",
"fc.go",
"fc_util.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/fc",
deps = [
"//pkg/features:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//pkg/volume/util/volumepathhandler:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"fc_test.go",
"fc_util_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/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"],
)
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- rootfs
- saad-ali
reviewers:
- saad-ali
- jsafrane
- rootfs
- jingxu97
- msau42
- mtanino
/*
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 fc
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
)
type fcAttacher struct {
host volume.VolumeHost
manager diskManager
}
var _ volume.Attacher = &fcAttacher{}
var _ volume.DeviceMounter = &fcAttacher{}
var _ volume.AttachableVolumePlugin = &fcPlugin{}
var _ volume.DeviceMountableVolumePlugin = &fcPlugin{}
func (plugin *fcPlugin) NewAttacher() (volume.Attacher, error) {
return &fcAttacher{
host: plugin.host,
manager: &fcUtil{},
}, nil
}
func (plugin *fcPlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
return plugin.NewAttacher()
}
func (plugin *fcPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
mounter := plugin.host.GetMounter(plugin.GetPluginName())
return mounter.GetMountRefs(deviceMountPath)
}
func (attacher *fcAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
return "", nil
}
func (attacher *fcAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
volumesAttachedCheck := make(map[*volume.Spec]bool)
for _, spec := range specs {
volumesAttachedCheck[spec] = true
}
return volumesAttachedCheck, nil
}
func (attacher *fcAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) {
mounter, err := volumeSpecToMounter(spec, attacher.host)
if err != nil {
klog.Warningf("failed to get fc mounter: %v", err)
return "", err
}
return attacher.manager.AttachDisk(*mounter)
}
func (attacher *fcAttacher) GetDeviceMountPath(
spec *volume.Spec) (string, error) {
mounter, err := volumeSpecToMounter(spec, attacher.host)
if err != nil {
klog.Warningf("failed to get fc mounter: %v", err)
return "", err
}
return attacher.manager.MakeGlobalPDName(*mounter.fcDisk), nil
}
func (attacher *fcAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
mounter := attacher.host.GetMounter(fcPluginName)
notMnt, err := 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
}
}
volumeSource, readOnly, err := getVolumeSource(spec)
if err != nil {
return err
}
options := []string{}
if readOnly {
options = append(options, "ro")
}
if notMnt {
diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Exec: attacher.host.GetExec(fcPluginName)}
mountOptions := volumeutil.MountOptionFromSpec(spec, options...)
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
if err != nil {
os.Remove(deviceMountPath)
return err
}
}
return nil
}
type fcDetacher struct {
mounter mount.Interface
manager diskManager
}
var _ volume.Detacher = &fcDetacher{}
var _ volume.DeviceUnmounter = &fcDetacher{}
func (plugin *fcPlugin) NewDetacher() (volume.Detacher, error) {
return &fcDetacher{
mounter: plugin.host.GetMounter(plugin.GetPluginName()),
manager: &fcUtil{},
}, nil
}
func (plugin *fcPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
return plugin.NewDetacher()
}
func (detacher *fcDetacher) Detach(volumeName string, nodeName types.NodeName) error {
return nil
}
func (detacher *fcDetacher) UnmountDevice(deviceMountPath string) error {
// Specify device name for DetachDisk later
devName, _, err := mount.GetDeviceNameFromMount(detacher.mounter, deviceMountPath)
if err != nil {
klog.Errorf("fc: failed to get device from mnt: %s\nError: %v", deviceMountPath, err)
return err
}
// Unmount for deviceMountPath(=globalPDPath)
err = mount.CleanupMountPoint(deviceMountPath, detacher.mounter, false)
if err != nil {
return fmt.Errorf("fc: failed to unmount: %s\nError: %v", deviceMountPath, err)
}
unMounter := volumeSpecToUnmounter(detacher.mounter)
err = detacher.manager.DetachDisk(*unMounter, devName)
if err != nil {
return fmt.Errorf("fc: failed to detach disk: %s\nError: %v", devName, err)
}
klog.V(4).Infof("fc: successfully detached disk: %s", devName)
return nil
}
func (plugin *fcPlugin) CanAttach(spec *volume.Spec) bool {
return true
}
func volumeSpecToMounter(spec *volume.Spec, host volume.VolumeHost) (*fcDiskMounter, error) {
fc, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
var lun string
var wwids []string
if fc.Lun != nil && len(fc.TargetWWNs) != 0 {
lun = strconv.Itoa(int(*fc.Lun))
} else if len(fc.WWIDs) != 0 {
for _, wwid := range fc.WWIDs {
wwids = append(wwids, strings.Replace(wwid, " ", "_", -1))
}
} else {
return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mounter")
}
fcDisk := &fcDisk{
plugin: &fcPlugin{
host: host,
},
wwns: fc.TargetWWNs,
lun: lun,
wwids: wwids,
io: &osIOHandler{},
}
// TODO: remove feature gate check after no longer needed
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
volumeMode, err := volumeutil.GetVolumeMode(spec)
if err != nil {
return nil, err
}
klog.V(5).Infof("fc: volumeSpecToMounter volumeMode %s", volumeMode)
return &fcDiskMounter{
fcDisk: fcDisk,
fsType: fc.FSType,
volumeMode: volumeMode,
readOnly: readOnly,
mounter: volumeutil.NewSafeFormatAndMountFromHost(fcPluginName, host),
deviceUtil: volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()),
}, nil
}
return &fcDiskMounter{
fcDisk: fcDisk,
fsType: fc.FSType,
readOnly: readOnly,
mounter: volumeutil.NewSafeFormatAndMountFromHost(fcPluginName, host),
deviceUtil: volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()),
}, nil
}
func volumeSpecToUnmounter(mounter mount.Interface) *fcDiskUnmounter {
return &fcDiskUnmounter{
fcDisk: &fcDisk{
io: &osIOHandler{},
},
mounter: mounter,
deviceUtil: volumeutil.NewDeviceHandler(volumeutil.NewIOHandler()),
}
}
/*
Copyright 2015 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 fc
import (
"os"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util"
)
// Abstract interface to disk operations.
type diskManager interface {
MakeGlobalPDName(disk fcDisk) string
MakeGlobalVDPDName(disk fcDisk) string
// Attaches the disk to the kubelet's host machine.
AttachDisk(b fcDiskMounter) (string, error)
// Detaches the disk from the kubelet's host machine.
DetachDisk(disk fcDiskUnmounter, devicePath string) error
// Detaches the block disk from the kubelet's host machine.
DetachBlockFCDisk(disk fcDiskUnmapper, mntPath, devicePath string) error
}
// utility to mount a disk based filesystem
func diskSetUp(manager diskManager, b fcDiskMounter, volPath string, mounter mount.Interface, fsGroup *int64) error {
globalPDPath := manager.MakeGlobalPDName(*b.fcDisk)
noMnt, err := mounter.IsLikelyNotMountPoint(volPath)
if err != nil && !os.IsNotExist(err) {
klog.Errorf("cannot validate mountpoint: %s", volPath)
return err
}
if !noMnt {
return nil
}
if err := os.MkdirAll(volPath, 0750); err != nil {
klog.Errorf("failed to mkdir:%s", volPath)
return err
}
// Perform a bind mount to the full path to allow duplicate mounts of the same disk.
options := []string{"bind"}
if b.readOnly {
options = append(options, "ro")
}
mountOptions := util.JoinMountOptions(options, b.mountOptions)
err = mounter.Mount(globalPDPath, volPath, "", mountOptions)
if err != nil {
klog.Errorf("Failed to bind mount: source:%s, target:%s, err:%v", globalPDPath, volPath, err)
noMnt, mntErr := b.mounter.IsLikelyNotMountPoint(volPath)
if mntErr != nil {
klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
return err
}
if !noMnt {
if mntErr = b.mounter.Unmount(volPath); mntErr != nil {
klog.Errorf("Failed to unmount: %v", mntErr)
return err
}
noMnt, mntErr = b.mounter.IsLikelyNotMountPoint(volPath)
if mntErr != nil {
klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
return err
}
if !noMnt {
// will most likely retry on next sync loop.
klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", volPath)
return err
}
}
os.Remove(volPath)
return err
}
if !b.readOnly {
volume.SetVolumeOwnership(&b, fsGroup)
}
return nil
}
/*
Copyright 2015 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 fc contains the internal representation of
// Fibre Channel (fc) volumes.
package fc // import "k8s.io/kubernetes/pkg/volume/fc"
/*
Copyright 2015 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 fc
import (
"os"
"reflect"
"testing"
"time"
"k8s.io/kubernetes/pkg/volume/util"
)
type fakeFileInfo struct {
name string
}
func (fi *fakeFileInfo) Name() string {
return fi.name
}
func (fi *fakeFileInfo) Size() int64 {
return 0
}
func (fi *fakeFileInfo) Mode() os.FileMode {
return 777
}
func (fi *fakeFileInfo) ModTime() time.Time {
return time.Now()
}
func (fi *fakeFileInfo) IsDir() bool {
return false
}
func (fi *fakeFileInfo) Sys() interface{} {
return nil
}
type fakeIOHandler struct{}
func (handler *fakeIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) {
switch dirname {
case "/dev/disk/by-path/":
f := &fakeFileInfo{
name: "pci-0000:41:00.0-fc-0x500a0981891b8dc5-lun-0",
}
return []os.FileInfo{f}, nil
case "/sys/block/":
f := &fakeFileInfo{
name: "dm-1",
}
return []os.FileInfo{f}, nil
case "/dev/disk/by-id/":
f := &fakeFileInfo{
name: "scsi-3600508b400105e210000900000490000",
}
return []os.FileInfo{f}, nil
}
return nil, nil
}
func (handler *fakeIOHandler) Lstat(name string) (os.FileInfo, error) {
return nil, nil
}
func (handler *fakeIOHandler) EvalSymlinks(path string) (string, error) {
return "/dev/sda", nil
}
func (handler *fakeIOHandler) WriteFile(filename string, data []byte, perm os.FileMode) error {
return nil
}
func TestSearchDisk(t *testing.T) {
fakeMounter := fcDiskMounter{
fcDisk: &fcDisk{
wwns: []string{"500a0981891b8dc5"},
lun: "0",
io: &fakeIOHandler{},
},
deviceUtil: util.NewDeviceHandler(util.NewIOHandler()),
}
devicePath, error := searchDisk(fakeMounter)
// if no disk matches input wwn and lun, exit
if devicePath == "" || error != nil {
t.Errorf("no fc disk found")
}
}
func TestSearchDiskWWID(t *testing.T) {
fakeMounter := fcDiskMounter{
fcDisk: &fcDisk{
wwids: []string{"3600508b400105e210000900000490000"},
io: &fakeIOHandler{},
},
deviceUtil: util.NewDeviceHandler(util.NewIOHandler()),
}
devicePath, error := searchDisk(fakeMounter)
// if no disk matches input wwid, exit
if devicePath == "" || error != nil {
t.Errorf("no fc disk found")
}
}
func TestParsePDName(t *testing.T) {
tests := []struct {
name string
path string
wwns []string
lun int32
wwids []string
expectError bool
}{
{
name: "single WWID",
path: "/var/lib/kubelet/plugins/kubernetes.io/fc/60050763008084e6e0000000000001ae",
wwids: []string{"60050763008084e6e0000000000001ae"},
},
{
name: "multiple WWID",
path: "/var/lib/kubelet/plugins/kubernetes.io/fc/60050763008084e6e0000000000001ae-60050763008084e6e0000000000001af",
wwids: []string{"60050763008084e6e0000000000001ae", "60050763008084e6e0000000000001af"},
},
{
name: "single WWN",
path: "/var/lib/kubelet/plugins/kubernetes.io/fc/50050768030539b6-lun-0",
wwns: []string{"50050768030539b6"},
lun: 0,
},
{
name: "multiple WWNs",
path: "/var/lib/kubelet/plugins/kubernetes.io/fc/50050768030539b6-50050768030539b7-lun-0",
wwns: []string{"50050768030539b6", "50050768030539b7"},
lun: 0,
},
{
name: "no WWNs",
path: "/var/lib/kubelet/plugins/kubernetes.io/fc/lun-0",
expectError: true,
},
{
name: "invalid lun",
path: "/var/lib/kubelet/plugins/kubernetes.io/fc/50050768030539b6-lun-x",
expectError: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
wwns, lun, wwids, err := parsePDName(test.path)
if test.expectError && err == nil {
t.Errorf("expected error but got none")
}
if !test.expectError && err != nil {
t.Errorf("got unexpected error: %s", err)
}
if !reflect.DeepEqual(wwns, test.wwns) {
t.Errorf("expected WWNs %+v, got %+v", test.wwns, wwns)
}
if lun != test.lun {
t.Errorf("expected lun %d, got %d", test.lun, lun)
}
if !reflect.DeepEqual(wwids, test.wwids) {
t.Errorf("expected WWIDs %+v, got %+v", test.wwids, wwids)
}
})
}
}
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",
"flocker.go",
"flocker_util.go",
"flocker_volume.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/flocker",
deps = [
"//pkg/util/env:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/rand:go_default_library",
"//staging/src/k8s.io/cloud-provider/volume/helpers:go_default_library",
"//vendor/github.com/clusterhq/flocker-go:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"flocker_test.go",
"flocker_util_test.go",
"flocker_volume_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
"//vendor/github.com/clusterhq/flocker-go:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- rootfs
- saad-ali
reviewers:
- agonzalezro
- simonswine
- saad-ali
- jsafrane
- rootfs
- jingxu97
- msau42
/*
Copyright 2015 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 flocker contains the internal representation of Flocker volumes
package flocker // import "k8s.io/kubernetes/pkg/volume/flocker"
/*
Copyright 2015 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 flocker
import (
"fmt"
"os"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
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"
flockerapi "github.com/clusterhq/flocker-go"
"github.com/stretchr/testify/assert"
)
const pluginName = "kubernetes.io/flocker"
const datasetOneID = "11111111-1111-1111-1111-111111111100"
const nodeOneID = "11111111-1111-1111-1111-111111111111"
const nodeTwoID = "22222222-2222-2222-2222-222222222222"
var _ flockerapi.Clientable = &fakeFlockerClient{}
type fakeFlockerClient struct {
DatasetID string
Primary string
Deleted bool
Metadata map[string]string
Nodes []flockerapi.NodeState
Error error
}
func newFakeFlockerClient() *fakeFlockerClient {
return &fakeFlockerClient{
DatasetID: datasetOneID,
Primary: nodeOneID,
Deleted: false,
Metadata: map[string]string{"Name": "dataset-one"},
Nodes: []flockerapi.NodeState{
{
Host: "1.2.3.4",
UUID: nodeOneID,
},
{
Host: "4.5.6.7",
UUID: nodeTwoID,
},
},
}
}
func (c *fakeFlockerClient) CreateDataset(options *flockerapi.CreateDatasetOptions) (*flockerapi.DatasetState, error) {
if c.Error != nil {
return nil, c.Error
}
return &flockerapi.DatasetState{
DatasetID: c.DatasetID,
}, nil
}
func (c *fakeFlockerClient) DeleteDataset(datasetID string) error {
c.DatasetID = datasetID
c.Deleted = true
return nil
}
func (c *fakeFlockerClient) GetDatasetState(datasetID string) (*flockerapi.DatasetState, error) {
return &flockerapi.DatasetState{}, nil
}
func (c *fakeFlockerClient) GetDatasetID(metaName string) (datasetID string, err error) {
if val, ok := c.Metadata["Name"]; !ok {
return val, nil
}
return "", fmt.Errorf("No dataset with metadata X found")
}
func (c *fakeFlockerClient) GetPrimaryUUID() (primaryUUID string, err error) {
return
}
func (c *fakeFlockerClient) ListNodes() (nodes []flockerapi.NodeState, err error) {
return c.Nodes, nil
}
func (c *fakeFlockerClient) UpdatePrimaryForDataset(primaryUUID, datasetID string) (*flockerapi.DatasetState, error) {
return &flockerapi.DatasetState{}, nil
}
type fakeFlockerUtil struct {
}
func (fake *fakeFlockerUtil) CreateVolume(c *flockerVolumeProvisioner) (datasetUUID string, volumeSizeGB int, labels map[string]string, err error) {
labels = make(map[string]string)
labels["fakeflockerutil"] = "yes"
return "test-flocker-volume-uuid", 3, labels, nil
}
func (fake *fakeFlockerUtil) DeleteVolume(cd *flockerVolumeDeleter) error {
if cd.datasetUUID != "test-flocker-volume-uuid" {
return fmt.Errorf("Deleter got unexpected datasetUUID: %s", cd.datasetUUID)
}
return nil
}
func newInitializedVolumePlugMgr(t *testing.T) (*volume.VolumePluginMgr, string) {
plugMgr := &volume.VolumePluginMgr{}
dir, err := utiltesting.MkTmpdir("flocker")
assert.NoError(t, err)
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(dir, nil, nil))
return plugMgr, dir
}
func TestPlugin(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("flockerTest")
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/flocker")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
spec := &v1.Volume{
Name: "vol1",
VolumeSource: v1.VolumeSource{
Flocker: &v1.FlockerVolumeSource{
DatasetUUID: "uuid1",
},
},
}
fakeManager := &fakeFlockerUtil{}
fakeMounter := &mount.FakeMounter{}
mounter, err := plug.(*flockerPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), fakeManager, fakeMounter)
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Errorf("Got a nil Mounter")
}
}
func TestGetByName(t *testing.T) {
assert := assert.New(t)
plugMgr, dir := newInitializedVolumePlugMgr(t)
defer os.RemoveAll(dir)
plug, err := plugMgr.FindPluginByName(pluginName)
assert.NotNil(plug, "Can't find the plugin by name")
assert.NoError(err)
}
func TestCanSupport(t *testing.T) {
assert := assert.New(t)
plugMgr, dir := newInitializedVolumePlugMgr(t)
defer os.RemoveAll(dir)
plug, err := plugMgr.FindPluginByName(pluginName)
assert.NoError(err)
specs := map[*volume.Spec]bool{
{
Volume: &v1.Volume{
VolumeSource: v1.VolumeSource{
Flocker: &v1.FlockerVolumeSource{},
},
},
}: true,
{
PersistentVolume: &v1.PersistentVolume{
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
Flocker: &v1.FlockerVolumeSource{},
},
},
},
}: true,
{
Volume: &v1.Volume{
VolumeSource: v1.VolumeSource{},
},
}: false,
}
for spec, expected := range specs {
actual := plug.CanSupport(spec)
assert.Equal(expected, actual)
}
}
func TestGetFlockerVolumeSource(t *testing.T) {
assert := assert.New(t)
p := flockerPlugin{}
spec := &volume.Spec{
Volume: &v1.Volume{
VolumeSource: v1.VolumeSource{
Flocker: &v1.FlockerVolumeSource{},
},
},
}
vs, ro := p.getFlockerVolumeSource(spec)
assert.False(ro)
assert.Equal(spec.Volume.Flocker, vs)
spec = &volume.Spec{
PersistentVolume: &v1.PersistentVolume{
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
Flocker: &v1.FlockerVolumeSource{},
},
},
},
}
vs, ro = p.getFlockerVolumeSource(spec)
assert.False(ro)
assert.Equal(spec.PersistentVolume.Spec.Flocker, vs)
}
func TestNewMounterDatasetName(t *testing.T) {
assert := assert.New(t)
plugMgr, dir := newInitializedVolumePlugMgr(t)
defer os.RemoveAll(dir)
plug, err := plugMgr.FindPluginByName(pluginName)
assert.NoError(err)
spec := &volume.Spec{
Volume: &v1.Volume{
VolumeSource: v1.VolumeSource{
Flocker: &v1.FlockerVolumeSource{
DatasetName: "something",
},
},
},
}
_, err = plug.NewMounter(spec, &v1.Pod{}, volume.VolumeOptions{})
assert.NoError(err)
}
func TestNewMounterDatasetUUID(t *testing.T) {
assert := assert.New(t)
plugMgr, dir := newInitializedVolumePlugMgr(t)
defer os.RemoveAll(dir)
plug, err := plugMgr.FindPluginByName(pluginName)
assert.NoError(err)
spec := &volume.Spec{
Volume: &v1.Volume{
VolumeSource: v1.VolumeSource{
Flocker: &v1.FlockerVolumeSource{
DatasetUUID: "uuid1",
},
},
},
}
mounter, err := plug.NewMounter(spec, &v1.Pod{}, volume.VolumeOptions{})
assert.NoError(err)
assert.NotNil(mounter, "got a nil mounter")
}
func TestNewUnmounter(t *testing.T) {
t.Skip("broken")
assert := assert.New(t)
p := flockerPlugin{}
unmounter, err := p.NewUnmounter("", types.UID(""))
assert.Nil(unmounter)
assert.NoError(err)
}
func TestIsReadOnly(t *testing.T) {
b := &flockerVolumeMounter{readOnly: true}
assert.True(t, b.GetAttributes().ReadOnly)
}
type mockFlockerClient struct {
datasetID, primaryUUID, path string
datasetState *flockerapi.DatasetState
}
func newMockFlockerClient(mockDatasetID, mockPrimaryUUID, mockPath string) *mockFlockerClient {
return &mockFlockerClient{
datasetID: mockDatasetID,
primaryUUID: mockPrimaryUUID,
path: mockPath,
datasetState: &flockerapi.DatasetState{
Path: mockPath,
DatasetID: mockDatasetID,
Primary: mockPrimaryUUID,
},
}
}
func (m mockFlockerClient) CreateDataset(metaName string) (*flockerapi.DatasetState, error) {
return m.datasetState, nil
}
func (m mockFlockerClient) GetDatasetState(datasetID string) (*flockerapi.DatasetState, error) {
return m.datasetState, nil
}
func (m mockFlockerClient) GetDatasetID(metaName string) (string, error) {
return m.datasetID, nil
}
func (m mockFlockerClient) GetPrimaryUUID() (string, error) {
return m.primaryUUID, nil
}
func (m mockFlockerClient) UpdatePrimaryForDataset(primaryUUID, datasetID string) (*flockerapi.DatasetState, error) {
return m.datasetState, nil
}
/*
TODO: re-enable after refactor
func TestSetUpAtInternal(t *testing.T) {
const dir = "dir"
mockPath := "expected-to-be-set-properly" // package var
expectedPath := mockPath
assert := assert.New(t)
plugMgr, rootDir := newInitializedVolumePlugMgr(t)
if rootDir != "" {
defer os.RemoveAll(rootDir)
}
plug, err := plugMgr.FindPluginByName(flockerPluginName)
assert.NoError(err)
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
b := flockerVolumeMounter{flockerVolume: &flockerVolume{pod: pod, plugin: plug.(*flockerPlugin)}}
b.client = newMockFlockerClient("dataset-id", "primary-uid", mockPath)
assert.NoError(b.SetUpAt(dir, nil))
assert.Equal(expectedPath, b.flocker.path)
}
*/
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package flocker
import (
"fmt"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/rand"
volumehelpers "k8s.io/cloud-provider/volume/helpers"
flockerapi "github.com/clusterhq/flocker-go"
"k8s.io/klog"
)
type flockerUtil struct{}
func (util *flockerUtil) DeleteVolume(d *flockerVolumeDeleter) error {
var err error
if d.flockerClient == nil {
d.flockerClient, err = d.plugin.newFlockerClient("")
if err != nil {
return err
}
}
datasetUUID, err := d.GetDatasetUUID()
if err != nil {
return err
}
return d.flockerClient.DeleteDataset(datasetUUID)
}
func (util *flockerUtil) CreateVolume(c *flockerVolumeProvisioner) (datasetUUID string, volumeSizeGiB int, labels map[string]string, err error) {
if c.flockerClient == nil {
c.flockerClient, err = c.plugin.newFlockerClient("")
if err != nil {
return
}
}
nodes, err := c.flockerClient.ListNodes()
if err != nil {
return
}
if len(nodes) < 1 {
err = fmt.Errorf("no nodes found inside the flocker cluster to provision a dataset")
return
}
// select random node
node := nodes[rand.Intn(len(nodes))]
klog.V(2).Infof("selected flocker node with UUID '%s' to provision dataset", node.UUID)
capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
requestBytes := capacity.Value()
volumeSizeGiB, err = volumehelpers.RoundUpToGiBInt(capacity)
if err != nil {
return
}
createOptions := &flockerapi.CreateDatasetOptions{
MaximumSize: requestBytes,
Metadata: map[string]string{
"type": "k8s-dynamic-prov",
"pvc": c.options.PVC.Name,
},
Primary: node.UUID,
}
datasetState, err := c.flockerClient.CreateDataset(createOptions)
if err != nil {
return
}
datasetUUID = datasetState.DatasetID
klog.V(2).Infof("successfully created Flocker dataset with UUID '%s'", datasetUUID)
return
}
/*
Copyright 2015 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 flocker
import (
"fmt"
"os"
"testing"
"k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
"github.com/stretchr/testify/assert"
)
func TestFlockerUtil_CreateVolume(t *testing.T) {
assert := assert.New(t)
// test CreateVolume happy path
pvc := volumetest.CreateTestPVC("3Gi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce})
options := volume.VolumeOptions{
PVC: pvc,
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
}
fakeFlockerClient := newFakeFlockerClient()
dir, p := newTestableProvisioner(assert, options)
provisioner := p.(*flockerVolumeProvisioner)
defer os.RemoveAll(dir)
provisioner.flockerClient = fakeFlockerClient
flockerUtil := &flockerUtil{}
datasetID, size, _, err := flockerUtil.CreateVolume(provisioner)
assert.NoError(err)
assert.Equal(datasetOneID, datasetID)
assert.Equal(3, size)
// test error during CreateVolume
fakeFlockerClient.Error = fmt.Errorf("Do not feel like provisioning")
_, _, _, err = flockerUtil.CreateVolume(provisioner)
assert.Equal(fakeFlockerClient.Error.Error(), err.Error())
}
/*
Copyright 2015 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 flocker
import (
"fmt"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util"
)
type volumeManager interface {
// Creates a volume
CreateVolume(provisioner *flockerVolumeProvisioner) (datasetUUID string, volumeSizeGB int, labels map[string]string, err error)
// Deletes a volume
DeleteVolume(deleter *flockerVolumeDeleter) error
}
type flockerVolumeDeleter struct {
*flockerVolume
}
var _ volume.Deleter = &flockerVolumeDeleter{}
func (b *flockerVolumeDeleter) GetPath() string {
return getPath(b.podUID, b.volName, b.plugin.host)
}
func (b *flockerVolumeDeleter) Delete() error {
return b.manager.DeleteVolume(b)
}
type flockerVolumeProvisioner struct {
*flockerVolume
options volume.VolumeOptions
}
var _ volume.Provisioner = &flockerVolumeProvisioner{}
func (c *flockerVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
if !util.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())
}
if len(c.options.Parameters) > 0 {
return nil, fmt.Errorf("Provisioning failed: Specified at least one unsupported parameter")
}
if c.options.PVC.Spec.Selector != nil {
return nil, fmt.Errorf("Provisioning failed: Specified unsupported selector")
}
if util.CheckPersistentVolumeClaimModeBlock(c.options.PVC) {
return nil, fmt.Errorf("%s does not support block volume provisioning", c.plugin.GetPluginName())
}
datasetUUID, sizeGB, labels, err := c.manager.CreateVolume(c)
if err != nil {
return nil, err
}
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: c.options.PVName,
Labels: map[string]string{},
Annotations: map[string]string{
util.VolumeDynamicallyCreatedByKey: "flocker-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", sizeGB)),
},
PersistentVolumeSource: v1.PersistentVolumeSource{
Flocker: &v1.FlockerVolumeSource{
DatasetUUID: datasetUUID,
},
},
},
}
if len(c.options.PVC.Spec.AccessModes) == 0 {
pv.Spec.AccessModes = c.plugin.GetAccessModes()
}
if len(labels) != 0 {
if pv.Labels == nil {
pv.Labels = make(map[string]string)
}
for k, v := range labels {
pv.Labels[k] = v
}
}
return pv, nil
}
/*
Copyright 2015 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 flocker
import (
"fmt"
"os"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
"github.com/stretchr/testify/assert"
)
func newTestableProvisioner(assert *assert.Assertions, options volume.VolumeOptions) (string, volume.Provisioner) {
tmpDir, err := utiltesting.MkTmpdir("flockervolumeTest")
assert.NoError(err, fmt.Sprintf("can't make a temp dir: %v", err))
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPluginByName(pluginName)
assert.NoError(err, "Can't find the plugin by name")
provisioner, err := plug.(*flockerPlugin).newProvisionerInternal(options, &fakeFlockerUtil{})
assert.NoError(err, fmt.Sprintf("Can't create new provisioner:%v", err))
return tmpDir, provisioner
}
func TestProvision(t *testing.T) {
assert := assert.New(t)
pvc := volumetest.CreateTestPVC("3Gi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce})
options := volume.VolumeOptions{
PVC: pvc,
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
}
dir, provisioner := newTestableProvisioner(assert, options)
defer os.RemoveAll(dir)
persistentSpec, err := provisioner.Provision(nil, nil)
assert.NoError(err, "Provision() failed: ", err)
cap := persistentSpec.Spec.Capacity[v1.ResourceStorage]
assert.Equal(int64(3*1024*1024*1024), cap.Value())
assert.Equal(
"test-flocker-volume-uuid",
persistentSpec.Spec.PersistentVolumeSource.Flocker.DatasetUUID,
)
assert.Equal(
map[string]string{"fakeflockerutil": "yes"},
persistentSpec.Labels,
)
// parameters are not supported
options = volume.VolumeOptions{
PVC: pvc,
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
Parameters: map[string]string{
"not-supported-params": "test123",
},
}
dir, provisioner = newTestableProvisioner(assert, options)
defer os.RemoveAll(dir)
persistentSpec, err = provisioner.Provision(nil, nil)
assert.Error(err, "Provision() did not fail with Parameters specified")
// selectors are not supported
pvc.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{"key": "value"}}
options = volume.VolumeOptions{
PVC: pvc,
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
}
dir, provisioner = newTestableProvisioner(assert, options)
defer os.RemoveAll(dir)
persistentSpec, err = provisioner.Provision(nil, nil)
assert.Error(err, "Provision() did not fail with Selector specified")
}
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",
"git_repo.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/git_repo",
deps = [
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["git_repo_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/volume:go_default_library",
"//pkg/volume/emptydir:go_default_library",
"//pkg/volume/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
"//vendor/k8s.io/utils/exec/testing:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- thockin
- saad-ali
reviewers:
- saad-ali
- jsafrane
- rootfs
- jingxu97
- msau42
/*
Copyright 2015 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 git_repo contains the internal representation of git repo volumes.
package git_repo // import "k8s.io/kubernetes/pkg/volume/git_repo"
/*
Copyright 2014 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 git_repo
import (
"fmt"
"io/ioutil"
"path"
"path/filepath"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
"k8s.io/utils/exec"
utilstrings "k8s.io/utils/strings"
)
// This is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{&gitRepoPlugin{nil}}
}
type gitRepoPlugin struct {
host volume.VolumeHost
}
var _ volume.VolumePlugin = &gitRepoPlugin{}
func wrappedVolumeSpec() volume.Spec {
return volume.Spec{
Volume: &v1.Volume{VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}},
}
}
const (
gitRepoPluginName = "kubernetes.io/git-repo"
)
func (plugin *gitRepoPlugin) Init(host volume.VolumeHost) error {
plugin.host = host
return nil
}
func (plugin *gitRepoPlugin) GetPluginName() string {
return gitRepoPluginName
}
func (plugin *gitRepoPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _ := getVolumeSource(spec)
if volumeSource == nil {
return "", fmt.Errorf("Spec does not reference a Git repo volume type")
}
return fmt.Sprintf(
"%v:%v:%v",
volumeSource.Repository,
volumeSource.Revision,
volumeSource.Directory), nil
}
func (plugin *gitRepoPlugin) CanSupport(spec *volume.Spec) bool {
return spec.Volume != nil && spec.Volume.GitRepo != nil
}
func (plugin *gitRepoPlugin) IsMigratedToCSI() bool {
return false
}
func (plugin *gitRepoPlugin) RequiresRemount() bool {
return false
}
func (plugin *gitRepoPlugin) SupportsMountOption() bool {
return false
}
func (plugin *gitRepoPlugin) SupportsBulkVolumeVerification() bool {
return false
}
func (plugin *gitRepoPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
if err := validateVolume(spec.Volume.GitRepo); err != nil {
return nil, err
}
return &gitRepoVolumeMounter{
gitRepoVolume: &gitRepoVolume{
volName: spec.Name(),
podUID: pod.UID,
plugin: plugin,
},
pod: *pod,
source: spec.Volume.GitRepo.Repository,
revision: spec.Volume.GitRepo.Revision,
target: spec.Volume.GitRepo.Directory,
exec: exec.New(),
opts: opts,
}, nil
}
func (plugin *gitRepoPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
return &gitRepoVolumeUnmounter{
&gitRepoVolume{
volName: volName,
podUID: podUID,
plugin: plugin,
},
}, nil
}
func (plugin *gitRepoPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
gitVolume := &v1.Volume{
Name: volumeName,
VolumeSource: v1.VolumeSource{
GitRepo: &v1.GitRepoVolumeSource{},
},
}
return volume.NewSpecFromVolume(gitVolume), nil
}
// gitRepo volumes are directories which are pre-filled from a git repository.
// These do not persist beyond the lifetime of a pod.
type gitRepoVolume struct {
volName string
podUID types.UID
plugin *gitRepoPlugin
volume.MetricsNil
}
var _ volume.Volume = &gitRepoVolume{}
func (gr *gitRepoVolume) GetPath() string {
name := gitRepoPluginName
return gr.plugin.host.GetPodVolumeDir(gr.podUID, utilstrings.EscapeQualifiedName(name), gr.volName)
}
// gitRepoVolumeMounter builds git repo volumes.
type gitRepoVolumeMounter struct {
*gitRepoVolume
pod v1.Pod
source string
revision string
target string
exec exec.Interface
opts volume.VolumeOptions
}
var _ volume.Mounter = &gitRepoVolumeMounter{}
func (b *gitRepoVolumeMounter) GetAttributes() volume.Attributes {
return volume.Attributes{
ReadOnly: false,
Managed: true,
SupportsSELinux: true, // xattr change should be okay, TODO: double check
}
}
// 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 *gitRepoVolumeMounter) CanMount() error {
return nil
}
// SetUp creates new directory and clones a git repo.
func (b *gitRepoVolumeMounter) SetUp(fsGroup *int64) error {
return b.SetUpAt(b.GetPath(), fsGroup)
}
// SetUpAt creates new directory and clones a git repo.
func (b *gitRepoVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
if volumeutil.IsReady(b.getMetaDir()) {
return nil
}
// Wrap EmptyDir, let it do the setup.
wrapped, err := b.plugin.host.NewWrapperMounter(b.volName, wrappedVolumeSpec(), &b.pod, b.opts)
if err != nil {
return err
}
if err := wrapped.SetUpAt(dir, fsGroup); err != nil {
return err
}
args := []string{"clone", "--", b.source}
if len(b.target) != 0 {
args = append(args, b.target)
}
if output, err := b.execCommand("git", args, dir); err != nil {
return fmt.Errorf("failed to exec 'git %s': %s: %v",
strings.Join(args, " "), output, err)
}
files, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
if len(b.revision) == 0 {
// Done!
volumeutil.SetReady(b.getMetaDir())
return nil
}
var subdir string
switch {
case len(b.target) != 0 && filepath.Clean(b.target) == ".":
// if target dir is '.', use the current dir
subdir = path.Join(dir)
case len(files) == 1:
// if target is not '.', use the generated folder
subdir = path.Join(dir, files[0].Name())
default:
// if target is not '.', but generated many files, it's wrong
return fmt.Errorf("unexpected directory contents: %v", files)
}
if output, err := b.execCommand("git", []string{"checkout", b.revision}, subdir); err != nil {
return fmt.Errorf("failed to exec 'git checkout %s': %s: %v", b.revision, output, err)
}
if output, err := b.execCommand("git", []string{"reset", "--hard"}, subdir); err != nil {
return fmt.Errorf("failed to exec 'git reset --hard': %s: %v", output, err)
}
volume.SetVolumeOwnership(b, fsGroup)
volumeutil.SetReady(b.getMetaDir())
return nil
}
func (b *gitRepoVolumeMounter) getMetaDir() string {
return path.Join(b.plugin.host.GetPodPluginDir(b.podUID, utilstrings.EscapeQualifiedName(gitRepoPluginName)), b.volName)
}
func (b *gitRepoVolumeMounter) execCommand(command string, args []string, dir string) ([]byte, error) {
cmd := b.exec.Command(command, args...)
cmd.SetDir(dir)
return cmd.CombinedOutput()
}
func validateVolume(src *v1.GitRepoVolumeSource) error {
if err := validateNonFlagArgument(src.Repository, "repository"); err != nil {
return err
}
if err := validateNonFlagArgument(src.Revision, "revision"); err != nil {
return err
}
if err := validateNonFlagArgument(src.Directory, "directory"); err != nil {
return err
}
return nil
}
// gitRepoVolumeUnmounter cleans git repo volumes.
type gitRepoVolumeUnmounter struct {
*gitRepoVolume
}
var _ volume.Unmounter = &gitRepoVolumeUnmounter{}
// TearDown simply deletes everything in the directory.
func (c *gitRepoVolumeUnmounter) TearDown() error {
return c.TearDownAt(c.GetPath())
}
// TearDownAt simply deletes everything in the directory.
func (c *gitRepoVolumeUnmounter) TearDownAt(dir string) error {
return volumeutil.UnmountViaEmptyDir(dir, c.plugin.host, c.volName, wrappedVolumeSpec(), c.podUID)
}
func getVolumeSource(spec *volume.Spec) (*v1.GitRepoVolumeSource, bool) {
var readOnly bool
var volumeSource *v1.GitRepoVolumeSource
if spec.Volume != nil && spec.Volume.GitRepo != nil {
volumeSource = spec.Volume.GitRepo
readOnly = spec.ReadOnly
}
return volumeSource, readOnly
}
func validateNonFlagArgument(arg, argName string) error {
if len(arg) > 0 && arg[0] == '-' {
return fmt.Errorf("%q is an invalid value for %s", arg, argName)
}
return nil
}
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",
"glusterfs.go",
"glusterfs_minmax.go",
"glusterfs_util.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/glusterfs",
deps = [
"//pkg/apis/core/v1/helper:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/cloud-provider/volume/helpers:go_default_library",
"//vendor/github.com/heketi/heketi/client/api/go-client:go_default_library",
"//vendor/github.com/heketi/heketi/pkg/glusterfs/api:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"glusterfs_minmax_test.go",
"glusterfs_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library",
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
"//vendor/github.com/heketi/heketi/pkg/glusterfs/api:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- rootfs
- saad-ali
- jingxu97
- humblec
reviewers:
- saad-ali
- jsafrane
- rootfs
- humblec
- jingxu97
- msau42
/*
Copyright 2015 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 glusterfs contains the internal representation of glusterfs
// volumes.
package glusterfs // import "k8s.io/kubernetes/pkg/volume/glusterfs"
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//
// This implementation is space-efficient for a sparse
// allocation over a big range. Could be optimized
// for high absolute allocation number with a bitmap.
//
package glusterfs
import (
"errors"
"sync"
)
var (
//ErrConflict returned when value is already in use.
ErrConflict = errors.New("number already allocated")
//ErrInvalidRange returned invalid range, for eg# min > max
ErrInvalidRange = errors.New("invalid range")
//ErrOutOfRange returned when value is not in pool range.
ErrOutOfRange = errors.New("out of range")
//ErrRangeFull returned when no more free values in the pool.
ErrRangeFull = errors.New("range full")
//ErrInternal returned when no free item found, but a.free != 0.
ErrInternal = errors.New("internal error")
)
//MinMaxAllocator defines allocator struct.
type MinMaxAllocator struct {
lock sync.Mutex
min int
max int
free int
used map[int]bool
}
var _ Rangeable = &MinMaxAllocator{}
// Rangeable is an Interface that can adjust its min/max range.
// Rangeable should be threadsafe
type Rangeable interface {
Allocate(int) (bool, error)
AllocateNext() (int, bool, error)
Release(int) error
Has(int) bool
Free() int
SetRange(min, max int) error
}
// NewMinMaxAllocator return a new allocator or error based on provided min/max value.
func NewMinMaxAllocator(min, max int) (*MinMaxAllocator, error) {
if min > max {
return nil, ErrInvalidRange
}
return &MinMaxAllocator{
min: min,
max: max,
free: 1 + max - min,
used: map[int]bool{},
}, nil
}
//SetRange defines the range/pool with provided min and max values.
func (a *MinMaxAllocator) SetRange(min, max int) error {
if min > max {
return ErrInvalidRange
}
a.lock.Lock()
defer a.lock.Unlock()
// Check if we need to change
if a.min == min && a.max == max {
return nil
}
a.min = min
a.max = max
// Recompute how many free we have in the range
numUsed := 0
for i := range a.used {
if a.inRange(i) {
numUsed++
}
}
a.free = 1 + max - min - numUsed
return nil
}
//Allocate allocates provided value in the allocator and mark it as used.
func (a *MinMaxAllocator) Allocate(i int) (bool, error) {
a.lock.Lock()
defer a.lock.Unlock()
if !a.inRange(i) {
return false, ErrOutOfRange
}
if a.has(i) {
return false, ErrConflict
}
a.used[i] = true
a.free--
return true, nil
}
//AllocateNext allocates next value from the allocator.
func (a *MinMaxAllocator) AllocateNext() (int, bool, error) {
a.lock.Lock()
defer a.lock.Unlock()
// Fast check if we're out of items
if a.free <= 0 {
return 0, false, ErrRangeFull
}
// Scan from the minimum until we find a free item
for i := a.min; i <= a.max; i++ {
if !a.has(i) {
a.used[i] = true
a.free--
return i, true, nil
}
}
// no free item found, but a.free != 0
return 0, false, ErrInternal
}
//Release free/delete provided value from the allocator.
func (a *MinMaxAllocator) Release(i int) error {
a.lock.Lock()
defer a.lock.Unlock()
if !a.has(i) {
return nil
}
delete(a.used, i)
if a.inRange(i) {
a.free++
}
return nil
}
func (a *MinMaxAllocator) has(i int) bool {
_, ok := a.used[i]
return ok
}
//Has check whether the provided value is used in the allocator
func (a *MinMaxAllocator) Has(i int) bool {
a.lock.Lock()
defer a.lock.Unlock()
return a.has(i)
}
//Free returns the number of free values in the allocator.
func (a *MinMaxAllocator) Free() int {
a.lock.Lock()
defer a.lock.Unlock()
return a.free
}
func (a *MinMaxAllocator) inRange(i int) bool {
return a.min <= i && i <= a.max
}
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package glusterfs
import (
"testing"
)
func TestNewFree(t *testing.T) {
min := 1
max := 10
m, err := NewMinMaxAllocator(min, max)
if err != nil {
t.Errorf("error creating new allocator: '%v'", err)
}
if f := m.Free(); f != (max - min + 1) {
t.Errorf("expect to get %d free, but got %d", (max - min + 1), f)
}
}
func TestNewInvalidRange(t *testing.T) {
if _, err := NewMinMaxAllocator(10, 1); err != ErrInvalidRange {
t.Errorf("expect to get Error '%v', got '%v'", ErrInvalidRange, err)
}
}
func TestSetRange(t *testing.T) {
min := 1
max := 10
m, err := NewMinMaxAllocator(min, max)
if err != nil {
t.Errorf("error creating new allocator: '%v'", err)
}
if err = m.SetRange(10, 1); err != ErrInvalidRange {
t.Errorf("expected to get error '%v', got '%v'", ErrInvalidRange, err)
}
if err = m.SetRange(1, 2); err != nil {
t.Errorf("error setting range: '%v'", err)
}
if f := m.Free(); f != 2 {
t.Errorf("expect to get %d free, but got %d", 2, f)
}
if ok, _ := m.Allocate(1); !ok {
t.Errorf("error allocate offset %v", 1)
}
if f := m.Free(); f != 1 {
t.Errorf("expect to get 1 free, but got %d", f)
}
if err = m.SetRange(1, 1); err != nil {
t.Errorf("error setting range: '%v'", err)
}
if f := m.Free(); f != 0 {
t.Errorf("expect to get 0 free, but got %d", f)
}
if err = m.SetRange(2, 2); err != nil {
t.Errorf("error setting range: '%v'", err)
}
if f := m.Free(); f != 1 {
t.Errorf("expect to get 1 free, but got %d", f)
}
}
func TestAllocateNext(t *testing.T) {
min := 1
max := 10
m, err := NewMinMaxAllocator(min, max)
if err != nil {
t.Errorf("error creating new allocator: '%v'", err)
}
el, ok, _ := m.AllocateNext()
if !ok {
t.Fatalf("unexpected error")
}
if !m.Has(el) {
t.Errorf("expect element %v allocated", el)
}
if f := m.Free(); f != (max-min+1)-1 {
t.Errorf("expect to get %d free, but got %d", (max-min+1)-1, f)
}
}
func TestAllocateMax(t *testing.T) {
min := 1
max := 10
m, err := NewMinMaxAllocator(min, max)
if err != nil {
t.Errorf("error creating new allocator: '%v'", err)
}
for i := 1; i <= max; i++ {
if _, ok, _ := m.AllocateNext(); !ok {
t.Fatalf("unexpected error")
}
}
if _, ok, _ := m.AllocateNext(); ok {
t.Errorf("unexpected success")
}
if f := m.Free(); f != 0 {
t.Errorf("expect to get %d free, but got %d", 0, f)
}
}
func TestAllocate(t *testing.T) {
min := 1
max := 10
offset := 3
m, err := NewMinMaxAllocator(min, max)
if err != nil {
t.Errorf("error creating new allocator: '%v'", err)
}
if ok, err := m.Allocate(offset); !ok {
t.Errorf("error allocate offset %v: %v", offset, err)
}
if !m.Has(offset) {
t.Errorf("expect element %v allocated", offset)
}
if f := m.Free(); f != (max-min+1)-1 {
t.Errorf("expect to get %d free, but got %d", (max-min+1)-1, f)
}
}
func TestAllocateConflict(t *testing.T) {
min := 1
max := 10
offset := 3
m, err := NewMinMaxAllocator(min, max)
if err != nil {
t.Errorf("error creating new allocator: '%v'", err)
}
if ok, err := m.Allocate(offset); !ok {
t.Errorf("error allocate offset %v: %v", offset, err)
}
ok, err := m.Allocate(offset)
if ok {
t.Errorf("unexpected success")
}
if err != ErrConflict {
t.Errorf("expected error '%v', got '%v'", ErrConflict, err)
}
}
func TestAllocateOutOfRange(t *testing.T) {
min := 1
max := 10
offset := 11
m, err := NewMinMaxAllocator(min, max)
if err != nil {
t.Errorf("error creating new allocator: '%v'", err)
}
ok, err := m.Allocate(offset)
if ok {
t.Errorf("unexpected success")
}
if err != ErrOutOfRange {
t.Errorf("expected error '%v', got '%v'", ErrOutOfRange, err)
}
}
func TestRelease(t *testing.T) {
min := 1
max := 10
offset := 3
m, err := NewMinMaxAllocator(min, max)
if err != nil {
t.Errorf("error creating new allocator: '%v'", err)
}
if ok, err := m.Allocate(offset); !ok {
t.Errorf("error allocate offset %v: %v", offset, err)
}
if !m.Has(offset) {
t.Errorf("expect offset %v allocated", offset)
}
if err = m.Release(offset); err != nil {
t.Errorf("unexpected error: %v", err)
}
if m.Has(offset) {
t.Errorf("expect offset %v not allocated", offset)
}
}
/*
Copyright 2015 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 glusterfs
import (
"bufio"
"fmt"
"os"
"k8s.io/klog"
)
// readGlusterLog will take the last 2 lines of the log file
// on failure of gluster SetUp and return those so kubelet can
// properly expose them
// return error on any failure
func readGlusterLog(path string, podName string) error {
var line1 string
var line2 string
linecount := 0
klog.Infof("failure, now attempting to read the gluster log for pod %s", podName)
// Check and make sure path exists
if len(path) == 0 {
return fmt.Errorf("log file does not exist for pod %s", podName)
}
// open the log file
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("could not open log file for pod %s", podName)
}
defer file.Close()
// read in and scan the file using scanner
// from stdlib
fscan := bufio.NewScanner(file)
// rather than guessing on bytes or using Seek
// going to scan entire file and take the last two lines
// generally the file should be small since it is pod specific
for fscan.Scan() {
if linecount > 0 {
line1 = line2
}
line2 = "\n" + fscan.Text()
linecount++
}
if linecount > 0 {
return fmt.Errorf("%v", line1+line2+"\n")
}
return nil
}
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["portworx_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"portworx.go",
"portworx_util.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/portworx",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/cloud-provider/volume/helpers:go_default_library",
"//vendor/github.com/libopenstorage/openstorage/api:go_default_library",
"//vendor/github.com/libopenstorage/openstorage/api/client:go_default_library",
"//vendor/github.com/libopenstorage/openstorage/api/client/volume:go_default_library",
"//vendor/github.com/libopenstorage/openstorage/api/spec:go_default_library",
"//vendor/github.com/libopenstorage/openstorage/volume:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
# See the OWNERS docs at https://go.k8s.io/owners
maintainers:
- adityadani
/*
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 portworx contains the internal representation of Portworx
// Block Device volumes.
package portworx
/*
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 portworx
import (
"fmt"
"os"
"path"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"
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"
)
const (
PortworxTestVolume = "portworx-test-vol"
)
func TestCanSupport(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("portworxVolumeTest")
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/portworx-volume")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.GetPluginName() != "kubernetes.io/portworx-volume" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{PortworxVolume: &v1.PortworxVolumeSource{}}}}) {
t.Errorf("Expected true")
}
if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{PortworxVolume: &v1.PortworxVolumeSource{}}}}}) {
t.Errorf("Expected true")
}
}
func TestGetAccessModes(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("portworxVolumeTest")
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.FindPersistentPluginByName("kubernetes.io/portworx-volume")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) {
t.Errorf("Expected to support AccessModeTypes: %s", v1.ReadWriteOnce)
}
if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) {
t.Errorf("Expected to support AccessModeTypes: %s", v1.ReadWriteMany)
}
if volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) {
t.Errorf("Expected not to support AccessModeTypes: %s", v1.ReadOnlyMany)
}
}
type fakePortworxManager struct {
attachCalled bool
mountCalled bool
}
func (fake *fakePortworxManager) AttachVolume(b *portworxVolumeMounter, attachOptions map[string]string) (string, error) {
fake.attachCalled = true
return "", nil
}
func (fake *fakePortworxManager) DetachVolume(c *portworxVolumeUnmounter) error {
return nil
}
func (fake *fakePortworxManager) MountVolume(b *portworxVolumeMounter, mountPath string) error {
fake.mountCalled = true
return nil
}
func (fake *fakePortworxManager) UnmountVolume(c *portworxVolumeUnmounter, mountPath string) error {
return nil
}
func (fake *fakePortworxManager) CreateVolume(c *portworxVolumeProvisioner) (volumeID string, volumeSizeGB int64, labels map[string]string, err error) {
labels = make(map[string]string)
labels["fakeportworxmanager"] = "yes"
return PortworxTestVolume, 100, labels, nil
}
func (fake *fakePortworxManager) DeleteVolume(cd *portworxVolumeDeleter) error {
if cd.volumeID != PortworxTestVolume {
return fmt.Errorf("Deleter got unexpected volume name: %s", cd.volumeID)
}
return nil
}
func (fake *fakePortworxManager) ResizeVolume(spec *volume.Spec, newSize resource.Quantity, volumeHost volume.VolumeHost) error {
return nil
}
func TestPlugin(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("portworxVolumeTest")
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/portworx-volume")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
spec := &v1.Volume{
Name: "vol1",
VolumeSource: v1.VolumeSource{
PortworxVolume: &v1.PortworxVolumeSource{
VolumeID: PortworxTestVolume,
FSType: "ext4",
},
},
}
fakeManager := &fakePortworxManager{}
// Test Mounter
fakeMounter := &mount.FakeMounter{}
mounter, err := plug.(*portworxVolumePlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), fakeManager, fakeMounter)
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Errorf("Got a nil Mounter")
}
volPath := path.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~portworx-volume/vol1")
path := mounter.GetPath()
if path != volPath {
t.Errorf("Got unexpected path: %s", path)
}
if err := mounter.SetUp(nil); err != nil {
t.Errorf("Expected success, got: %v", err)
}
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
t.Errorf("SetUp() failed, volume path not created: %s", path)
} else {
t.Errorf("SetUp() failed: %v", err)
}
}
if !fakeManager.attachCalled {
t.Errorf("Attach watch not called")
}
if !fakeManager.mountCalled {
t.Errorf("Mount watch not called")
}
// Test Unmounter
fakeManager = &fakePortworxManager{}
unmounter, err := plug.(*portworxVolumePlugin).newUnmounterInternal("vol1", types.UID("poduid"), fakeManager, fakeMounter)
if err != nil {
t.Errorf("Failed to make a new Unmounter: %v", err)
}
if unmounter == nil {
t.Errorf("Got a nil Unmounter")
}
if err := unmounter.TearDown(); err != nil {
t.Errorf("Expected success, got: %v", err)
}
// Test Provisioner
options := volume.VolumeOptions{
PVC: volumetest.CreateTestPVC("100Gi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}),
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
}
provisioner, err := plug.(*portworxVolumePlugin).newProvisionerInternal(options, &fakePortworxManager{})
if err != nil {
t.Errorf("Error creating a new provisioner:%v", err)
}
persistentSpec, err := provisioner.Provision(nil, nil)
if err != nil {
t.Errorf("Provision() failed: %v", err)
}
if persistentSpec.Spec.PersistentVolumeSource.PortworxVolume.VolumeID != PortworxTestVolume {
t.Errorf("Provision() returned unexpected volume ID: %s", persistentSpec.Spec.PersistentVolumeSource.PortworxVolume.VolumeID)
}
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.Labels["fakeportworxmanager"] != "yes" {
t.Errorf("Provision() returned unexpected labels: %v", persistentSpec.Labels)
}
// Test Deleter
volSpec := &volume.Spec{
PersistentVolume: persistentSpec,
}
deleter, err := plug.(*portworxVolumePlugin).newDeleterInternal(volSpec, &fakePortworxManager{})
if err != nil {
t.Errorf("Error creating a new Deleter:%v", err)
}
err = deleter.Delete()
if err != nil {
t.Errorf("Deleter() failed: %v", err)
}
}
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",
"quobyte.go",
"quobyte_util.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/quobyte",
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/cloud-provider/volume/helpers:go_default_library",
"//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/github.com/quobyte/api:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["quobyte_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/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"],
)
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- rootfs
- saad-ali
reviewers:
- johscheuer
- saad-ali
- jsafrane
- rootfs
- jingxu97
- msau42
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package quobyte contains the internal representation of Quobyte
// volumes.
package quobyte
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package quobyte
import (
"fmt"
"os"
"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("quobyte_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/quobyte")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.GetPluginName() != "kubernetes.io/quobyte" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{}}}}) {
t.Errorf("Expected false")
}
if plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) {
t.Errorf("Expected false")
}
}
func TestGetAccessModes(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("quobyte_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/quobyte")
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) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) {
t.Errorf("Expected three AccessModeTypes: %s, %s, and %s", v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany)
}
}
func doTestPlugin(t *testing.T, spec *volume.Spec) {
tmpDir, err := utiltesting.MkTmpdir("quobyte_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/quobyte")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
mounter, err := plug.(*quobytePlugin).newMounterInternal(spec, pod, &mount.FakeMounter{})
volumePath := mounter.GetPath()
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Error("Got a nil Mounter")
}
if volumePath != fmt.Sprintf("%s/plugins/kubernetes.io~quobyte/root#root@vol", tmpDir) {
t.Errorf("Got unexpected path: %s expected: %s", volumePath, fmt.Sprintf("%s/plugins/kubernetes.io~quobyte/root#root@vol", tmpDir))
}
if err := mounter.SetUp(nil); err != nil {
t.Errorf("Expected success, got: %v", err)
}
unmounter, err := plug.(*quobytePlugin).newUnmounterInternal("vol", types.UID("poduid"), &mount.FakeMounter{})
if err != nil {
t.Errorf("Failed to make a new unmounter: %v", err)
}
if unmounter == nil {
t.Error("Got a nil unmounter")
}
if err := unmounter.TearDown(); err != nil {
t.Errorf("Expected success, got: %v", err)
}
// We don't need to check tear down, we don't unmount quobyte
}
func TestPluginVolume(t *testing.T) {
vol := &v1.Volume{
Name: "vol1",
VolumeSource: v1.VolumeSource{
Quobyte: &v1.QuobyteVolumeSource{Registry: "reg:7861", Volume: "vol", ReadOnly: false, User: "root", Group: "root"},
},
}
doTestPlugin(t, volume.NewSpecFromVolume(vol))
}
func TestPluginPersistentVolume(t *testing.T) {
vol := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "vol1",
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
Quobyte: &v1.QuobyteVolumeSource{Registry: "reg:7861", Volume: "vol", ReadOnly: false, User: "root", Group: "root"},
},
},
}
doTestPlugin(t, volume.NewSpecFromPersistentVolume(vol, false))
}
func TestPersistentClaimReadOnlyFlag(t *testing.T) {
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "pvA",
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
Quobyte: &v1.QuobyteVolumeSource{Registry: "reg:7861", Volume: "vol", ReadOnly: false, User: "root", Group: "root"},
},
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,
},
}
tmpDir, err := utiltesting.MkTmpdir("quobyte_test")
if err != nil {
t.Fatalf("error creating temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
client := fake.NewSimpleClientset(pv, claim)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, client, nil))
plug, _ := plugMgr.FindPluginByName(quobytePluginName)
// 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{UID: types.UID("poduid")}}
mounter, _ := plug.NewMounter(spec, pod, volume.VolumeOptions{})
if mounter == nil {
t.Fatalf("Got a nil Mounter")
}
if !mounter.GetAttributes().ReadOnly {
t.Errorf("Expected true for mounter.IsReadOnly")
}
}
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package quobyte
import (
"net"
"os"
"path/filepath"
"strings"
"k8s.io/api/core/v1"
volumehelpers "k8s.io/cloud-provider/volume/helpers"
quobyteapi "github.com/quobyte/api"
"k8s.io/klog"
)
type quobyteVolumeManager struct {
config *quobyteAPIConfig
}
func (manager *quobyteVolumeManager) createVolume(provisioner *quobyteVolumeProvisioner, createQuota bool) (quobyte *v1.QuobyteVolumeSource, size int, err error) {
capacity := provisioner.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
volumeSize, err := volumehelpers.RoundUpToGiBInt(capacity)
if err != nil {
return nil, 0, err
}
// Quobyte has the concept of Volumes which doen't have a specific size (they can grow unlimited)
// to simulate a size constraint we set here a Quota for logical space
volumeRequest := &quobyteapi.CreateVolumeRequest{
Name: provisioner.volume,
RootUserID: provisioner.user,
RootGroupID: provisioner.group,
TenantID: provisioner.tenant,
ConfigurationName: provisioner.config,
}
quobyteClient := manager.createQuobyteClient()
volumeUUID, err := quobyteClient.CreateVolume(volumeRequest)
if err != nil {
return &v1.QuobyteVolumeSource{}, volumeSize, err
}
// Set Quota for Volume with specified byte size
if createQuota {
err = quobyteClient.SetVolumeQuota(volumeUUID, uint64(capacity.Value()))
if err != nil {
return &v1.QuobyteVolumeSource{}, volumeSize, err
}
}
klog.V(4).Infof("Created Quobyte volume %s", provisioner.volume)
return &v1.QuobyteVolumeSource{
Registry: provisioner.registry,
Volume: provisioner.volume,
User: provisioner.user,
Group: provisioner.group,
}, volumeSize, nil
}
func (manager *quobyteVolumeManager) deleteVolume(deleter *quobyteVolumeDeleter) error {
return manager.createQuobyteClient().DeleteVolumeByName(deleter.volume, deleter.tenant)
}
func (manager *quobyteVolumeManager) createQuobyteClient() *quobyteapi.QuobyteClient {
return quobyteapi.NewQuobyteClient(
manager.config.quobyteAPIServer,
manager.config.quobyteUser,
manager.config.quobytePassword,
)
}
func (mounter *quobyteMounter) pluginDirIsMounted(pluginDir string) (bool, error) {
mounts, err := mounter.mounter.List()
if err != nil {
return false, err
}
for _, mountPoint := range mounts {
if strings.HasPrefix(mountPoint.Type, "quobyte") {
continue
}
if mountPoint.Path == pluginDir {
klog.V(4).Infof("quobyte: found mountpoint %s in /proc/mounts", mountPoint.Path)
return true, nil
}
}
return false, nil
}
func (mounter *quobyteMounter) correctTraillingSlash(regStr string) string {
return filepath.Clean(regStr) + string(os.PathSeparator)
}
func validateRegistry(registry string) bool {
if len(registry) == 0 {
return false
}
for _, hostPortPair := range strings.Split(registry, ",") {
if _, _, err := net.SplitHostPort(hostPortPair); err != nil {
return false
}
}
return true
}
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"attacher.go",
"disk_manager.go",
"doc.go",
"rbd.go",
"rbd_util.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/rbd",
deps = [
"//pkg/features:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//pkg/volume/util/volumepathhandler:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/cloud-provider/volume/helpers:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/path:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["rbd_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/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"],
)
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- rootfs
- jsafrane
reviewers:
- sjenning
- saad-ali
- jsafrane
- rootfs
- jingxu97
- msau42
- cofyc
/*
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 rbd
import (
"fmt"
"os"
"time"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
volutil "k8s.io/kubernetes/pkg/volume/util"
)
// NewAttacher implements AttachableVolumePlugin.NewAttacher.
func (plugin *rbdPlugin) NewAttacher() (volume.Attacher, error) {
return plugin.newAttacherInternal(&RBDUtil{})
}
// NewDeviceMounter implements DeviceMountableVolumePlugin.NewDeviceMounter
func (plugin *rbdPlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
return plugin.NewAttacher()
}
func (plugin *rbdPlugin) newAttacherInternal(manager diskManager) (volume.Attacher, error) {
return &rbdAttacher{
plugin: plugin,
manager: manager,
mounter: volutil.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
}, nil
}
// NewDetacher implements AttachableVolumePlugin.NewDetacher.
func (plugin *rbdPlugin) NewDetacher() (volume.Detacher, error) {
return plugin.newDetacherInternal(&RBDUtil{})
}
// NewDeviceUnmounter implements DeviceMountableVolumePlugin.NewDeviceUnmounter
func (plugin *rbdPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
return plugin.NewDetacher()
}
func (plugin *rbdPlugin) newDetacherInternal(manager diskManager) (volume.Detacher, error) {
return &rbdDetacher{
plugin: plugin,
manager: manager,
mounter: volutil.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
}, nil
}
// GetDeviceMountRefs implements AttachableVolumePlugin.GetDeviceMountRefs.
func (plugin *rbdPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
mounter := plugin.host.GetMounter(plugin.GetPluginName())
return mounter.GetMountRefs(deviceMountPath)
}
func (plugin *rbdPlugin) CanAttach(spec *volume.Spec) bool {
return true
}
// rbdAttacher implements volume.Attacher interface.
type rbdAttacher struct {
plugin *rbdPlugin
mounter *mount.SafeFormatAndMount
manager diskManager
}
var _ volume.Attacher = &rbdAttacher{}
var _ volume.DeviceMounter = &rbdAttacher{}
// Attach implements Attacher.Attach.
// We do not lock image here, because it requires kube-controller-manager to
// access external `rbd` utility. And there is no need since AttachDetach
// controller will not try to attach RWO volumes which are already attached to
// other nodes.
func (attacher *rbdAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
return "", nil
}
// VolumesAreAttached implements Attacher.VolumesAreAttached.
// There is no way to confirm whether the volume is attached or not from
// outside of the kubelet node. This method needs to return true always, like
// iSCSI, FC plugin.
func (attacher *rbdAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
volumesAttachedCheck := make(map[*volume.Spec]bool)
for _, spec := range specs {
volumesAttachedCheck[spec] = true
}
return volumesAttachedCheck, nil
}
// WaitForAttach implements Attacher.WaitForAttach. It's called by kubelet to
// attach volume onto the node.
// This method is idempotent, callers are responsible for retrying on failure.
func (attacher *rbdAttacher) WaitForAttach(spec *volume.Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error) {
klog.V(4).Infof("rbd: waiting for attach volume (name: %s) for pod (name: %s, uid: %s)", spec.Name(), pod.Name, pod.UID)
mounter, err := attacher.plugin.createMounterFromVolumeSpecAndPod(spec, pod)
if err != nil {
klog.Warningf("failed to create mounter: %v", spec)
return "", err
}
realDevicePath, err := attacher.manager.AttachDisk(*mounter)
if err != nil {
return "", err
}
klog.V(3).Infof("rbd: successfully wait for attach volume (spec: %s, pool: %s, image: %s) at %s", spec.Name(), mounter.Pool, mounter.Image, realDevicePath)
return realDevicePath, nil
}
// GetDeviceMountPath implements Attacher.GetDeviceMountPath.
func (attacher *rbdAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) {
img, err := getVolumeSourceImage(spec)
if err != nil {
return "", err
}
pool, err := getVolumeSourcePool(spec)
if err != nil {
return "", err
}
return makePDNameInternal(attacher.plugin.host, pool, img), nil
}
// MountDevice implements Attacher.MountDevice. It is called by the kubelet to
// mount device at the given mount path.
// This method is idempotent, callers are responsible for retrying on failure.
func (attacher *rbdAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
klog.V(4).Infof("rbd: mouting device %s to %s", devicePath, deviceMountPath)
notMnt, err := attacher.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 !notMnt {
return nil
}
fstype, err := getVolumeSourceFSType(spec)
if err != nil {
return err
}
ro, err := getVolumeSourceReadOnly(spec)
if err != nil {
return err
}
options := []string{}
if ro {
options = append(options, "ro")
}
mountOptions := volutil.MountOptionFromSpec(spec, options...)
err = attacher.mounter.FormatAndMount(devicePath, deviceMountPath, fstype, mountOptions)
if err != nil {
os.Remove(deviceMountPath)
return fmt.Errorf("rbd: failed to mount device %s at %s (fstype: %s), error %v", devicePath, deviceMountPath, fstype, err)
}
klog.V(3).Infof("rbd: successfully mount device %s at %s (fstype: %s)", devicePath, deviceMountPath, fstype)
return nil
}
// rbdDetacher implements volume.Detacher interface.
type rbdDetacher struct {
plugin *rbdPlugin
manager diskManager
mounter *mount.SafeFormatAndMount
}
var _ volume.Detacher = &rbdDetacher{}
var _ volume.DeviceUnmounter = &rbdDetacher{}
// UnmountDevice implements Detacher.UnmountDevice. It unmounts the global
// mount of the RBD image. This is called once all bind mounts have been
// unmounted.
// Internally, it does four things:
// - Unmount device from deviceMountPath.
// - Detach device from the node.
// - Remove lock if found. (No need to check volume readonly or not, because
// device is not on the node anymore, it's safe to remove lock.)
// - Remove the deviceMountPath at last.
// This method is idempotent, callers are responsible for retrying on failure.
func (detacher *rbdDetacher) UnmountDevice(deviceMountPath string) error {
if pathExists, pathErr := mount.PathExists(deviceMountPath); pathErr != nil {
return fmt.Errorf("Error checking if path exists: %v", pathErr)
} else if !pathExists {
klog.Warningf("Warning: Unmount skipped because path does not exist: %v", deviceMountPath)
return nil
}
devicePath, _, err := mount.GetDeviceNameFromMount(detacher.mounter, deviceMountPath)
if err != nil {
return err
}
// Unmount the device from the device mount point.
klog.V(4).Infof("rbd: unmouting device mountpoint %s", deviceMountPath)
if err = detacher.mounter.Unmount(deviceMountPath); err != nil {
return err
}
klog.V(3).Infof("rbd: successfully umount device mountpath %s", deviceMountPath)
klog.V(4).Infof("rbd: detaching device %s", devicePath)
err = detacher.manager.DetachDisk(detacher.plugin, deviceMountPath, devicePath)
if err != nil {
return err
}
klog.V(3).Infof("rbd: successfully detach device %s", devicePath)
err = os.Remove(deviceMountPath)
if err != nil {
return err
}
klog.V(3).Infof("rbd: successfully remove device mount point %s", deviceMountPath)
return nil
}
// Detach implements Detacher.Detach.
func (detacher *rbdDetacher) Detach(volumeName string, nodeName types.NodeName) error {
return nil
}
/*
Copyright 2014 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.
*/
//
// diskManager interface and diskSetup/TearDown functions abstract commonly used procedures to setup a block volume
// rbd volume implements diskManager, calls diskSetup when creating a volume, and calls diskTearDown inside volume unmounter.
// TODO: consolidate, refactor, and share diskManager among iSCSI, GCE PD, and RBD
//
package rbd
import (
"fmt"
"os"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util"
)
// Abstract interface to disk operations.
type diskManager interface {
// MakeGlobalPDName creates global persistent disk path.
MakeGlobalPDName(disk rbd) string
// MakeGlobalVDPDName creates global block disk path.
MakeGlobalVDPDName(disk rbd) string
// Attaches the disk to the kubelet's host machine.
// If it successfully attaches, the path to the device
// is returned. Otherwise, an error will be returned.
AttachDisk(disk rbdMounter) (string, error)
// Detaches the disk from the kubelet's host machine.
DetachDisk(plugin *rbdPlugin, deviceMountPath string, device string) error
// Detaches the block disk from the kubelet's host machine.
DetachBlockDisk(disk rbdDiskUnmapper, mntPath string) error
// Creates a rbd image.
CreateImage(provisioner *rbdVolumeProvisioner) (r *v1.RBDPersistentVolumeSource, volumeSizeGB int, err error)
// Deletes a rbd image.
DeleteImage(deleter *rbdVolumeDeleter) error
// Expands a rbd image
ExpandImage(expander *rbdVolumeExpander, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error)
}
// utility to mount a disk based filesystem
func diskSetUp(manager diskManager, b rbdMounter, volPath string, mounter mount.Interface, fsGroup *int64) error {
globalPDPath := manager.MakeGlobalPDName(*b.rbd)
notMnt, err := mounter.IsLikelyNotMountPoint(globalPDPath)
if err != nil && !os.IsNotExist(err) {
klog.Errorf("cannot validate mountpoint: %s", globalPDPath)
return err
}
if notMnt {
return fmt.Errorf("no device is mounted at %s", globalPDPath)
}
notMnt, err = mounter.IsLikelyNotMountPoint(volPath)
if err != nil && !os.IsNotExist(err) {
klog.Errorf("cannot validate mountpoint: %s", volPath)
return err
}
if !notMnt {
return nil
}
if err := os.MkdirAll(volPath, 0750); err != nil {
klog.Errorf("failed to mkdir:%s", volPath)
return err
}
// Perform a bind mount to the full path to allow duplicate mounts of the same disk.
options := []string{"bind"}
if (&b).GetAttributes().ReadOnly {
options = append(options, "ro")
}
mountOptions := util.JoinMountOptions(b.mountOptions, options)
err = mounter.Mount(globalPDPath, volPath, "", mountOptions)
if err != nil {
klog.Errorf("failed to bind mount:%s", globalPDPath)
return err
}
klog.V(3).Infof("rbd: successfully bind mount %s to %s with options %v", globalPDPath, volPath, mountOptions)
if !b.ReadOnly {
volume.SetVolumeOwnership(&b, fsGroup)
}
return nil
}
// utility to tear down a disk based filesystem
func diskTearDown(manager diskManager, c rbdUnmounter, volPath string, mounter mount.Interface) error {
notMnt, err := mounter.IsLikelyNotMountPoint(volPath)
if err != nil && !os.IsNotExist(err) {
klog.Errorf("cannot validate mountpoint: %s", volPath)
return err
}
if notMnt {
klog.V(3).Infof("volume path %s is not a mountpoint, deleting", volPath)
return os.Remove(volPath)
}
// Unmount the bind-mount inside this pod.
if err := mounter.Unmount(volPath); err != nil {
klog.Errorf("failed to umount %s", volPath)
return err
}
notMnt, mntErr := mounter.IsLikelyNotMountPoint(volPath)
if mntErr != nil && !os.IsNotExist(mntErr) {
klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
return mntErr
}
if notMnt {
if err := os.Remove(volPath); err != nil {
klog.V(2).Info("Error removing mountpoint ", volPath, ": ", err)
return err
}
}
return nil
}
/*
Copyright 2015 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 rbd contains the internal representation of Rados Block Store (Ceph)
// volumes.
package rbd // import "k8s.io/kubernetes/pkg/volume/rbd"
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"sio_mgr_test.go",
"sio_util_test.go",
"sio_volume_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
"//vendor/github.com/codedellemc/goscaleio/types/v1:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"sio_client.go",
"sio_mgr.go",
"sio_plugin.go",
"sio_util.go",
"sio_volume.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/scaleio",
deps = [
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//staging/src/k8s.io/cloud-provider/volume/helpers:go_default_library",
"//vendor/github.com/codedellemc/goscaleio:go_default_library",
"//vendor/github.com/codedellemc/goscaleio/types/v1:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/keymutex:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
/*
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 scaleio
import (
"errors"
"strconv"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/klog"
siotypes "github.com/codedellemc/goscaleio/types/v1"
)
type storageInterface interface {
CreateVolume(string, int64) (*siotypes.Volume, error)
AttachVolume(string, bool) (string, error)
IsAttached(string) (bool, error)
DetachVolume(string) error
DeleteVolume(string) error
}
type sioMgr struct {
client sioInterface
configData map[string]string
exec mount.Exec
}
func newSioMgr(configs map[string]string, exec mount.Exec) (*sioMgr, error) {
if configs == nil {
return nil, errors.New("missing configuration data")
}
configs[confKey.protectionDomain] = defaultString(configs[confKey.protectionDomain], "default")
configs[confKey.storagePool] = defaultString(configs[confKey.storagePool], "default")
configs[confKey.sdcRootPath] = defaultString(configs[confKey.sdcRootPath], sdcRootPath)
configs[confKey.storageMode] = defaultString(configs[confKey.storageMode], "ThinProvisioned")
mgr := &sioMgr{configData: configs, exec: exec}
return mgr, nil
}
// getClient safely returns an sioInterface
func (m *sioMgr) getClient() (sioInterface, error) {
if m.client == nil {
klog.V(4).Info(log("creating scaleio client"))
configs := m.configData
username := configs[confKey.username]
password := configs[confKey.password]
gateway := configs[confKey.gateway]
b, err := strconv.ParseBool(configs[confKey.sslEnabled])
if err != nil {
klog.Error(log("failed to parse sslEnabled, must be either \"true\" or \"false\""))
return nil, err
}
certsEnabled := b
klog.V(4).Info(log("creating new client for gateway %s", gateway))
client, err := newSioClient(gateway, username, password, certsEnabled, m.exec)
if err != nil {
klog.Error(log("failed to create scaleio client: %v", err))
return nil, err
}
client.sysName = configs[confKey.system]
client.pdName = configs[confKey.protectionDomain]
client.spName = configs[confKey.storagePool]
client.sdcPath = configs[confKey.sdcRootPath]
client.provisionMode = configs[confKey.storageMode]
client.sdcGUID = configs[confKey.sdcGUID]
m.client = client
klog.V(4).Info(log("client created successfully [gateway=%s]", gateway))
}
return m.client, nil
}
// CreateVolume creates a new ScaleIO volume
func (m *sioMgr) CreateVolume(volName string, sizeGB int64) (*siotypes.Volume, error) {
client, err := m.getClient()
if err != nil {
return nil, err
}
klog.V(4).Infof("scaleio: creating volume %s", volName)
vol, err := client.CreateVolume(volName, sizeGB)
if err != nil {
klog.V(4).Infof("scaleio: failed creating volume %s: %v", volName, err)
return nil, err
}
klog.V(4).Infof("scaleio: created volume %s successfully", volName)
return vol, nil
}
// AttachVolume maps a ScaleIO volume to the running node. If flag multiMaps,
// ScaleIO will allow other SDC to map to volume.
func (m *sioMgr) AttachVolume(volName string, multipleMappings bool) (string, error) {
client, err := m.getClient()
if err != nil {
klog.Error(log("attach volume failed: %v", err))
return "", err
}
klog.V(4).Infoln(log("attaching volume %s", volName))
iid, err := client.IID()
if err != nil {
klog.Error(log("failed to get instanceID"))
return "", err
}
klog.V(4).Info(log("attaching volume %s to host instance %s", volName, iid))
devs, err := client.Devs()
if err != nil {
return "", err
}
vol, err := client.FindVolume(volName)
if err != nil {
klog.Error(log("failed to find volume %s: %v", volName, err))
return "", err
}
// handle vol if already attached
if len(vol.MappedSdcInfo) > 0 {
if m.isSdcMappedToVol(iid, vol) {
klog.V(4).Info(log("skippping attachment, volume %s already attached to sdc %s", volName, iid))
return devs[vol.ID], nil
}
}
// attach volume, get deviceName
if err := client.AttachVolume(sioVolumeID(vol.ID), multipleMappings); err != nil {
klog.Error(log("attachment for volume %s failed :%v", volName, err))
return "", err
}
device, err := client.WaitForAttachedDevice(vol.ID)
if err != nil {
klog.Error(log("failed while waiting for device to attach: %v", err))
return "", err
}
klog.V(4).Info(log("volume %s attached successfully as %s to instance %s", volName, device, iid))
return device, nil
}
// IsAttached verifies that the named ScaleIO volume is still attached
func (m *sioMgr) IsAttached(volName string) (bool, error) {
client, err := m.getClient()
if err != nil {
return false, err
}
iid, err := client.IID()
if err != nil {
klog.Error("scaleio: failed to get instanceID")
return false, err
}
vol, err := client.FindVolume(volName)
if err != nil {
return false, err
}
return m.isSdcMappedToVol(iid, vol), nil
}
// DetachVolume detaches the name ScaleIO volume from an instance
func (m *sioMgr) DetachVolume(volName string) error {
client, err := m.getClient()
if err != nil {
return err
}
iid, err := client.IID()
if err != nil {
klog.Error(log("failed to get instanceID: %v", err))
return err
}
vol, err := client.FindVolume(volName)
if err != nil {
return err
}
if !m.isSdcMappedToVol(iid, vol) {
klog.Warning(log(
"skipping detached, vol %s not attached to instance %s",
volName, iid,
))
return nil
}
if err := client.DetachVolume(sioVolumeID(vol.ID)); err != nil {
klog.Error(log("failed to detach vol %s: %v", volName, err))
return err
}
klog.V(4).Info(log("volume %s detached successfully", volName))
return nil
}
// DeleteVolumes removes the ScaleIO volume
func (m *sioMgr) DeleteVolume(volName string) error {
client, err := m.getClient()
if err != nil {
return err
}
vol, err := client.FindVolume(volName)
if err != nil {
return err
}
if err := client.DeleteVolume(sioVolumeID(vol.ID)); err != nil {
klog.Error(log("failed to delete volume %s: %v", volName, err))
return err
}
klog.V(4).Info(log("deleted volume %s successfully", volName))
return nil
}
// isSdcMappedToVol returns true if the sdc is mapped to the volume
func (m *sioMgr) isSdcMappedToVol(sdcID string, vol *siotypes.Volume) bool {
if len(vol.MappedSdcInfo) == 0 {
klog.V(4).Info(log("no attachment found"))
return false
}
for _, sdcInfo := range vol.MappedSdcInfo {
if sdcInfo.SdcID == sdcID {
return true
}
}
return false
}
/*
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 scaleio
import (
"errors"
"testing"
"time"
"k8s.io/kubernetes/pkg/util/mount"
siotypes "github.com/codedellemc/goscaleio/types/v1"
)
var (
fakeSdcID = "test-sdc-123456789"
fakeVolumeName = "test-vol-0001"
fakeVolumeID = "1234567890"
fakeDev = "/dev/testABC"
fakeConfig = map[string]string{
confKey.gateway: "http://sio.gateway:1234",
confKey.sslEnabled: "false",
confKey.system: "scaleio",
confKey.volumeName: "sio-0001",
confKey.secretName: "sio-secret",
confKey.username: "c2lvdXNlcgo=", // siouser
confKey.password: "c2lvcGFzc3dvcmQK", // siopassword
}
)
func newTestMgr(t *testing.T) *sioMgr {
mgr, err := newSioMgr(fakeConfig, mount.NewFakeExec(nil))
if err != nil {
t.Error(err)
}
mgr.client = newFakeSio()
return mgr
}
func TestMgrNew(t *testing.T) {
mgr, err := newSioMgr(fakeConfig, mount.NewFakeExec(nil))
if err != nil {
t.Fatal(err)
}
if mgr.configData == nil {
t.Fatal("configuration data not set")
}
if mgr.configData[confKey.volumeName] != "sio-0001" {
t.Errorf("expecting %s, got %s", "sio-0001", mgr.configData[confKey.volumeName])
}
// check defaults
if mgr.configData[confKey.protectionDomain] != "default" {
t.Errorf("unexpected value for confData[protectionDomain] %s", mgr.configData[confKey.protectionDomain])
}
if mgr.configData[confKey.storagePool] != "default" {
t.Errorf("unexpected value for confData[storagePool] %s", mgr.configData[confKey.storagePool])
}
if mgr.configData[confKey.storageMode] != "ThinProvisioned" {
t.Errorf("unexpected value for confData[storageMode] %s", mgr.configData[confKey.storageMode])
}
}
func TestMgrGetClient(t *testing.T) {
mgr := newTestMgr(t)
_, err := mgr.getClient()
if err != nil {
t.Fatal(err)
}
if mgr.client == nil {
t.Fatal("mgr.client not set")
}
}
func TestMgrCreateVolume(t *testing.T) {
mgr := newTestMgr(t)
vol, err := mgr.CreateVolume("test-vol-0001", 8*1024*1024)
if err != nil {
t.Fatal(err)
}
if vol.Name != "test-vol-0001" {
t.Errorf("unexpected vol.Name %s", vol.Name)
}
}
func TestMgrAttachVolume(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
device, err := mgr.AttachVolume("test-vol-0001", false)
if err != nil {
t.Fatal(err)
}
if device != "/dev/testABC" {
t.Errorf("unexpected value for mapped device %s", device)
}
}
func TestMgrAttachVolume_AlreadyAttached(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
dev, err := mgr.AttachVolume("test-vol-0001", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if dev != "/dev/testABC" {
t.Errorf("unexpected value for mapped device %s", dev)
}
}
func TestMgrAttachVolume_VolumeNotFoundError(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
_, err := mgr.AttachVolume("test-vol-0002", false)
if err == nil {
t.Error("attachVolume should fail with volume not found error")
}
}
func TestMgrAttachVolume_WaitForAttachError(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
go func() {
c := mgr.client.(*fakeSio)
close(c.waitAttachCtrl)
}()
_, err := mgr.AttachVolume("test-vol-0001", false)
if err == nil {
t.Error("attachVolume should fail with attach timeout error")
}
}
func TestMgrDetachVolume(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
if err := mgr.DetachVolume("test-vol-0001"); err != nil {
t.Fatal(err)
}
fakeSio := mgr.client.(*fakeSio)
if len(fakeSio.volume.MappedSdcInfo) != 0 {
t.Errorf("expecting attached sdc to 0, got %d", len(fakeSio.volume.MappedSdcInfo))
}
if len(fakeSio.devs) != 0 {
t.Errorf("expecting local devs to be 0, got %d", len(fakeSio.devs))
}
}
func TestMgrDetachVolume_VolumeNotFound(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
err := mgr.DetachVolume("test-vol-0002")
if err == nil {
t.Fatal("expected a volume not found failure")
}
}
func TestMgrDetachVolume_VolumeNotAttached(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
err := mgr.DetachVolume("test-vol-0001")
if err != nil {
t.Fatal(err)
}
}
func TestMgrDetachVolume_VolumeAlreadyDetached(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
mgr.DetachVolume("test-vol-0001")
err := mgr.DetachVolume("test-vol-0001")
if err != nil {
t.Fatal("failed detaching a volume already detached")
}
}
func TestMgrDetachVolume_WaitForDetachError(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
mgr.AttachVolume("test-vol-0001", false)
err := mgr.DetachVolume("test-vol-0001")
if err != nil {
t.Error("detachVolume failed")
}
}
func TestMgrDeleteVolume(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
err := mgr.DeleteVolume("test-vol-0001")
if err != nil {
t.Fatal(err)
}
sio := mgr.client.(*fakeSio)
if sio.volume != nil {
t.Errorf("volume not nil after delete operation")
}
}
func TestMgrDeleteVolume_VolumeNotFound(t *testing.T) {
mgr := newTestMgr(t)
mgr.CreateVolume("test-vol-0001", 8*1024*1024)
err := mgr.DeleteVolume("test-vol-0002")
if err == nil {
t.Fatal("expected volume not found error")
}
}
// ************************************************************
// Helper Test Types
// ************************************************************
type fakeSio struct {
volume *siotypes.Volume
waitAttachCtrl chan struct{}
waitDetachCtrl chan struct{}
devs map[string]string
isMultiMap bool
}
func newFakeSio() *fakeSio {
return &fakeSio{
waitAttachCtrl: make(chan struct{}),
waitDetachCtrl: make(chan struct{}),
}
}
func (f *fakeSio) FindVolume(volumeName string) (*siotypes.Volume, error) {
if f.volume == nil || f.volume.Name != volumeName {
return nil, errors.New("volume not found")
}
return f.volume, nil
}
func (f *fakeSio) Volume(id sioVolumeID) (*siotypes.Volume, error) {
if f.volume == nil || f.volume.ID != string(id) {
return nil, errors.New("volume not found")
}
return f.volume, nil
}
func (f *fakeSio) CreateVolume(volName string, sizeGB int64) (*siotypes.Volume, error) {
f.volume = &siotypes.Volume{
ID: fakeVolumeID,
Name: volName,
SizeInKb: int(sizeGB),
VolumeType: "test",
}
return f.volume, nil
}
func (f *fakeSio) AttachVolume(id sioVolumeID, multiMaps bool) error {
f.isMultiMap = multiMaps
_, err := f.Volume(id)
if err != nil {
return err
}
f.volume.MappedSdcInfo = []*siotypes.MappedSdcInfo{
{SdcID: fakeSdcID},
}
return nil
}
func (f *fakeSio) DetachVolume(id sioVolumeID) error {
if _, err := f.Volume(id); err != nil {
return err
}
f.volume.MappedSdcInfo = nil
delete(f.devs, f.volume.ID)
return nil
}
func (f *fakeSio) DeleteVolume(id sioVolumeID) error {
if _, err := f.Volume(id); err != nil {
return err
}
f.volume = nil
return nil
}
func (f *fakeSio) IID() (string, error) {
return fakeSdcID, nil
}
func (f *fakeSio) Devs() (map[string]string, error) {
if f.volume == nil {
return nil, errors.New("volume not found")
}
f.devs = map[string]string{
f.volume.ID: fakeDev,
}
return f.devs, nil
}
func (f *fakeSio) GetVolumeRefs(volID sioVolumeID) (int, error) {
if f.volume == nil {
return 0, nil
}
return 1, nil
}
func (f *fakeSio) WaitForAttachedDevice(token string) (string, error) {
select {
case <-time.After(500 * time.Millisecond):
return fakeDev, nil
case <-f.waitAttachCtrl:
return "", errors.New("attached device timeout")
}
}
func (f *fakeSio) WaitForDetachedDevice(token string) error {
select {
case <-time.After(500 * time.Millisecond):
delete(f.devs, f.volume.ID)
return nil
case <-f.waitDetachCtrl:
return errors.New("detach device timeout")
}
}
/*
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 scaleio
import (
"errors"
api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/utils/keymutex"
)
const (
sioPluginName = "kubernetes.io/scaleio"
sioConfigFileName = "sioconf.dat"
)
type sioPlugin struct {
host volume.VolumeHost
volumeMtx keymutex.KeyMutex
}
// ProbeVolumePlugins is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin {
p := &sioPlugin{
host: nil,
}
return []volume.VolumePlugin{p}
}
// *******************
// VolumePlugin Impl
// *******************
var _ volume.VolumePlugin = &sioPlugin{}
func (p *sioPlugin) Init(host volume.VolumeHost) error {
p.host = host
p.volumeMtx = keymutex.NewHashed(0)
return nil
}
func (p *sioPlugin) GetPluginName() string {
return sioPluginName
}
func (p *sioPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
attribs, err := getVolumeSourceAttribs(spec)
if err != nil {
return "", err
}
return attribs.volName, nil
}
func (p *sioPlugin) CanSupport(spec *volume.Spec) bool {
return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.ScaleIO != nil) ||
(spec.Volume != nil && spec.Volume.ScaleIO != nil)
}
func (p *sioPlugin) IsMigratedToCSI() bool {
return false
}
func (p *sioPlugin) RequiresRemount() bool {
return false
}
func (p *sioPlugin) NewMounter(
spec *volume.Spec,
pod *api.Pod,
_ volume.VolumeOptions) (volume.Mounter, error) {
// extract source info from either ScaleIOVolumeSource or ScaleIOPersistentVolumeSource type
attribs, err := getVolumeSourceAttribs(spec)
if err != nil {
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,
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
func (p *sioPlugin) NewUnmounter(specName string, podUID types.UID) (volume.Unmounter, error) {
klog.V(4).Info(log("Unmounter for %s", specName))
return &sioVolume{
podUID: podUID,
volSpecName: specName,
plugin: p,
}, nil
}
func (p *sioPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
sioVol := &api.Volume{
Name: volumeName,
VolumeSource: api.VolumeSource{
ScaleIO: &api.ScaleIOVolumeSource{},
},
}
return volume.NewSpecFromVolume(sioVol), nil
}
// SupportsMountOption returns true if volume plugins supports Mount options
// Specifying mount options in a volume plugin that doesn't support
// user specified mount options will result in error creating persistent volumes
func (p *sioPlugin) SupportsMountOption() bool {
return false
}
// SupportsBulkVolumeVerification checks if volume plugin type is capable
// of enabling bulk polling of all nodes. This can speed up verification of
// attached volumes by quite a bit, but underlying pluging must support it.
func (p *sioPlugin) SupportsBulkVolumeVerification() bool {
return false
}
//******************************
// PersistentVolumePlugin Impl
// *****************************
var _ volume.PersistentVolumePlugin = &sioPlugin{}
func (p *sioPlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
return []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
api.ReadOnlyMany,
}
}
// ***************************
// DeletableVolumePlugin Impl
//****************************
var _ volume.DeletableVolumePlugin = &sioPlugin{}
func (p *sioPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
attribs, err := getVolumeSourceAttribs(spec)
if err != nil {
klog.Error(log("deleter failed to extract volume attributes from spec: %v", err))
return nil, err
}
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,
secretName: secretName,
secretNamespace: secretNS,
volSpecName: spec.Name(),
volName: attribs.volName,
plugin: p,
readOnly: attribs.readOnly,
}, nil
}
// *********************************
// ProvisionableVolumePlugin Impl
// *********************************
var _ volume.ProvisionableVolumePlugin = &sioPlugin{}
func (p *sioPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
klog.V(4).Info(log("creating Provisioner"))
configData := options.Parameters
if configData == nil {
klog.Error(log("provisioner missing parameters, unable to continue"))
return nil, errors.New("option parameters missing")
}
// 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,
secretName: secretName,
secretNamespace: secretNS,
volSpecName: options.PVName,
}, nil
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment