diff --git a/deploy/chart/local-path-provisioner/templates/configmap.yaml b/deploy/chart/local-path-provisioner/templates/configmap.yaml index 6ad117b6aef9ccbe1d594ddcdcb77fb52d08cde9..0f45cdab9bdc2f3d025bfb8798fc36a0cb0dbc3a 100644 --- a/deploy/chart/local-path-provisioner/templates/configmap.yaml +++ b/deploy/chart/local-path-provisioner/templates/configmap.yaml @@ -14,6 +14,9 @@ data: {{- with .Values.sharedFileSystemPath }} {{- $config = set $config "sharedFileSystemPath" . }} {{- end }} + {{- with .Values.multipleStorageClasses }} + {{- $config = set $config "sharedFileSystemPath" . }} + {{- end }} {{- $config | toPrettyJson | nindent 4 }} setup: |- {{- .Values.configmap.setup | nindent 4 }} diff --git a/deploy/chart/local-path-provisioner/templates/storageclass.yaml b/deploy/chart/local-path-provisioner/templates/storageclass.yaml index 7dc9b17829e834e48fafdd1b90ee6d5acf91bd47..70fe8c995e3e17b95b0375a91e0734bae3ce5bcf 100644 --- a/deploy/chart/local-path-provisioner/templates/storageclass.yaml +++ b/deploy/chart/local-path-provisioner/templates/storageclass.yaml @@ -1,15 +1,25 @@ -{{ if .Values.storageClass.create -}} +{{- $storageClasses := list }} +{{ if .Values.multipleStorageClasses }} +{{ $storageClasses = .Values.multipleStorageClasses }} +{{- else }} +{{ $storageClasses = list .Values }} +{{ end }} +{{- $dot := . }} +{{- range $values := $storageClasses }} +{{ if $values.storageClass.create -}} apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: - name: {{ .Values.storageClass.name }} + name: {{ $values.storageClass.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 }} diff --git a/deploy/chart/local-path-provisioner/values.yaml b/deploy/chart/local-path-provisioner/values.yaml index 277007535f9866fcbb829a2b2fb5fe90caca105d..5b3fb0b7589a4c1bd943a4676a07309ae3964760 100644 --- a/deploy/chart/local-path-provisioner/values.yaml +++ b/deploy/chart/local-path-provisioner/values.yaml @@ -73,6 +73,21 @@ nodePathMap: # If `sharedFileSystemPath` is used, then `nodePathMap` must be set to `[]`. # sharedFileSystemPath: "" +# `multipleStorageClasses` 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 +# multipleStorageClasses: +# - storageClass: +# name: my-storage-class-name +# create: true +# defaultClass: false +# reclaimPolicy: Delete +# sharedFileSystemPath: "" +# ## OR +# # See above +# nodePathMap: {} + podAnnotations: {} podSecurityContext: {} diff --git a/provisioner.go b/provisioner.go index b226a2144cf70b7577e077aacbbcf127ec35811f..797df703c5f07c7db1c557470846955e6bfd526c 100644 --- a/provisioner.go +++ b/provisioner.go @@ -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 }