Skip to content
Commits on Source (3)
......@@ -196,9 +196,11 @@ The helperPod is allowed to run on nodes experiencing disk pressure conditions,
`sharedFileSystemPath` allows the provisioner to use a filesystem that is mounted on all nodes at the same time.
In this case all access modes are supported: `ReadWriteOnce`, `ReadOnlyMany` and `ReadWriteMany` for storage claims.
`storageClassConfigs` is a map from storage class names to objects containing `nodePathMap` or `sharedFilesystemPath`, as described above.
In addition `volumeBindingMode: Immediate` can be used in StorageClass definition.
Please note that `nodePathMap` and `sharedFileSystemPath` are mutually exclusive. If `sharedFileSystemPath` is used, then `nodePathMap` must be set to `[]`.
Please note that `nodePathMap`, `sharedFileSystemPath`, and `storageClassConfigs` are mutually exclusive. If `sharedFileSystemPath` or `stroageClassConfigs` are used, then `nodePathMap` must be set to `[]`.
The `setupCommand` and `teardownCommand` allow you to specify the path to binary files in helperPod that will be called when creating or deleting pvc respectively. This can be useful if you need to use distroless images for security reasons. See the examples/distroless directory for an example. A binary file can take the following parameters:
| Parameter | Description |
......
......@@ -14,6 +14,20 @@ data:
{{- with .Values.sharedFileSystemPath }}
{{- $config = set $config "sharedFileSystemPath" . }}
{{- end }}
{{- with .Values.storageClassConfigs }}
{{- $configs := dict }}
{{- range $key, $value := . }}
{{- $configValue := dict }}
{{- with $value.nodePathMap }}
{{- $configValue = set $configValue "nodePathMap" . }}
{{- end }}
{{- with $value.sharedFileSystemPath }}
{{- $configValue = set $configValue "sharedFileSystemPath" . }}
{{- end }}
{{- $configs = set $configs $key $configValue }}
{{- end }}
{{- $config = set $config "storageClassConfigs" $configs }}
{{- end }}
{{- $config | toPrettyJson | nindent 4 }}
setup: |-
{{- .Values.configmap.setup | nindent 4 }}
......
{{ if .Values.storageClass.create -}}
{{- $storageClasses := list }}
{{ if .Values.storageClassConfigs }}
{{ $storageClasses = .Values.storageClassConfigs }}
{{- else }}
{{ $storageClasses = dict .Values.storageClass.name .Values }}
{{ end }}
{{- $dot := . }}
{{- range $name, $values := $storageClasses }}
{{ if $values.storageClass.create -}}
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: {{ .Values.storageClass.name }}
name: {{ $name }}
labels:
{{ include "local-path-provisioner.labels" . | indent 4 }}
{{ include "local-path-provisioner.labels" $dot | indent 4 }}
annotations:
storageclass.kubernetes.io/is-default-class: "{{ .Values.storageClass.defaultClass }}"
defaultVolumeType: "{{ .Values.storageClass.defaultVolumeType }}"
provisioner: {{ template "local-path-provisioner.provisionerName" . }}
volumeBindingMode: {{ .Values.storageClass.volumeBindingMode }}
reclaimPolicy: {{ .Values.storageClass.reclaimPolicy }}
storageclass.kubernetes.io/is-default-class: "{{ $values.storageClass.defaultClass }}"
defaultVolumeType: "{{ $values.storageClass.defaultVolumeType }}"
provisioner: {{ template "local-path-provisioner.provisionerName" $dot }}
volumeBindingMode: {{ $values.storageClass.volumeBindingMode }}
reclaimPolicy: {{ $values.storageClass.reclaimPolicy }}
allowVolumeExpansion: true
{{- end }}
---
{{- end }}
......@@ -73,6 +73,22 @@ nodePathMap:
# If `sharedFileSystemPath` is used, then `nodePathMap` must be set to `[]`.
# sharedFileSystemPath: ""
# `storageClassConfigs` allows the provisioner to manage multiple independent storage classes.
# Each storage class must have a unique name, and contains the same fields as shown above for
# a single storage class setup, EXCEPT for the provisionerName, which is the same for all
# storage classes, and name, which is the key of the map.
# storageClassConfigs: {}
# my-storage-class:
# storageClass:
# create: true
# defaultClass: false
# defaultVolumeType: hostPath
# reclaimPolicy: Delete
# sharedFileSystemPath: ""
# ## OR
# # See above
# nodePathMap: {}
podAnnotations: {}
podSecurityContext: {}
......
......@@ -81,12 +81,22 @@ type NodePathMapData struct {
Paths []string `json:"paths,omitempty"`
}
type ConfigData struct {
type StorageClassConfigData struct {
NodePathMap []*NodePathMapData `json:"nodePathMap,omitempty"`
CmdTimeoutSeconds int `json:"cmdTimeoutSeconds,omitempty"`
SharedFileSystemPath string `json:"sharedFileSystemPath,omitempty"`
SetupCommand string `json:"setupCommand,omitempty"`
TeardownCommand string `json:"teardownCommand,omitempty"`
}
type ConfigData struct {
CmdTimeoutSeconds int `json:"cmdTimeoutSeconds,omitempty"`
SetupCommand string `json:"setupCommand,omitempty"`
TeardownCommand string `json:"teardownCommand,omitempty"`
StorageClassConfigData
StorageClassConfigs map[string]StorageClassConfigData `json:"storageClassConfigs"`
}
type StorageClassConfig struct {
NodePathMap map[string]*NodePathMap
SharedFileSystemPath string
}
type NodePathMap struct {
......@@ -94,11 +104,11 @@ type NodePathMap struct {
}
type Config struct {
NodePathMap map[string]*NodePathMap
CmdTimeoutSeconds int
SharedFileSystemPath string
SetupCommand string
TeardownCommand string
CmdTimeoutSeconds int
SetupCommand string
TeardownCommand string
StorageClassConfig
StorageClassConfigs map[string]StorageClassConfig
}
func NewProvisioner(ctx context.Context, kubeClient *clientset.Clientset,
......@@ -177,7 +187,7 @@ func (p *LocalPathProvisioner) watchAndRefreshConfig() {
}()
}
func (p *LocalPathProvisioner) getPathOnNode(node string, requestedPath string) (string, error) {
func (p *LocalPathProvisioner) getPathOnNode(node string, requestedPath string, c *StorageClassConfig) (string, error) {
p.configMutex.RLock()
defer p.configMutex.RUnlock()
......@@ -185,8 +195,7 @@ func (p *LocalPathProvisioner) getPathOnNode(node string, requestedPath string)
return "", fmt.Errorf("no valid config available")
}
c := p.config
sharedFS, err := p.isSharedFilesystem()
sharedFS, err := p.isSharedFilesystem(c)
if err != nil {
return "", err
}
......@@ -222,7 +231,7 @@ func (p *LocalPathProvisioner) getPathOnNode(node string, requestedPath string)
return path, nil
}
func (p *LocalPathProvisioner) isSharedFilesystem() (bool, error) {
func (p *LocalPathProvisioner) isSharedFilesystem(c *StorageClassConfig) (bool, error) {
p.configMutex.RLock()
defer p.configMutex.RUnlock()
......@@ -230,7 +239,6 @@ func (p *LocalPathProvisioner) isSharedFilesystem() (bool, error) {
return false, fmt.Errorf("no valid config available")
}
c := p.config
if (c.SharedFileSystemPath != "") && (len(c.NodePathMap) != 0) {
return false, fmt.Errorf("both nodePathMap and sharedFileSystemPath are defined. Please make sure only one is in use")
}
......@@ -246,11 +254,30 @@ func (p *LocalPathProvisioner) isSharedFilesystem() (bool, error) {
return false, fmt.Errorf("both nodePathMap and sharedFileSystemPath are unconfigured")
}
func (p *LocalPathProvisioner) pickConfig(storageClassName string) (*StorageClassConfig, error) {
if len(p.config.StorageClassConfigs) == 0 {
return &p.config.StorageClassConfig, nil
}
cfg, ok := p.config.StorageClassConfigs[storageClassName]
if !ok {
return nil, fmt.Errorf("BUG: Got request for unexpected storage class %s", storageClassName)
}
return &cfg, nil
}
func (p *LocalPathProvisioner) Provision(ctx context.Context, opts pvController.ProvisionOptions) (*v1.PersistentVolume, pvController.ProvisioningState, error) {
cfg, err := p.pickConfig(opts.StorageClass.Name)
if err != nil {
return nil, pvController.ProvisioningFinished, err
}
return p.provisionFor(opts, cfg)
}
func (p *LocalPathProvisioner) provisionFor(opts pvController.ProvisionOptions, c *StorageClassConfig) (*v1.PersistentVolume, pvController.ProvisioningState, error) {
pvc := opts.PVC
node := opts.SelectedNode
storageClass := opts.StorageClass
sharedFS, err := p.isSharedFilesystem()
sharedFS, err := p.isSharedFilesystem(c)
if err != nil {
return nil, pvController.ProvisioningFinished, err
}
......@@ -279,7 +306,7 @@ func (p *LocalPathProvisioner) Provision(ctx context.Context, opts pvController.
requestedPath = storageClass.Parameters["nodePath"]
}
}
basePath, err := p.getPathOnNode(nodeName, requestedPath)
basePath, err := p.getPathOnNode(nodeName, requestedPath, c)
if err != nil {
return nil, pvController.ProvisioningFinished, err
}
......@@ -307,7 +334,7 @@ func (p *LocalPathProvisioner) Provision(ctx context.Context, opts pvController.
Mode: *pvc.Spec.VolumeMode,
SizeInBytes: storage.Value(),
Node: nodeName,
}); err != nil {
}, c); err != nil {
return nil, pvController.ProvisioningFinished, err
}
......@@ -375,10 +402,19 @@ func (p *LocalPathProvisioner) Provision(ctx context.Context, opts pvController.
}
func (p *LocalPathProvisioner) Delete(ctx context.Context, pv *v1.PersistentVolume) (err error) {
cfg, err := p.pickConfig(pv.Spec.StorageClassName)
if err != nil {
return err
}
return p.deleteFor(pv, cfg)
}
func (p *LocalPathProvisioner) deleteFor(pv *v1.PersistentVolume, c *StorageClassConfig) (err error) {
defer func() {
err = errors.Wrapf(err, "failed to delete volume %v", pv.Name)
}()
path, node, err := p.getPathAndNodeForPV(pv)
path, node, err := p.getPathAndNodeForPV(pv, c)
if err != nil {
return err
}
......@@ -401,7 +437,7 @@ func (p *LocalPathProvisioner) Delete(ctx context.Context, pv *v1.PersistentVolu
Mode: *pv.Spec.VolumeMode,
SizeInBytes: storage.Value(),
Node: node,
}); err != nil {
}, c); err != nil {
logrus.Infof("clean up volume %v failed: %v", pv.Name, err)
return err
}
......@@ -411,7 +447,7 @@ func (p *LocalPathProvisioner) Delete(ctx context.Context, pv *v1.PersistentVolu
return nil
}
func (p *LocalPathProvisioner) getPathAndNodeForPV(pv *v1.PersistentVolume) (path, node string, err error) {
func (p *LocalPathProvisioner) getPathAndNodeForPV(pv *v1.PersistentVolume, cfg *StorageClassConfig) (path, node string, err error) {
defer func() {
err = errors.Wrapf(err, "failed to delete volume %v", pv.Name)
}()
......@@ -425,7 +461,7 @@ func (p *LocalPathProvisioner) getPathAndNodeForPV(pv *v1.PersistentVolume) (pat
return "", "", fmt.Errorf("no path set")
}
sharedFS, err := p.isSharedFilesystem()
sharedFS, err := p.isSharedFilesystem(cfg)
if err != nil {
return "", "", err
}
......@@ -475,11 +511,11 @@ type volumeOptions struct {
Node string
}
func (p *LocalPathProvisioner) createHelperPod(action ActionType, cmd []string, o volumeOptions) (err error) {
func (p *LocalPathProvisioner) createHelperPod(action ActionType, cmd []string, o volumeOptions, cfg *StorageClassConfig) (err error) {
defer func() {
err = errors.Wrapf(err, "failed to %v volume %v", action, o.Name)
}()
sharedFS, err := p.isSharedFilesystem()
sharedFS, err := p.isSharedFilesystem(cfg)
if err != nil {
return err
}
......@@ -667,13 +703,39 @@ func loadConfigFile(configFile string) (cfgData *ConfigData, err error) {
}
func canonicalizeConfig(data *ConfigData) (cfg *Config, err error) {
defer func() {
err = errors.Wrapf(err, "config canonicalization failed")
}()
cfg = &Config{}
cfg.SharedFileSystemPath = data.SharedFileSystemPath
if len(data.StorageClassConfigs) == 0 {
defaultConfig, err := canonicalizeStorageClassConfig(&data.StorageClassConfigData)
if err != nil {
return nil, err
}
cfg.StorageClassConfig = *defaultConfig
} else {
cfg.StorageClassConfigs = make(map[string]StorageClassConfig, len(data.StorageClassConfigs))
for name, classData := range data.StorageClassConfigs {
classCfg, err := canonicalizeStorageClassConfig(&classData)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("config for class %s is invalid", name))
}
cfg.StorageClassConfigs[name] = *classCfg
}
}
cfg.SetupCommand = data.SetupCommand
cfg.TeardownCommand = data.TeardownCommand
if data.CmdTimeoutSeconds > 0 {
cfg.CmdTimeoutSeconds = data.CmdTimeoutSeconds
} else {
cfg.CmdTimeoutSeconds = defaultCmdTimeoutSeconds
}
return cfg, nil
}
func canonicalizeStorageClassConfig(data *StorageClassConfigData) (cfg *StorageClassConfig, err error) {
defer func() {
err = errors.Wrapf(err, "StorageClass config canonicalization failed")
}()
cfg = &StorageClassConfig{}
cfg.SharedFileSystemPath = data.SharedFileSystemPath
cfg.NodePathMap = map[string]*NodePathMap{}
for _, n := range data.NodePathMap {
if cfg.NodePathMap[n.Node] != nil {
......@@ -698,11 +760,6 @@ func canonicalizeConfig(data *ConfigData) (cfg *Config, err error) {
npMap.Paths[path] = struct{}{}
}
}
if data.CmdTimeoutSeconds > 0 {
cfg.CmdTimeoutSeconds = data.CmdTimeoutSeconds
} else {
cfg.CmdTimeoutSeconds = defaultCmdTimeoutSeconds
}
return cfg, nil
}
......
......@@ -144,6 +144,12 @@ func (p *PodTestSuite) TestPodWithSubpath() {
runTest(p, []string{p.config.IMAGE}, "ready", hostPathVolumeType)
}
func (p *PodTestSuite) TestPodWithMultipleStorageClasses() {
p.kustomizeDir = "multiple-storage-classes"
runTest(p, []string{p.config.IMAGE}, "ready", hostPathVolumeType)
}
func runTest(p *PodTestSuite, images []string, waitCondition, volumeType string) {
kustomizeDir := testdataFile(p.kustomizeDir)
......
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../deploy
- storage-class-shared.yaml
- pod.yaml
- pvc.yaml
patchesStrategicMerge:
- local-path-config.yaml
commonLabels:
app: local-path-provisioner
images:
- name: rancher/local-path-provisioner
newTag: dev
kind: ConfigMap
apiVersion: v1
metadata:
name: local-path-config
namespace: local-path-storage
data:
config.json: |-
{
"storageClassConfigs":{
"local-path": {
"nodePathMap": [
{
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
"paths":["/opt/local-path-provisioner"]
}
]
},
"local-path-shared": {
"sharedFilesystemPath": "/opt/local-path-provisioner-shared"
}
}
}
apiVersion: v1
kind: Pod
metadata:
name: volume-test
spec:
containers:
- name: volume-test
image: nginx:stable-alpine
imagePullPolicy: IfNotPresent
volumeMounts:
- name: volv
mountPath: /data
- name: volv-shared
mountPath: /shared-data
ports:
- containerPort: 80
volumes:
- name: volv
persistentVolumeClaim:
claimName: local-path-pvc
- name: volv-shared
persistentVolumeClaim:
claimName: local-path-shared-pvc
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 128Mi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-shared-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: local-path-shared
resources:
requests:
storage: 128Mi
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path-shared
provisioner: rancher.io/local-path
volumeBindingMode: Immediate
reclaimPolicy: Delete