Unverified Commit 9c86087d authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #63011 from NickrenREN/local-plugin-change

Automatic merge from submit-queue (batch tested with PRs 63011, 68089, 67944, 68132). If you want to cherry-pick this change to another branch, please follow the instructions here: https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md. Support both directory and block device for local volume plugin FileSystem VolumeMode Support both directory and block device for local volume plugin FileSystem VolumeMode xref: [local storage dynamic provisioning design #1914](https://github.com/kubernetes/community/pull/1914) **What this PR does / why we need it**: **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes # **Special notes for your reviewer**: **Release note**: ```release-note Support both directory and block device for local volume plugin FileSystem VolumeMode ```
parents 1fc36a57 5bc823f1
......@@ -81795,8 +81795,12 @@
"path"
],
"properties": {
"fsType": {
"description": "Filesystem type to mount. It applies only when the Path is a block device. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default value is to auto-select a fileystem if unspecified.",
"type": "string"
},
"path": {
"description": "The full path to the volume on the node. It can be either a directory or block device (disk, partition, ...). Directories can be represented only by PersistentVolume with VolumeMode=Filesystem. Block devices can be represented only by VolumeMode=Block, which also requires the BlockVolume alpha feature gate to be enabled.",
"description": "The full path to the volume on the node. It can be either a directory or block device (disk, partition, ...).",
"type": "string"
}
}
......@@ -20242,7 +20242,11 @@
"properties": {
"path": {
"type": "string",
"description": "The full path to the volume on the node. It can be either a directory or block device (disk, partition, ...). Directories can be represented only by PersistentVolume with VolumeMode=Filesystem. Block devices can be represented only by VolumeMode=Block, which also requires the BlockVolume alpha feature gate to be enabled."
"description": "The full path to the volume on the node. It can be either a directory or block device (disk, partition, ...)."
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. It applies only when the Path is a block device. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default value is to auto-select a fileystem if unspecified."
}
}
},
......
......@@ -6641,11 +6641,18 @@ Examples:<br>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">path</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">The full path to the volume on the node. It can be either a directory or block device (disk, partition, &#8230;). Directories can be represented only by PersistentVolume with VolumeMode=Filesystem. Block devices can be represented only by VolumeMode=Block, which also requires the BlockVolume alpha feature gate to be enabled.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">The full path to the volume on the node. It can be either a directory or block device (disk, partition, &#8230;).</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">fsType</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Filesystem type to mount. It applies only when the Path is a block device. Must be a filesystem type supported by the host operating system. Ex. "ext4", "xfs", "ntfs". The default value is to auto-select a fileystem if unspecified.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
......
......@@ -1513,10 +1513,14 @@ type KeyToPath struct {
type LocalVolumeSource struct {
// The full path to the volume on the node.
// It can be either a directory or block device (disk, partition, ...).
// Directories can be represented only by PersistentVolume with VolumeMode=Filesystem.
// Block devices can be represented only by VolumeMode=Block, which also requires the
// BlockVolume alpha feature gate to be enabled.
Path string
// Filesystem type to mount.
// It applies only when the Path is a block device.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". The default value is to auto-select a fileystem if unspecified.
// +optional
FSType *string
}
// Represents storage that is managed by an external CSI volume driver (Beta feature)
......
......@@ -4074,6 +4074,7 @@ func Convert_core_LocalObjectReference_To_v1_LocalObjectReference(in *core.Local
func autoConvert_v1_LocalVolumeSource_To_core_LocalVolumeSource(in *v1.LocalVolumeSource, out *core.LocalVolumeSource, s conversion.Scope) error {
out.Path = in.Path
out.FSType = (*string)(unsafe.Pointer(in.FSType))
return nil
}
......@@ -4084,6 +4085,7 @@ func Convert_v1_LocalVolumeSource_To_core_LocalVolumeSource(in *v1.LocalVolumeSo
func autoConvert_core_LocalVolumeSource_To_v1_LocalVolumeSource(in *core.LocalVolumeSource, out *v1.LocalVolumeSource, s conversion.Scope) error {
out.Path = in.Path
out.FSType = (*string)(unsafe.Pointer(in.FSType))
return nil
}
......
......@@ -1959,6 +1959,11 @@ func (in *LocalObjectReference) DeepCopy() *LocalObjectReference {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LocalVolumeSource) DeepCopyInto(out *LocalVolumeSource) {
*out = *in
if in.FSType != nil {
in, out := &in.FSType, &out.FSType
*out = new(string)
**out = **in
}
return
}
......@@ -2884,7 +2889,7 @@ func (in *PersistentVolumeSource) DeepCopyInto(out *PersistentVolumeSource) {
if in.Local != nil {
in, out := &in.Local, &out.Local
*out = new(LocalVolumeSource)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
......
......@@ -194,7 +194,7 @@ func (f *FakeMounter) GetFileType(pathname string) (FileType, error) {
if t, ok := f.Filesystem[pathname]; ok {
return t, nil
}
return FileType("fake"), nil
return FileType("Directory"), nil
}
func (f *FakeMounter) MakeDir(pathname string) error {
......
......@@ -331,8 +331,8 @@ func HasMountRefs(mountPath string, mountRefs []string) bool {
return count > 0
}
// pathWithinBase checks if give path is within given base directory.
func pathWithinBase(fullPath, basePath string) bool {
// PathWithinBase checks if give path is within given base directory.
func PathWithinBase(fullPath, basePath string) bool {
rel, err := filepath.Rel(basePath, fullPath)
if err != nil {
return false
......
......@@ -665,7 +665,7 @@ func findMountInfo(path, mountInfoPath string) (mountInfo, error) {
// point that is prefix of 'path' - that's the mount where path resides
var info *mountInfo
for i := len(infos) - 1; i >= 0; i-- {
if pathWithinBase(path, infos[i].mountPoint) {
if PathWithinBase(path, infos[i].mountPoint) {
info = &infos[i]
break
}
......@@ -736,7 +736,7 @@ func (mounter *Mounter) PrepareSafeSubpath(subPath Subpath) (newHostPath string,
// This implementation is shared between Linux and NsEnterMounter
func safeOpenSubPath(mounter Interface, subpath Subpath) (int, error) {
if !pathWithinBase(subpath.Path, subpath.VolumePath) {
if !PathWithinBase(subpath.Path, subpath.VolumePath) {
return -1, fmt.Errorf("subpath %q not within volume path %q", subpath.Path, subpath.VolumePath)
}
fd, err := doSafeOpen(subpath.Path, subpath.VolumePath)
......@@ -964,7 +964,7 @@ func cleanSubPath(mounter Interface, subpath Subpath) error {
// removeEmptyDirs works backwards from endDir to baseDir and removes each directory
// if it is empty. It stops once it encounters a directory that has content
func removeEmptyDirs(baseDir, endDir string) error {
if !pathWithinBase(endDir, baseDir) {
if !PathWithinBase(endDir, baseDir) {
return fmt.Errorf("endDir %q is not within baseDir %q", endDir, baseDir)
}
......@@ -1052,7 +1052,7 @@ func getMode(pathname string) (os.FileMode, error) {
func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
glog.V(4).Infof("Creating directory %q within base %q", pathname, base)
if !pathWithinBase(pathname, base) {
if !PathWithinBase(pathname, base) {
return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
}
......@@ -1079,7 +1079,7 @@ func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
if err != nil {
return fmt.Errorf("error opening directory %s: %s", existingPath, err)
}
if !pathWithinBase(fullExistingPath, base) {
if !PathWithinBase(fullExistingPath, base) {
return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
}
......@@ -1241,7 +1241,7 @@ func doSafeOpen(pathname string, base string) (int, error) {
// sure the user cannot change already existing directories into symlinks.
for _, seg := range segments {
currentPath = filepath.Join(currentPath, seg)
if !pathWithinBase(currentPath, base) {
if !PathWithinBase(currentPath, base) {
return -1, fmt.Errorf("path %s is outside of allowed base %s", currentPath, base)
}
......@@ -1298,7 +1298,7 @@ func searchMountPoints(hostSource, mountInfoPath string) ([]string, error) {
// We need search in backward order because it's possible for later mounts
// to overlap earlier mounts.
for i := len(mis) - 1; i >= 0; i-- {
if hostSource == mis[i].mountPoint || pathWithinBase(hostSource, mis[i].mountPoint) {
if hostSource == mis[i].mountPoint || PathWithinBase(hostSource, mis[i].mountPoint) {
// If it's a mount point or path under a mount point.
mountID = mis[i].id
rootPath = filepath.Join(mis[i].root, strings.TrimPrefix(hostSource, mis[i].mountPoint))
......
......@@ -413,7 +413,7 @@ func TestPathWithinBase(t *testing.T) {
},
}
for _, test := range tests {
if pathWithinBase(test.fullPath, test.basePath) != test.expected {
if PathWithinBase(test.fullPath, test.basePath) != test.expected {
t.Errorf("test %q failed: expected %v", test.name, test.expected)
}
......
......@@ -309,7 +309,7 @@ func lockAndCheckSubPathWithoutSymlink(volumePath, subPath string) ([]uintptr, e
break
}
if !pathWithinBase(currentFullPath, volumePath) {
if !PathWithinBase(currentFullPath, volumePath) {
errorResult = fmt.Errorf("SubPath %q not within volume path %q", currentFullPath, volumePath)
break
}
......@@ -499,7 +499,7 @@ func (mounter *Mounter) SafeMakeDir(subdir string, base string, perm os.FileMode
func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
glog.V(4).Infof("Creating directory %q within base %q", pathname, base)
if !pathWithinBase(pathname, base) {
if !PathWithinBase(pathname, base) {
return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
}
......@@ -534,7 +534,7 @@ func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
if err != nil {
return fmt.Errorf("cannot read link %s: %s", base, err)
}
if !pathWithinBase(fullExistingPath, fullBasePath) {
if !PathWithinBase(fullExistingPath, fullBasePath) {
return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
}
......
......@@ -576,8 +576,8 @@ func TestPathWithinBase(t *testing.T) {
}
for _, test := range tests {
result := pathWithinBase(test.fullPath, test.basePath)
assert.Equal(t, result, test.expectedResult, "Expect result not equal with pathWithinBase(%s, %s) return: %q, expected: %q",
result := PathWithinBase(test.fullPath, test.basePath)
assert.Equal(t, result, test.expectedResult, "Expect result not equal with PathWithinBase(%s, %s) return: %q, expected: %q",
test.fullPath, test.basePath, result, test.expectedResult)
}
}
......
......@@ -320,7 +320,7 @@ func (mounter *NsenterMounter) SafeMakeDir(subdir string, base string, perm os.F
evaluatedBase = filepath.Clean(evaluatedBase)
rootDir := filepath.Clean(mounter.rootDir)
if pathWithinBase(evaluatedBase, rootDir) {
if PathWithinBase(evaluatedBase, rootDir) {
// Base is in /var/lib/kubelet. This directory is shared between the
// container with kubelet and the host. We don't need to add '/rootfs'.
// This is useful when /rootfs is mounted as read-only - we can still
......
......@@ -33,6 +33,7 @@ go_test(
embed = [":go_default_library"],
deps = select({
"@io_bazel_rules_go//go/platform:darwin": [
"//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",
......@@ -41,6 +42,7 @@ go_test(
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
],
"@io_bazel_rules_go//go/platform:linux": [
"//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",
......@@ -49,6 +51,7 @@ go_test(
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
],
"@io_bazel_rules_go//go/platform:windows": [
"//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",
......
......@@ -38,6 +38,10 @@ import (
"k8s.io/kubernetes/pkg/volume/validation"
)
const (
defaultFSType = "ext4"
)
// This is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{&localVolumePlugin{}}
......@@ -111,6 +115,11 @@ func (plugin *localVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ vo
return nil, err
}
globalLocalPath, err := plugin.getGlobalLocalPath(spec)
if err != nil {
return nil, err
}
return &localVolumeMounter{
localVolume: &localVolume{
pod: pod,
......@@ -118,7 +127,7 @@ func (plugin *localVolumePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ vo
volName: spec.Name(),
mounter: plugin.host.GetMounter(plugin.GetPluginName()),
plugin: plugin,
globalPath: volumeSource.Path,
globalPath: globalLocalPath,
MetricsProvider: volume.NewMetricsStatFS(volumeSource.Path),
},
readOnly: readOnly,
......@@ -207,6 +216,163 @@ func (plugin *localVolumePlugin) ConstructBlockVolumeSpec(podUID types.UID, volu
return volume.NewSpecFromPersistentVolume(localVolume, false), nil
}
func (plugin *localVolumePlugin) generateBlockDeviceBaseGlobalPath() string {
return filepath.Join(plugin.host.GetPluginDir(localVolumePluginName), mount.MountsInGlobalPDPath)
}
func (plugin *localVolumePlugin) getGlobalLocalPath(spec *volume.Spec) (string, error) {
if spec.PersistentVolume.Spec.Local == nil || len(spec.PersistentVolume.Spec.Local.Path) == 0 {
return "", fmt.Errorf("local volume source is nil or local path is not set")
}
fileType, err := plugin.host.GetMounter(plugin.GetPluginName()).GetFileType(spec.PersistentVolume.Spec.Local.Path)
if err != nil {
return "", err
}
switch fileType {
case mount.FileTypeDirectory:
return spec.PersistentVolume.Spec.Local.Path, nil
case mount.FileTypeBlockDev:
return filepath.Join(plugin.generateBlockDeviceBaseGlobalPath(), spec.Name()), nil
default:
return "", fmt.Errorf("only directory and block device are supported")
}
}
var _ volume.DeviceMountableVolumePlugin = &localVolumePlugin{}
type deviceMounter struct {
plugin *localVolumePlugin
mounter *mount.SafeFormatAndMount
}
var _ volume.DeviceMounter = &deviceMounter{}
func (plugin *localVolumePlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
return &deviceMounter{
plugin: plugin,
mounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
}, nil
}
func (dm *deviceMounter) mountLocalBlockDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
glog.V(4).Infof("local: mounting device %s to %s", devicePath, deviceMountPath)
notMnt, err := dm.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 := util.MountOptionFromSpec(spec, options...)
err = dm.mounter.FormatAndMount(devicePath, deviceMountPath, fstype, mountOptions)
if err != nil {
os.Remove(deviceMountPath)
return fmt.Errorf("local: failed to mount device %s at %s (fstype: %s), error %v", devicePath, deviceMountPath, fstype, err)
}
glog.V(3).Infof("local: successfully mount device %s at %s (fstype: %s)", devicePath, deviceMountPath, fstype)
return nil
}
func (dm *deviceMounter) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
if spec.PersistentVolume.Spec.Local == nil || len(spec.PersistentVolume.Spec.Local.Path) == 0 {
return fmt.Errorf("local volume source is nil or local path is not set")
}
fileType, err := dm.mounter.GetFileType(spec.PersistentVolume.Spec.Local.Path)
if err != nil {
return err
}
switch fileType {
case mount.FileTypeBlockDev:
// local volume plugin does not implement AttachableVolumePlugin interface, so set devicePath to Path in PV spec directly
devicePath = spec.PersistentVolume.Spec.Local.Path
return dm.mountLocalBlockDevice(spec, devicePath, deviceMountPath)
case mount.FileTypeDirectory:
// if the given local volume path is of already filesystem directory, return directly
return nil
default:
return fmt.Errorf("only directory and block device are supported")
}
}
func getVolumeSourceFSType(spec *volume.Spec) (string, error) {
if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.Local != nil {
if spec.PersistentVolume.Spec.Local.FSType != nil {
return *spec.PersistentVolume.Spec.Local.FSType, nil
} else {
// if the FSType is not set in local PV spec, setting it to default ("ext4")
return defaultFSType, nil
}
}
return "", fmt.Errorf("spec does not reference a Local volume type")
}
func getVolumeSourceReadOnly(spec *volume.Spec) (bool, error) {
if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.Local != nil {
// local volumes used as a PersistentVolume gets the ReadOnly flag indirectly through
// the persistent-claim volume used to mount the PV
return spec.ReadOnly, nil
}
return false, fmt.Errorf("spec does not reference a Local volume type")
}
func (dm *deviceMounter) GetDeviceMountPath(spec *volume.Spec) (string, error) {
return dm.plugin.getGlobalLocalPath(spec)
}
func (plugin *localVolumePlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
return &deviceMounter{
plugin: plugin,
mounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
}, nil
}
func (plugin *localVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
mounter := plugin.host.GetMounter(plugin.GetPluginName())
return mounter.GetMountRefs(deviceMountPath)
}
var _ volume.DeviceUnmounter = &deviceMounter{}
func (dm *deviceMounter) UnmountDevice(deviceMountPath string) error {
// If the local PV is a block device,
// The deviceMountPath is generated to the format like :/var/lib/kubelet/plugins/kubernetes.io/local-volume/mounts/localpv.spec.Name;
// If it is a filesystem directory, then the deviceMountPath is set directly to pvSpec.Local.Path
// We only need to unmount block device here, so we need to check if the deviceMountPath passed here
// has base mount path: /var/lib/kubelet/plugins/kubernetes.io/local-volume/mounts
basemountPath := dm.plugin.generateBlockDeviceBaseGlobalPath()
if mount.PathWithinBase(deviceMountPath, basemountPath) {
return util.UnmountPath(deviceMountPath, dm.mounter)
}
return nil
}
// Local volumes represent a local directory on a node.
// The directory at the globalPath will be bind-mounted to the pod's directory
type localVolume struct {
......
......@@ -31,16 +31,18 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/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"
)
const (
testPVName = "pvA"
testMountPath = "pods/poduid/volumes/kubernetes.io~local-volume/pvA"
testGlobalPath = "plugins/kubernetes.io~local-volume/volumeDevices/pvA"
testPodPath = "pods/poduid/volumeDevices/kubernetes.io~local-volume"
testNodeName = "fakeNodeName"
testPVName = "pvA"
testMountPath = "pods/poduid/volumes/kubernetes.io~local-volume/pvA"
testGlobalPath = "plugins/kubernetes.io~local-volume/volumeDevices/pvA"
testPodPath = "pods/poduid/volumeDevices/kubernetes.io~local-volume"
testNodeName = "fakeNodeName"
testBlockFormattingToFSGlobalPath = "plugins/kubernetes.io/local-volume/mounts/pvA"
)
func getPlugin(t *testing.T) (string, volume.VolumePlugin) {
......@@ -102,6 +104,33 @@ func getPersistentPlugin(t *testing.T) (string, volume.PersistentVolumePlugin) {
return tmpDir, plug
}
func getDeviceMountablePluginWithBlockPath(t *testing.T, isBlockDevice bool) (string, volume.DeviceMountableVolumePlugin) {
tmpDir, err := utiltesting.MkTmpdir("localVolumeTest")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
plugMgr := volume.VolumePluginMgr{}
var pathToFSType map[string]mount.FileType
if isBlockDevice {
pathToFSType = map[string]mount.FileType{
tmpDir: mount.FileTypeBlockDev,
}
}
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHostWithMounterFSType(tmpDir, nil, nil, pathToFSType))
plug, err := plugMgr.FindDeviceMountablePluginByName(localVolumePluginName)
if err != nil {
os.RemoveAll(tmpDir)
t.Fatalf("Can't find the plugin by name")
}
if plug.GetPluginName() != localVolumePluginName {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
return tmpDir, plug
}
func getTestVolume(readOnly bool, path string, isBlock bool) *volume.Spec {
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
......@@ -179,6 +208,87 @@ func TestInvalidLocalPath(t *testing.T) {
}
}
func TestBlockDeviceGlobalPathAndMountDevice(t *testing.T) {
// Block device global mount path testing
tmpBlockDir, plug := getDeviceMountablePluginWithBlockPath(t, true)
defer os.RemoveAll(tmpBlockDir)
dm, err := plug.NewDeviceMounter()
if err != nil {
t.Errorf("Failed to make a new device mounter: %v", err)
}
pvSpec := getTestVolume(false, tmpBlockDir, false)
expectedGlobalPath := filepath.Join(tmpBlockDir, testBlockFormattingToFSGlobalPath)
actualPath, err := dm.GetDeviceMountPath(pvSpec)
if err != nil {
t.Errorf("Failed to get device mount path: %v", err)
}
if expectedGlobalPath != actualPath {
t.Fatalf("Expected device mount global path:%s, got: %s", expectedGlobalPath, actualPath)
}
fmt.Println("expected global path is:", expectedGlobalPath)
err = dm.MountDevice(pvSpec, tmpBlockDir, expectedGlobalPath)
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(actualPath); err != nil {
if os.IsNotExist(err) {
t.Errorf("DeviceMounter.MountDevice() failed, device mount path not created: %s", actualPath)
} else {
t.Errorf("DeviceMounter.MountDevice() failed: %v", err)
}
}
du, err := plug.NewDeviceUnmounter()
if err != nil {
t.Fatalf("Create device unmounter error: %v", err)
}
err = du.UnmountDevice(actualPath)
if err != nil {
t.Fatalf("Unmount device error: %v", err)
}
}
func TestFSGlobalPathAndMountDevice(t *testing.T) {
// FS global path testing
tmpFSDir, plug := getDeviceMountablePluginWithBlockPath(t, false)
defer os.RemoveAll(tmpFSDir)
dm, err := plug.NewDeviceMounter()
if err != nil {
t.Errorf("Failed to make a new device mounter: %v", err)
}
pvSpec := getTestVolume(false, tmpFSDir, false)
expectedGlobalPath := tmpFSDir
actualPath, err := dm.GetDeviceMountPath(pvSpec)
if err != nil {
t.Errorf("Failed to get device mount path: %v", err)
}
if expectedGlobalPath != actualPath {
t.Fatalf("Expected device mount global path:%s, got: %s", expectedGlobalPath, actualPath)
}
// Actually, we will do nothing if the local path is FS type
err = dm.MountDevice(pvSpec, tmpFSDir, expectedGlobalPath)
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(expectedGlobalPath); err != nil {
if os.IsNotExist(err) {
t.Errorf("DeviceMounter.MountDevice() failed, device mount path not created: %s", expectedGlobalPath)
} else {
t.Errorf("DeviceMounter.MountDevice() failed: %v", err)
}
}
}
func TestMountUnmount(t *testing.T) {
tmpDir, plug := getPlugin(t)
defer os.RemoveAll(tmpDir)
......
......@@ -60,33 +60,40 @@ type fakeVolumeHost struct {
}
func NewFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin) *fakeVolumeHost {
return newFakeVolumeHost(rootDir, kubeClient, plugins, nil)
return newFakeVolumeHost(rootDir, kubeClient, plugins, nil, nil)
}
func NewFakeVolumeHostWithCloudProvider(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, cloud cloudprovider.Interface) *fakeVolumeHost {
return newFakeVolumeHost(rootDir, kubeClient, plugins, cloud)
return newFakeVolumeHost(rootDir, kubeClient, plugins, cloud, nil)
}
func NewFakeVolumeHostWithNodeLabels(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, labels map[string]string) *fakeVolumeHost {
volHost := newFakeVolumeHost(rootDir, kubeClient, plugins, nil)
volHost := newFakeVolumeHost(rootDir, kubeClient, plugins, nil, nil)
volHost.nodeLabels = labels
return volHost
}
func NewFakeVolumeHostWithNodeName(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, nodeName string) *fakeVolumeHost {
volHost := newFakeVolumeHost(rootDir, kubeClient, plugins, nil)
volHost := newFakeVolumeHost(rootDir, kubeClient, plugins, nil, nil)
volHost.nodeName = nodeName
return volHost
}
func newFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, cloud cloudprovider.Interface) *fakeVolumeHost {
func newFakeVolumeHost(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, cloud cloudprovider.Interface, pathToTypeMap map[string]mount.FileType) *fakeVolumeHost {
host := &fakeVolumeHost{rootDir: rootDir, kubeClient: kubeClient, cloud: cloud}
host.mounter = &mount.FakeMounter{}
host.mounter = &mount.FakeMounter{
Filesystem: pathToTypeMap,
}
host.exec = mount.NewFakeExec(nil)
host.pluginMgr.InitPlugins(plugins, nil /* prober */, host)
return host
}
func NewFakeVolumeHostWithMounterFSType(rootDir string, kubeClient clientset.Interface, plugins []VolumePlugin, pathToTypeMap map[string]mount.FileType) *fakeVolumeHost {
volHost := newFakeVolumeHost(rootDir, kubeClient, plugins, nil, pathToTypeMap)
return volHost
}
func (f *fakeVolumeHost) GetPluginDir(podUID string) string {
return path.Join(f.rootDir, "plugins", podUID)
}
......
......@@ -1724,10 +1724,14 @@ message LocalObjectReference {
message LocalVolumeSource {
// The full path to the volume on the node.
// It can be either a directory or block device (disk, partition, ...).
// Directories can be represented only by PersistentVolume with VolumeMode=Filesystem.
// Block devices can be represented only by VolumeMode=Block, which also requires the
// BlockVolume alpha feature gate to be enabled.
optional string path = 1;
// Filesystem type to mount.
// It applies only when the Path is a block device.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". The default value is to auto-select a fileystem if unspecified.
// +optional
optional string fsType = 2;
}
// Represents an NFS mount that lasts the lifetime of a pod.
......
......@@ -1601,10 +1601,14 @@ type KeyToPath struct {
type LocalVolumeSource struct {
// The full path to the volume on the node.
// It can be either a directory or block device (disk, partition, ...).
// Directories can be represented only by PersistentVolume with VolumeMode=Filesystem.
// Block devices can be represented only by VolumeMode=Block, which also requires the
// BlockVolume alpha feature gate to be enabled.
Path string `json:"path" protobuf:"bytes,1,opt,name=path"`
// Filesystem type to mount.
// It applies only when the Path is a block device.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". The default value is to auto-select a fileystem if unspecified.
// +optional
FSType *string `json:"fsType,omitempty" protobuf:"bytes,2,opt,name=fsType"`
}
// Represents storage that is managed by an external CSI volume driver (Beta feature)
......
......@@ -891,8 +891,9 @@ func (LocalObjectReference) SwaggerDoc() map[string]string {
}
var map_LocalVolumeSource = map[string]string{
"": "Local represents directly-attached storage with node affinity (Beta feature)",
"path": "The full path to the volume on the node. It can be either a directory or block device (disk, partition, ...). Directories can be represented only by PersistentVolume with VolumeMode=Filesystem. Block devices can be represented only by VolumeMode=Block, which also requires the BlockVolume alpha feature gate to be enabled.",
"": "Local represents directly-attached storage with node affinity (Beta feature)",
"path": "The full path to the volume on the node. It can be either a directory or block device (disk, partition, ...).",
"fsType": "Filesystem type to mount. It applies only when the Path is a block device. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default value is to auto-select a fileystem if unspecified.",
}
func (LocalVolumeSource) SwaggerDoc() map[string]string {
......
......@@ -1957,6 +1957,11 @@ func (in *LocalObjectReference) DeepCopy() *LocalObjectReference {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LocalVolumeSource) DeepCopyInto(out *LocalVolumeSource) {
*out = *in
if in.FSType != nil {
in, out := &in.FSType, &out.FSType
*out = new(string)
**out = **in
}
return
}
......@@ -2882,7 +2887,7 @@ func (in *PersistentVolumeSource) DeepCopyInto(out *PersistentVolumeSource) {
if in.Local != nil {
in, out := &in.Local, &out.Local
*out = new(LocalVolumeSource)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.StorageOS != nil {
in, out := &in.StorageOS, &out.StorageOS
......
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