Commit 5c45db56 authored by Di Xu's avatar Di Xu

implement proposal 34058: hostPath volume type

parent 625eb9ab
......@@ -355,6 +355,15 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
r.Keyring = "/etc/ceph/keyring"
}
},
func(obj *api.HostPathVolumeSource, c fuzz.Continue) {
c.FuzzNoCustom(obj)
types := []api.HostPathType{api.HostPathDirectoryOrCreate, api.HostPathDirectory, api.HostPathFileOrCreate,
api.HostPathFile, api.HostPathSocket, api.HostPathCharDev, api.HostPathBlockDev}
typeVol := types[c.Rand.Intn(len(types))]
if obj.Path != "" && obj.Type == nil {
obj.Type = &typeVol
}
},
func(pv *api.PersistentVolume, c fuzz.Continue) {
c.FuzzNoCustom(pv) // fuzz self without calling this function again
types := []api.PersistentVolumePhase{api.VolumeAvailable, api.VolumePending, api.VolumeBound, api.VolumeReleased, api.VolumeFailed}
......
......@@ -597,10 +597,34 @@ const (
ClaimLost PersistentVolumeClaimPhase = "Lost"
)
type HostPathType string
const (
// If nothing exists at the given path, an empty directory will be created there
// as needed with file mode 0755, having the same group and ownership with Kubelet.
HostPathDirectoryOrCreate HostPathType = "DirectoryOrCreate"
// A directory must exist at the given path
HostPathDirectory HostPathType = "Directory"
// If nothing exists at the given path, an empty file will be created there
// as needed with file mode 0644, having the same group and ownership with Kubelet.
HostPathFileOrCreate HostPathType = "FileOrCreate"
// A file must exist at the given path
HostPathFile HostPathType = "File"
// A UNIX socket must exist at the given path
HostPathSocket HostPathType = "Socket"
// A character device must exist at the given path
HostPathCharDev HostPathType = "CharDevice"
// A block device must exist at the given path
HostPathBlockDev HostPathType = "BlockDevice"
)
// Represents a host path mapped into a pod.
// Host path volumes do not support ownership management or SELinux relabeling.
type HostPathVolumeSource struct {
// If the path is a symlink, it will follow the link to the real path.
Path string
// Defaults to DirectoryOrCreate
Type *HostPathType
}
// Represents an empty directory for a pod.
......
......@@ -369,3 +369,10 @@ func SetDefaults_ScaleIOVolumeSource(obj *v1.ScaleIOVolumeSource) {
obj.FSType = "xfs"
}
}
func SetDefaults_HostPathVolumeSource(obj *v1.HostPathVolumeSource) {
typeVol := v1.HostPathDirectoryOrCreate
if obj.Type == nil {
obj.Type = &typeVol
}
}
......@@ -623,6 +623,7 @@ func validateHostPathVolumeSource(hostPath *api.HostPathVolumeSource, fldPath *f
}
allErrs = append(allErrs, validatePathNoBacksteps(hostPath.Path, fldPath.Child("path"))...)
allErrs = append(allErrs, validateHostPathType(hostPath.Type, fldPath.Child("type"))...)
return allErrs
}
......@@ -974,6 +975,25 @@ func validateProjectedVolumeSource(projection *api.ProjectedVolumeSource, fldPat
return allErrs
}
var supportedHostPathTypes = sets.NewString(
string(api.HostPathDirectoryOrCreate),
string(api.HostPathDirectory),
string(api.HostPathFileOrCreate),
string(api.HostPathFile),
string(api.HostPathSocket),
string(api.HostPathCharDev),
string(api.HostPathBlockDev))
func validateHostPathType(hostPathType *api.HostPathType, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if !supportedHostPathTypes.Has(string(*hostPathType)) {
allErrs = append(allErrs, field.NotSupported(fldPath, hostPathType, supportedHostPathTypes.List()))
}
return allErrs
}
// This validate will make sure targetPath:
// 1. is not abs path
// 2. does not have any element which is ".."
......
......@@ -43,6 +43,12 @@ const (
envVarNameErrMsg = "a valid environment variable name must consist of"
)
func newHostPathType(pathType string) *api.HostPathType {
hostPathType := new(api.HostPathType)
*hostPathType = api.HostPathType(pathType)
return hostPathType
}
func testVolume(name string, namespace string, spec api.PersistentVolumeSpec) *api.PersistentVolume {
objMeta := metav1.ObjectMeta{Name: name}
if namespace != "" {
......@@ -86,7 +92,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
StorageClassName: "valid",
}),
......@@ -99,7 +108,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRetain,
}),
......@@ -112,7 +124,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{"fakemode"},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
}),
},
......@@ -124,7 +139,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
PersistentVolumeReclaimPolicy: "fakeReclaimPolicy",
}),
......@@ -137,7 +155,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
}),
},
......@@ -149,7 +170,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
}),
},
......@@ -173,7 +197,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
}),
},
......@@ -184,7 +211,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
api.ResourceName(api.ResourceStorage): resource.MustParse("5G"),
},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "foo", FSType: "ext4"},
},
}),
......@@ -197,7 +227,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/"},
HostPath: &api.HostPathVolumeSource{
Path: "/",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle,
}),
......@@ -210,7 +243,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/a/.."},
HostPath: &api.HostPathVolumeSource{
Path: "/a/..",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRecycle,
}),
......@@ -223,7 +259,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
StorageClassName: "-invalid-",
}),
......@@ -272,7 +311,10 @@ func TestValidatePersistentVolumes(t *testing.T) {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo/.."},
HostPath: &api.HostPathVolumeSource{
Path: "/foo/..",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
StorageClassName: "backstep-hostpath",
}),
......@@ -1109,6 +1151,7 @@ func TestValidateVolumes(t *testing.T) {
EmptyDir: &api.EmptyDirVolumeSource{},
HostPath: &api.HostPathVolumeSource{
Path: "/mnt/path",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
},
......@@ -1116,7 +1159,20 @@ func TestValidateVolumes(t *testing.T) {
errfield: "hostPath",
errdetail: "may not specify more than 1 volume",
},
// HostPath
// HostPath Default
{
name: "default HostPath",
vol: api.Volume{
Name: "hostpath",
VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/mnt/path",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
},
},
// HostPath Supported
{
name: "valid HostPath",
vol: api.Volume{
......@@ -1124,9 +1180,25 @@ func TestValidateVolumes(t *testing.T) {
VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/mnt/path",
Type: newHostPathType(string(api.HostPathSocket)),
},
},
},
},
// HostPath Invalid
{
name: "invalid HostPath",
vol: api.Volume{
Name: "hostpath",
VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/mnt/path",
Type: newHostPathType("invalid"),
},
},
},
errtype: field.ErrorTypeNotSupported,
errfield: "type",
},
{
name: "invalid HostPath backsteps",
......@@ -1135,6 +1207,7 @@ func TestValidateVolumes(t *testing.T) {
VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/mnt/path/..",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
},
......
......@@ -69,6 +69,7 @@ func (kl *Kubelet) newVolumeMounterFromPlugins(spec *volume.Spec, pod *v1.Pod, o
if err != nil {
return nil, fmt.Errorf("can't use volume plugins for %s: %v", spec.Name(), err)
}
opts.Containerized = kl.kubeletConfiguration.Containerized
physicalMounter, err := plugin.NewMounter(spec, pod, opts)
if err != nil {
return nil, fmt.Errorf("failed to instantiate mounter for volume: %s using plugin: %s with a root cause: %v", spec.Name(), plugin.GetPluginName(), err)
......
......@@ -46,6 +46,12 @@ func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer)
return persistentVolumeStorage, statusStorage, server
}
func newHostPathType(pathType string) *api.HostPathType {
hostPathType := new(api.HostPathType)
*hostPathType = api.HostPathType(pathType)
return hostPathType
}
func validNewPersistentVolume(name string) *api.PersistentVolume {
pv := &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
......@@ -57,7 +63,7 @@ func validNewPersistentVolume(name string) *api.PersistentVolume {
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{Path: "/foo"},
HostPath: &api.HostPathVolumeSource{Path: "/foo", Type: newHostPathType(string(api.HostPathDirectoryOrCreate))},
},
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimRetain,
},
......
......@@ -11,6 +11,7 @@ go_library(
srcs = [
"doc.go",
"host_path.go",
"host_path_unix.go",
],
deps = [
"//pkg/volume:go_default_library",
......
......@@ -99,14 +99,15 @@ func (plugin *hostPathPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
}
}
func (plugin *hostPathPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
func (plugin *hostPathPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
hostPathVolumeSource, readOnly, err := getVolumeSource(spec)
if err != nil {
return nil, err
}
path := hostPathVolumeSource.Path
return &hostPathMounter{
hostPath: &hostPath{path: hostPathVolumeSource.Path},
hostPath: &hostPath{path: path, pathType: hostPathVolumeSource.Type},
readOnly: readOnly,
}, nil
}
......@@ -175,7 +176,8 @@ func newProvisioner(options volume.VolumeOptions, host volume.VolumeHost, plugin
// HostPath volumes represent a bare host file or directory mount.
// The direct at the specified path will be directly exposed to the container.
type hostPath struct {
path string
path string
pathType *v1.HostPathType
volume.MetricsNil
}
......@@ -211,7 +213,8 @@ func (b *hostPathMounter) SetUp(fsGroup *int64) error {
if err != nil {
return fmt.Errorf("invalid HostPath `%s`: %v", b.GetPath(), err)
}
return nil
return checkType(b.GetPath(), b.pathType)
}
// SetUpAt does not make sense for host paths - probably programmer error.
......@@ -314,3 +317,170 @@ func getVolumeSource(spec *volume.Spec) (*v1.HostPathVolumeSource, bool, error)
return nil, false, fmt.Errorf("Spec does not reference an HostPath volume type")
}
type hostPathTypeChecker interface {
Exists() bool
IsFile() bool
MakeFile() error
IsDir() bool
MakeDir() error
IsBlock() bool
IsChar() bool
IsSocket() bool
GetPath() string
}
type fileTypeChecker interface {
getFileType(fileInfo os.FileInfo) (v1.HostPathType, error)
}
// this is implemented in per-OS files
type defaultFileTypeChecker struct{}
type osFileTypeChecker struct {
path string
exists bool
info os.FileInfo
checker fileTypeChecker
}
func (ftc *osFileTypeChecker) Exists() bool {
return ftc.exists
}
func (ftc *osFileTypeChecker) IsFile() bool {
if !ftc.Exists() {
return false
}
return !ftc.info.IsDir()
}
func (ftc *osFileTypeChecker) MakeFile() error {
f, err := os.OpenFile(ftc.path, os.O_CREATE, os.FileMode(0644))
defer f.Close()
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}
func (ftc *osFileTypeChecker) IsDir() bool {
if !ftc.Exists() {
return false
}
return ftc.info.IsDir()
}
func (ftc *osFileTypeChecker) MakeDir() error {
err := os.MkdirAll(ftc.path, os.FileMode(0755))
if err != nil {
if !os.IsExist(err) {
return err
}
}
return nil
}
func (ftc *osFileTypeChecker) IsBlock() bool {
if !ftc.Exists() {
return false
}
blkDevType, err := ftc.checker.getFileType(ftc.info)
if err != nil {
return false
}
return blkDevType == v1.HostPathBlockDev
}
func (ftc *osFileTypeChecker) IsChar() bool {
if !ftc.Exists() {
return false
}
charDevType, err := ftc.checker.getFileType(ftc.info)
if err != nil {
return false
}
return charDevType == v1.HostPathCharDev
}
func (ftc *osFileTypeChecker) IsSocket() bool {
if !ftc.Exists() {
return false
}
socketType, err := ftc.checker.getFileType(ftc.info)
if err != nil {
return false
}
return socketType == v1.HostPathSocket
}
func (ftc *osFileTypeChecker) GetPath() string {
return ftc.path
}
func newOSFileTypeChecker(path string, checker fileTypeChecker) (hostPathTypeChecker, error) {
ftc := osFileTypeChecker{path: path, checker: checker}
info, err := os.Stat(path)
if err != nil {
ftc.exists = false
if !os.IsNotExist(err) {
return nil, err
}
} else {
ftc.info = info
ftc.exists = true
}
return &ftc, nil
}
func checkType(path string, pathType *v1.HostPathType) error {
ftc, err := newOSFileTypeChecker(path, &defaultFileTypeChecker{})
if err != nil {
return err
}
return checkTypeInternal(ftc, pathType)
}
func checkTypeInternal(ftc hostPathTypeChecker, pathType *v1.HostPathType) error {
switch *pathType {
case v1.HostPathDirectoryOrCreate:
if !ftc.Exists() {
return ftc.MakeDir()
}
fallthrough
case v1.HostPathDirectory:
if !ftc.IsDir() {
return fmt.Errorf("hostPath type check failed: %s is not a directory", ftc.GetPath())
}
case v1.HostPathFileOrCreate:
if !ftc.Exists() {
return ftc.MakeFile()
}
fallthrough
case v1.HostPathFile:
if !ftc.IsFile() {
return fmt.Errorf("hostPath type check failed: %s is not a file", ftc.GetPath())
}
case v1.HostPathSocket:
if !ftc.IsSocket() {
return fmt.Errorf("hostPath type check failed: %s is not a socket file", ftc.GetPath())
}
case v1.HostPathCharDev:
if !ftc.IsChar() {
return fmt.Errorf("hostPath type check failed: %s is not a character device", ftc.GetPath())
}
case v1.HostPathBlockDev:
if !ftc.IsBlock() {
return fmt.Errorf("hostPath type check failed: %s is not a block device", ftc.GetPath())
}
default:
return fmt.Errorf("%s is an invalid volume type", *pathType)
}
return nil
}
// +build linux darwin
/*
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 host_path
import (
"fmt"
"os"
"syscall"
"k8s.io/api/core/v1"
)
func (dftc *defaultFileTypeChecker) getFileType(info os.FileInfo) (v1.HostPathType, error) {
mode := info.Sys().(*syscall.Stat_t).Mode
switch mode & syscall.S_IFMT {
case syscall.S_IFSOCK:
return v1.HostPathSocket, nil
case syscall.S_IFBLK:
return v1.HostPathBlockDev, nil
case syscall.S_IFCHR:
return v1.HostPathCharDev, nil
}
return "", fmt.Errorf("only recognise socket, block device and character device")
}
/*
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 host_path
import (
"fmt"
"os"
"syscall"
"k8s.io/api/core/v1"
)
func (dftc *defaultFileTypeChecker) getFileType(info os.FileInfo) (v1.HostPathType, error) {
mode := info.Sys().(*syscall.Win32FileAttributeData).FileAttributes
switch mode & syscall.S_IFMT {
case syscall.S_IFSOCK:
return v1.HostPathSocket, nil
case syscall.S_IFBLK:
return v1.HostPathBlockDev, nil
case syscall.S_IFCHR:
return v1.HostPathCharDev, nil
}
return "", fmt.Errorf("only recognise socket, block device and character device")
}
......@@ -66,6 +66,8 @@ type VolumeOptions struct {
CloudTags *map[string]string
// Volume provisioning parameters from StorageClass
Parameters map[string]string
// This flag helps identify whether kubelet is running in a container
Containerized bool
}
// VolumePlugin is an interface to volume plugins that can be used on a
......
......@@ -681,12 +681,39 @@ const (
ClaimLost PersistentVolumeClaimPhase = "Lost"
)
type HostPathType string
const (
// If nothing exists at the given path, an empty directory will be created there
// as needed with file mode 0755, having the same group and ownership with Kubelet.
HostPathDirectoryOrCreate HostPathType = "DirectoryOrCreate"
// A directory must exist at the given path
HostPathDirectory HostPathType = "Directory"
// If nothing exists at the given path, an empty file will be created there
// as needed with file mode 0644, having the same group and ownership with Kubelet.
HostPathFileOrCreate HostPathType = "FileOrCreate"
// A file must exist at the given path
HostPathFile HostPathType = "File"
// A UNIX socket must exist at the given path
HostPathSocket HostPathType = "Socket"
// A character device must exist at the given path
HostPathCharDev HostPathType = "CharDevice"
// A block device must exist at the given path
HostPathBlockDev HostPathType = "BlockDevice"
)
// Represents a host path mapped into a pod.
// Host path volumes do not support ownership management or SELinux relabeling.
type HostPathVolumeSource struct {
// Path of the directory on the host.
// If the path is a symlink, it will follow the link to the real path.
// More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath
Path string `json:"path" protobuf:"bytes,1,opt,name=path"`
// Type for HostPath Volume
// Defaults to DirectoryOrCreate
// More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath
// +optional
Type *HostPathType `json:"type,omitempty" protobuf:"bytes,2,opt,name=type"`
}
// Represents an empty directory for a pod.
......
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