mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
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 ```
This commit is contained in:
commit
9c86087dba
6
api/openapi-spec/swagger.json
generated
6
api/openapi-spec/swagger.json
generated
@ -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"
|
||||
}
|
||||
}
|
||||
|
6
api/swagger-spec/v1.json
generated
6
api/swagger-spec/v1.json
generated
@ -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."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
9
docs/api-reference/v1/definitions.html
generated
9
docs/api-reference/v1/definitions.html
generated
@ -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, …). 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, …).</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)
|
||||
|
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
@ -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
|
||||
}
|
||||
|
||||
|
7
pkg/apis/core/zz_generated.deepcopy.go
generated
7
pkg/apis/core/zz_generated.deepcopy.go
generated
@ -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)
|
||||
}
|
||||
|
997
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
997
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -78,8 +78,12 @@ const (
|
||||
GCELocalSSDVolumeType localVolumeType = "gce-localssd-scsi-fs"
|
||||
// Creates a local file, formats it, and maps it as a block device.
|
||||
BlockLocalVolumeType localVolumeType = "block"
|
||||
// Creates a local file, formats it, and mounts it to use as local volume.
|
||||
BlockFsLocalVolumeType localVolumeType = "blockfs"
|
||||
// Creates a local file serving as the backing for block device., formats it,
|
||||
// and mounts it to use as FS mode local volume.
|
||||
BlockFsWithFormatLocalVolumeType localVolumeType = "blockfswithformat"
|
||||
// Creates a local file serving as the backing for block device. do not format it manually,
|
||||
// and mounts it to use as FS mode local volume.
|
||||
BlockFsWithoutFormatLocalVolumeType localVolumeType = "blockfswithoutformat"
|
||||
)
|
||||
|
||||
var setupLocalVolumeMap = map[localVolumeType]func(*localTestConfig, *v1.Node) *localTestVolume{
|
||||
@ -90,7 +94,8 @@ var setupLocalVolumeMap = map[localVolumeType]func(*localTestConfig, *v1.Node) *
|
||||
DirectoryBindMountedLocalVolumeType: setupLocalVolumeDirectoryBindMounted,
|
||||
DirectoryLinkBindMountedLocalVolumeType: setupLocalVolumeDirectoryLinkBindMounted,
|
||||
BlockLocalVolumeType: setupLocalVolumeBlock,
|
||||
BlockFsLocalVolumeType: setupLocalVolumeBlockFs,
|
||||
BlockFsWithFormatLocalVolumeType: setupLocalVolumeBlockFsWithFormat,
|
||||
BlockFsWithoutFormatLocalVolumeType: setupLocalVolumeBlockFsWithoutFormat,
|
||||
}
|
||||
|
||||
var cleanupLocalVolumeMap = map[localVolumeType]func(*localTestConfig, *localTestVolume){
|
||||
@ -101,7 +106,8 @@ var cleanupLocalVolumeMap = map[localVolumeType]func(*localTestConfig, *localTes
|
||||
DirectoryBindMountedLocalVolumeType: cleanupLocalVolumeDirectoryBindMounted,
|
||||
DirectoryLinkBindMountedLocalVolumeType: cleanupLocalVolumeDirectoryLinkBindMounted,
|
||||
BlockLocalVolumeType: cleanupLocalVolumeBlock,
|
||||
BlockFsLocalVolumeType: cleanupLocalVolumeBlockFs,
|
||||
BlockFsWithFormatLocalVolumeType: cleanupLocalVolumeBlockFsWithFormat,
|
||||
BlockFsWithoutFormatLocalVolumeType: cleanupLocalVolumeBlockFsWithoutFormat,
|
||||
}
|
||||
|
||||
type localTestVolume struct {
|
||||
@ -247,6 +253,11 @@ var _ = utils.SIGDescribe("PersistentVolumes-local ", func() {
|
||||
pod1, pod1Err = createLocalPod(config, testVol, nil)
|
||||
Expect(pod1Err).NotTo(HaveOccurred())
|
||||
verifyLocalPod(config, testVol, pod1, config.node0.Name)
|
||||
|
||||
writeCmd := createWriteCmd(volumeDir, testFile, testFileContent, testVol.localVolumeType)
|
||||
|
||||
By("Writing in pod1")
|
||||
podRWCmdExec(pod1, writeCmd)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
@ -256,16 +267,16 @@ var _ = utils.SIGDescribe("PersistentVolumes-local ", func() {
|
||||
|
||||
It("should be able to mount volume and read from pod1", func() {
|
||||
By("Reading in pod1")
|
||||
// testFileContent was written during setupLocalVolume
|
||||
// testFileContent was written in BeforeEach
|
||||
testReadFileContent(volumeDir, testFile, testFileContent, pod1, testVolType)
|
||||
})
|
||||
|
||||
It("should be able to mount volume and write from pod1", func() {
|
||||
// testFileContent was written during setupLocalVolume
|
||||
// testFileContent was written in BeforeEach
|
||||
testReadFileContent(volumeDir, testFile, testFileContent, pod1, testVolType)
|
||||
|
||||
By("Writing in pod1")
|
||||
writeCmd, _ := createWriteAndReadCmds(volumeDir, testFile, testVol.hostDir /*writeTestFileContent*/, testVolType)
|
||||
writeCmd := createWriteCmd(volumeDir, testFile, testVol.hostDir /*writeTestFileContent*/, testVolType)
|
||||
podRWCmdExec(pod1, writeCmd)
|
||||
})
|
||||
})
|
||||
@ -346,12 +357,12 @@ var _ = utils.SIGDescribe("PersistentVolumes-local ", func() {
|
||||
|
||||
Context("Local volume that cannot be mounted [Slow]", func() {
|
||||
// TODO:
|
||||
// - check for these errors in unit tests intead
|
||||
// - check for these errors in unit tests instead
|
||||
It("should fail due to non-existent path", func() {
|
||||
ep := &eventPatterns{
|
||||
reason: "FailedMount",
|
||||
pattern: make([]string, 2)}
|
||||
ep.pattern = append(ep.pattern, "MountVolume.SetUp failed")
|
||||
ep.pattern = append(ep.pattern, "MountVolume.NewMounter initialization failed")
|
||||
|
||||
testVol := &localTestVolume{
|
||||
node: config.node0,
|
||||
@ -461,7 +472,7 @@ var _ = utils.SIGDescribe("PersistentVolumes-local ", func() {
|
||||
|
||||
// Delete the persistent volume claim: file will be cleaned up and volume be re-created.
|
||||
By("Deleting the persistent volume claim to clean up persistent volume and re-create one")
|
||||
writeCmd, _ := createWriteAndReadCmds(volumePath, testFile, testFileContent, DirectoryLocalVolumeType)
|
||||
writeCmd := createWriteCmd(volumePath, testFile, testFileContent, DirectoryLocalVolumeType)
|
||||
err = issueNodeCommand(config, writeCmd, config.node0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = config.client.CoreV1().PersistentVolumeClaims(claim.Namespace).Delete(claim.Name, &metav1.DeleteOptions{})
|
||||
@ -731,8 +742,6 @@ func testPodWithNodeConflict(config *localTestConfig, testVolType localVolumeTyp
|
||||
|
||||
err = framework.WaitForPodNameUnschedulableInNamespace(config.client, pod.Name, pod.Namespace)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cleanupLocalVolumes(config, []*localTestVolume{testVol})
|
||||
}
|
||||
|
||||
type eventPatterns struct {
|
||||
@ -766,7 +775,12 @@ func twoPodsReadWriteTest(config *localTestConfig, testVol *localTestVolume) {
|
||||
Expect(pod1Err).NotTo(HaveOccurred())
|
||||
verifyLocalPod(config, testVol, pod1, config.node0.Name)
|
||||
|
||||
// testFileContent was written during setupLocalVolume
|
||||
writeCmd := createWriteCmd(volumeDir, testFile, testFileContent, testVol.localVolumeType)
|
||||
|
||||
By("Writing in pod1")
|
||||
podRWCmdExec(pod1, writeCmd)
|
||||
|
||||
// testFileContent was written after creating pod1
|
||||
testReadFileContent(volumeDir, testFile, testFileContent, pod1, testVol.localVolumeType)
|
||||
|
||||
By("Creating pod2 to read from the PV")
|
||||
@ -774,16 +788,16 @@ func twoPodsReadWriteTest(config *localTestConfig, testVol *localTestVolume) {
|
||||
Expect(pod2Err).NotTo(HaveOccurred())
|
||||
verifyLocalPod(config, testVol, pod2, config.node0.Name)
|
||||
|
||||
// testFileContent was written during setupLocalVolume
|
||||
// testFileContent was written after creating pod1
|
||||
testReadFileContent(volumeDir, testFile, testFileContent, pod2, testVol.localVolumeType)
|
||||
|
||||
writeCmd := createWriteCmd(volumeDir, testFile, testVol.hostDir /*writeTestFileContent*/, testVol.localVolumeType)
|
||||
writeCmd = createWriteCmd(volumeDir, testFile, testVol.hostDir /*writeTestFileContent*/, testVol.localVolumeType)
|
||||
|
||||
By("Writing in pod1")
|
||||
podRWCmdExec(pod1, writeCmd)
|
||||
By("Writing in pod2")
|
||||
podRWCmdExec(pod2, writeCmd)
|
||||
|
||||
By("Reading in pod2")
|
||||
testReadFileContent(volumeDir, testFile, testVol.hostDir, pod2, testVol.localVolumeType)
|
||||
By("Reading in pod1")
|
||||
testReadFileContent(volumeDir, testFile, testVol.hostDir, pod1, testVol.localVolumeType)
|
||||
|
||||
By("Deleting pod1")
|
||||
framework.DeletePodOrFail(config.client, config.ns, pod1.Name)
|
||||
@ -798,14 +812,14 @@ func twoPodsReadWriteSerialTest(config *localTestConfig, testVol *localTestVolum
|
||||
Expect(pod1Err).NotTo(HaveOccurred())
|
||||
verifyLocalPod(config, testVol, pod1, config.node0.Name)
|
||||
|
||||
// testFileContent was written during setupLocalVolume
|
||||
testReadFileContent(volumeDir, testFile, testFileContent, pod1, testVol.localVolumeType)
|
||||
|
||||
writeCmd := createWriteCmd(volumeDir, testFile, testVol.hostDir /*writeTestFileContent*/, testVol.localVolumeType)
|
||||
writeCmd := createWriteCmd(volumeDir, testFile, testFileContent, testVol.localVolumeType)
|
||||
|
||||
By("Writing in pod1")
|
||||
podRWCmdExec(pod1, writeCmd)
|
||||
|
||||
// testFileContent was written after creating pod1
|
||||
testReadFileContent(volumeDir, testFile, testFileContent, pod1, testVol.localVolumeType)
|
||||
|
||||
By("Deleting pod1")
|
||||
framework.DeletePodOrFail(config.client, config.ns, pod1.Name)
|
||||
|
||||
@ -815,7 +829,7 @@ func twoPodsReadWriteSerialTest(config *localTestConfig, testVol *localTestVolum
|
||||
verifyLocalPod(config, testVol, pod2, config.node0.Name)
|
||||
|
||||
By("Reading in pod2")
|
||||
testReadFileContent(volumeDir, testFile, testVol.hostDir, pod2, testVol.localVolumeType)
|
||||
testReadFileContent(volumeDir, testFile, testFileContent, pod2, testVol.localVolumeType)
|
||||
|
||||
By("Deleting pod2")
|
||||
framework.DeletePodOrFail(config.client, config.ns, pod2.Name)
|
||||
@ -885,11 +899,13 @@ func cleanupLocalVolumes(config *localTestConfig, volumes []*localTestVolume) {
|
||||
}
|
||||
}
|
||||
|
||||
func setupWriteTestFile(hostDir string, config *localTestConfig, localVolumeType localVolumeType, node *v1.Node) *localTestVolume {
|
||||
writeCmd, _ := createWriteAndReadCmds(hostDir, testFile, testFileContent, localVolumeType)
|
||||
By(fmt.Sprintf("Creating test file on node %q in path %q", node.Name, hostDir))
|
||||
err := issueNodeCommand(config, writeCmd, node)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
func generateLocalTestVolume(hostDir string, config *localTestConfig, localVolumeType localVolumeType, node *v1.Node) *localTestVolume {
|
||||
if localVolumeType != BlockLocalVolumeType && localVolumeType != BlockFsWithoutFormatLocalVolumeType {
|
||||
mkdirCmd := fmt.Sprintf("mkdir -p %s", hostDir)
|
||||
err := issueNodeCommand(config, mkdirCmd, node)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
return &localTestVolume{
|
||||
node: node,
|
||||
hostDir: hostDir,
|
||||
@ -901,8 +917,7 @@ func setupLocalVolumeTmpfs(config *localTestConfig, node *v1.Node) *localTestVol
|
||||
testDirName := "local-volume-test-" + string(uuid.NewUUID())
|
||||
hostDir := filepath.Join(hostBase, testDirName)
|
||||
createAndMountTmpfsLocalVolume(config, hostDir, node)
|
||||
// populate volume with testFile containing testFileContent
|
||||
return setupWriteTestFile(hostDir, config, TmpfsLocalVolumeType, node)
|
||||
return generateLocalTestVolume(hostDir, config, TmpfsLocalVolumeType, node)
|
||||
}
|
||||
|
||||
func setupLocalVolumeGCELocalSSD(config *localTestConfig, node *v1.Node) *localTestVolume {
|
||||
@ -910,15 +925,13 @@ func setupLocalVolumeGCELocalSSD(config *localTestConfig, node *v1.Node) *localT
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dirName := strings.Fields(res)[0]
|
||||
hostDir := "/mnt/disks/by-uuid/google-local-ssds-scsi-fs/" + dirName
|
||||
// Populate volume with testFile containing testFileContent.
|
||||
return setupWriteTestFile(hostDir, config, GCELocalSSDVolumeType, node)
|
||||
return generateLocalTestVolume(hostDir, config, GCELocalSSDVolumeType, node)
|
||||
}
|
||||
|
||||
func setupLocalVolumeDirectory(config *localTestConfig, node *v1.Node) *localTestVolume {
|
||||
testDirName := "local-volume-test-" + string(uuid.NewUUID())
|
||||
hostDir := filepath.Join(hostBase, testDirName)
|
||||
// Populate volume with testFile containing testFileContent.
|
||||
return setupWriteTestFile(hostDir, config, DirectoryLocalVolumeType, node)
|
||||
return generateLocalTestVolume(hostDir, config, DirectoryLocalVolumeType, node)
|
||||
}
|
||||
|
||||
// launchNodeExecPodForLocalPV launches a hostexec pod for local PV and waits
|
||||
@ -992,11 +1005,10 @@ func setupLocalVolumeDirectoryLink(config *localTestConfig, node *v1.Node) *loca
|
||||
testDirName := "local-volume-test-" + string(uuid.NewUUID())
|
||||
hostDir := filepath.Join(hostBase, testDirName)
|
||||
hostDirBackend := hostDir + "-backend"
|
||||
cmd := fmt.Sprintf("mkdir %s && ln -s %s %s", hostDirBackend, hostDirBackend, hostDir)
|
||||
cmd := fmt.Sprintf("mkdir %s && sudo ln -s %s %s", hostDirBackend, hostDirBackend, hostDir)
|
||||
_, err := issueNodeCommandWithResult(config, cmd, node)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Populate volume with testFile containing testFileContent.
|
||||
return setupWriteTestFile(hostDir, config, DirectoryLinkLocalVolumeType, node)
|
||||
return generateLocalTestVolume(hostDir, config, DirectoryLinkLocalVolumeType, node)
|
||||
}
|
||||
|
||||
func setupLocalVolumeDirectoryBindMounted(config *localTestConfig, node *v1.Node) *localTestVolume {
|
||||
@ -1005,20 +1017,18 @@ func setupLocalVolumeDirectoryBindMounted(config *localTestConfig, node *v1.Node
|
||||
cmd := fmt.Sprintf("mkdir %s && sudo mount --bind %s %s", hostDir, hostDir, hostDir)
|
||||
_, err := issueNodeCommandWithResult(config, cmd, node)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Populate volume with testFile containing testFileContent.
|
||||
return setupWriteTestFile(hostDir, config, DirectoryBindMountedLocalVolumeType, node)
|
||||
return generateLocalTestVolume(hostDir, config, DirectoryBindMountedLocalVolumeType, node)
|
||||
}
|
||||
|
||||
func setupLocalVolumeDirectoryLinkBindMounted(config *localTestConfig, node *v1.Node) *localTestVolume {
|
||||
testDirName := "local-volume-test-" + string(uuid.NewUUID())
|
||||
hostDir := filepath.Join(hostBase, testDirName)
|
||||
hostDirBackend := hostDir + "-backend"
|
||||
cmd := fmt.Sprintf("mkdir %s && sudo mount --bind %s %s && ln -s %s %s",
|
||||
cmd := fmt.Sprintf("mkdir %s && sudo mount --bind %s %s && sudo ln -s %s %s",
|
||||
hostDirBackend, hostDirBackend, hostDirBackend, hostDirBackend, hostDir)
|
||||
_, err := issueNodeCommandWithResult(config, cmd, node)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Populate volume with testFile containing testFileContent.
|
||||
return setupWriteTestFile(hostDir, config, DirectoryLinkBindMountedLocalVolumeType, node)
|
||||
return generateLocalTestVolume(hostDir, config, DirectoryLinkBindMountedLocalVolumeType, node)
|
||||
}
|
||||
|
||||
func setupLocalVolumeBlock(config *localTestConfig, node *v1.Node) *localTestVolume {
|
||||
@ -1026,14 +1036,13 @@ func setupLocalVolumeBlock(config *localTestConfig, node *v1.Node) *localTestVol
|
||||
hostDir := filepath.Join(hostBase, testDirName)
|
||||
createAndMapBlockLocalVolume(config, hostDir, node)
|
||||
loopDev := getBlockLoopDev(config, hostDir, node)
|
||||
// Populate block volume with testFile containing testFileContent.
|
||||
volume := setupWriteTestFile(loopDev, config, BlockLocalVolumeType, node)
|
||||
volume := generateLocalTestVolume(loopDev, config, BlockLocalVolumeType, node)
|
||||
volume.hostDir = loopDev
|
||||
volume.loopDevDir = hostDir
|
||||
return volume
|
||||
}
|
||||
|
||||
func setupLocalVolumeBlockFs(config *localTestConfig, node *v1.Node) *localTestVolume {
|
||||
func setupLocalVolumeBlockFsWithFormat(config *localTestConfig, node *v1.Node) *localTestVolume {
|
||||
testDirName := "local-volume-test-" + string(uuid.NewUUID())
|
||||
hostDir := filepath.Join(hostBase, testDirName)
|
||||
createAndMapBlockLocalVolume(config, hostDir, node)
|
||||
@ -1043,13 +1052,25 @@ func setupLocalVolumeBlockFs(config *localTestConfig, node *v1.Node) *localTestV
|
||||
cmd := fmt.Sprintf("sudo mkfs -t ext4 %s && sudo mount -t ext4 %s %s && sudo chmod o+rwx %s", loopDev, loopDev, hostDir, hostDir)
|
||||
_, err := issueNodeCommandWithResult(config, cmd, node)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// Populate block volume with testFile containing testFileContent.
|
||||
volume := setupWriteTestFile(hostDir, config, BlockFsLocalVolumeType, node)
|
||||
volume := generateLocalTestVolume(hostDir, config, BlockFsWithFormatLocalVolumeType, node)
|
||||
volume.hostDir = hostDir
|
||||
volume.loopDevDir = loopDev
|
||||
return volume
|
||||
}
|
||||
|
||||
func setupLocalVolumeBlockFsWithoutFormat(config *localTestConfig, node *v1.Node) *localTestVolume {
|
||||
testDirName := "local-volume-test-" + string(uuid.NewUUID())
|
||||
hostDir := filepath.Join(hostBase, testDirName)
|
||||
createAndMapBlockLocalVolume(config, hostDir, node)
|
||||
loopDev := getBlockLoopDev(config, hostDir, node)
|
||||
volume := generateLocalTestVolume(loopDev, config, BlockFsWithoutFormatLocalVolumeType, node)
|
||||
// we do this in order to set block device path to local PV spec path directly
|
||||
// and test local volume plugin FileSystem mode on block device
|
||||
volume.hostDir = loopDev
|
||||
volume.loopDevDir = hostDir
|
||||
return volume
|
||||
}
|
||||
|
||||
// Determine the /dev/loopXXX device associated with this test, via its hostDir.
|
||||
func getBlockLoopDev(config *localTestConfig, hostDir string, node *v1.Node) string {
|
||||
loopDevCmd := fmt.Sprintf("E2E_LOOP_DEV=$(sudo losetup | grep %s/file | awk '{ print $1 }') 2>&1 > /dev/null && echo ${E2E_LOOP_DEV}", hostDir)
|
||||
@ -1100,7 +1121,7 @@ func cleanupLocalVolumeDirectoryLink(config *localTestConfig, volume *localTestV
|
||||
By("Removing the test directory")
|
||||
hostDir := volume.hostDir
|
||||
hostDirBackend := hostDir + "-backend"
|
||||
removeCmd := fmt.Sprintf("rm -r %s && rm -r %s", hostDir, hostDirBackend)
|
||||
removeCmd := fmt.Sprintf("sudo rm -r %s && rm -r %s", hostDir, hostDirBackend)
|
||||
err := issueNodeCommand(config, removeCmd, volume.node)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
@ -1119,7 +1140,7 @@ func cleanupLocalVolumeDirectoryLinkBindMounted(config *localTestConfig, volume
|
||||
By("Removing the test directory")
|
||||
hostDir := volume.hostDir
|
||||
hostDirBackend := hostDir + "-backend"
|
||||
removeCmd := fmt.Sprintf("rm %s && sudo umount %s && rm -r %s", hostDir, hostDirBackend, hostDirBackend)
|
||||
removeCmd := fmt.Sprintf("sudo rm %s && sudo umount %s && rm -r %s", hostDir, hostDirBackend, hostDirBackend)
|
||||
err := issueNodeCommand(config, removeCmd, volume.node)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
@ -1135,7 +1156,7 @@ func cleanupLocalVolumeBlock(config *localTestConfig, volume *localTestVolume) {
|
||||
}
|
||||
|
||||
// Deletes the PVC/PV and removes the test directory holding the block file.
|
||||
func cleanupLocalVolumeBlockFs(config *localTestConfig, volume *localTestVolume) {
|
||||
func cleanupLocalVolumeBlockFsWithFormat(config *localTestConfig, volume *localTestVolume) {
|
||||
// umount first
|
||||
By("Umount blockfs mountpoint")
|
||||
umountCmd := fmt.Sprintf("sudo umount %s", volume.hostDir)
|
||||
@ -1147,6 +1168,15 @@ func cleanupLocalVolumeBlockFs(config *localTestConfig, volume *localTestVolume)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func cleanupLocalVolumeBlockFsWithoutFormat(config *localTestConfig, volume *localTestVolume) {
|
||||
volume.hostDir = volume.loopDevDir
|
||||
unmapBlockLocalVolume(config, volume.hostDir, volume.node)
|
||||
By("Removing the test directory")
|
||||
removeCmd := fmt.Sprintf("rm -r %s", volume.hostDir)
|
||||
err := issueNodeCommand(config, removeCmd, volume.node)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func makeLocalPVCConfig(config *localTestConfig, volumeType localVolumeType) framework.PersistentVolumeClaimConfig {
|
||||
pvcConfig := framework.PersistentVolumeClaimConfig{
|
||||
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
|
||||
@ -1337,14 +1367,6 @@ func unmapBlockLocalVolume(config *localTestConfig, dir string, node *v1.Node) {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
// Create corresponding write and read commands
|
||||
// to be executed via hostexec Pod on the node with the local PV
|
||||
func createWriteAndReadCmds(testFileDir string, testFile string, writeTestFileContent string, volumeType localVolumeType) (writeCmd string, readCmd string) {
|
||||
writeCmd = createWriteCmd(testFileDir, testFile, writeTestFileContent, volumeType)
|
||||
readCmd = createReadCmd(testFileDir, testFile, volumeType)
|
||||
return writeCmd, readCmd
|
||||
}
|
||||
|
||||
func createWriteCmd(testDir string, testFile string, writeTestFileContent string, volumeType localVolumeType) string {
|
||||
if volumeType == BlockLocalVolumeType {
|
||||
// testDir is the block device.
|
||||
@ -1378,7 +1400,7 @@ func createReadCmd(testFileDir string, testFile string, volumeType localVolumeTy
|
||||
|
||||
// Read testFile and evaluate whether it contains the testFileContent
|
||||
func testReadFileContent(testFileDir string, testFile string, testFileContent string, pod *v1.Pod, volumeType localVolumeType) {
|
||||
readCmd := createReadCmd(volumeDir, testFile, volumeType)
|
||||
readCmd := createReadCmd(testFileDir, testFile, volumeType)
|
||||
readOut := podRWCmdExec(pod, readCmd)
|
||||
Expect(readOut).To(ContainSubstring(testFileContent))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user