From d64279f5389cb8e25a67df23984dba819c51fbe5 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Tue, 23 Jan 2024 17:47:44 +0100 Subject: [PATCH 1/2] Add support for custom path patterns Add support for the `pathPattern` parameter, if set it is expanded as a go template with the PV name and PVC metadata as input. This allow configuring the provisioner to use predictable paths so volumes can be prefilled externally or re-used. Signed-off-by: Alban Bedel --- README.md | 6 ++++-- provisioner.go | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 04a580b8d..779db387e 100644 --- a/README.md +++ b/README.md @@ -270,8 +270,9 @@ A few things to note; the annotation for the `StorageClass` will apply to all vo ### Storage classes -If more than one `paths` are specified in the `nodePathMap` the path is chosen randomly. To make the provisioner choose a specific path, use a `storageClass` defined with a parameter called `nodePath`. Note that this path should be defined in the `nodePathMap` +If more than one `paths` are specified in the `nodePathMap` the path is chosen randomly. To make the provisioner choose a specific path, use a `storageClass` defined with a parameter called `nodePath`. Note that this path should be defined in the `nodePathMap`. +By default the volume subdirectory is named using the template `{{ .PVName }}_{{ .PVC.Namespace }}_{{ .PVC.Name }}` which make the directory specific to the PV instance. The template can be changed using the `pathPattern` parameter which is interpreted as a go template. The template has access to the PV name using the `PVName` variable and the PVC metadata object, including labels and annotations, with the `PVC` variable. ``` apiVersion: storage.k8s.io/v1 kind: StorageClass @@ -280,11 +281,12 @@ metadata: provisioner: rancher.io/local-path parameters: nodePath: /data/ssd + pathPattern: "{{ .PVC.Namespace }}/{{ .PVC.Name }}" volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Delete ``` -Here the provisioner will use the path `/data/ssd` when storage class `ssd-local-path` is used. +Here the provisioner will use the path `/data/ssd` with a subdirectory per namespace and PVC when storage class `ssd-local-path` is used. ## Uninstall diff --git a/provisioner.go b/provisioner.go index 797df703c..57f67e019 100644 --- a/provisioner.go +++ b/provisioner.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "sync" + "text/template" "time" "github.com/Sirupsen/logrus" @@ -265,6 +266,31 @@ func (p *LocalPathProvisioner) pickConfig(storageClassName string) (*StorageClas return &cfg, nil } +type pvMetadata struct { + PVName string + PVC metav1.ObjectMeta +} + +func pathFromPattern(pattern string, opts pvController.ProvisionOptions) (string, error) { + metadata := pvMetadata{ + PVName: opts.PVName, + PVC: opts.PVC.ObjectMeta, + } + + tpl, err := template.New("pathPattern").Parse(pattern) + if err != nil { + return "", err + } + + buf := new(bytes.Buffer) + err = tpl.Execute(buf, metadata) + if err != nil { + return "", err + } + + return buf.String(), 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 { @@ -314,6 +340,15 @@ func (p *LocalPathProvisioner) provisionFor(opts pvController.ProvisionOptions, name := opts.PVName folderName := strings.Join([]string{name, opts.PVC.Namespace, opts.PVC.Name}, "_") + pathPattern, exists := opts.StorageClass.Parameters["pathPattern"] + if exists { + folderName, err = pathFromPattern(pathPattern, opts) + if err != nil { + err = errors.Wrapf(err, "failed to create path from pattern %v", pathPattern) + return nil, pvController.ProvisioningFinished, err + } + } + path := filepath.Join(basePath, folderName) if nodeName == "" { logrus.Infof("Creating volume %v at %v", name, path) From d56371dc1492a9ad9dc30e72611e1fd63624a2d7 Mon Sep 17 00:00:00 2001 From: Alban Bedel Date: Wed, 24 Jan 2024 12:42:01 +0100 Subject: [PATCH 2/2] deployment: Add support for the storage class path pattern to the chart Add the `pathPattern` parameter to the storage class values so it can also be configured from the helm chart. Signed-off-by: Alban Bedel --- deploy/chart/local-path-provisioner/README.md | 1 + .../chart/local-path-provisioner/templates/storageclass.yaml | 4 ++++ deploy/chart/local-path-provisioner/values.yaml | 3 +++ 3 files changed, 8 insertions(+) diff --git a/deploy/chart/local-path-provisioner/README.md b/deploy/chart/local-path-provisioner/README.md index e785246f6..e06342620 100644 --- a/deploy/chart/local-path-provisioner/README.md +++ b/deploy/chart/local-path-provisioner/README.md @@ -65,6 +65,7 @@ default values. | `storageClass.defaultVolumeType` | The default volume type this storage class creates | `hostPath` | | `storageClass.name` | The name to assign the created StorageClass | local-path | | `storageClass.reclaimPolicy` | ReclaimPolicy field of the class | Delete | +| `storageClass.pathPattern` | Template for the volume directory name | `nil` | | `nodePathMap` | Configuration of where to store the data on each node | `[{node: DEFAULT_PATH_FOR_NON_LISTED_NODES, paths: [/opt/local-path-provisioner]}]` | | `resources` | Local Path Provisioner resource requests & limits | `{}` | | `rbac.create` | If true, create & use RBAC resources | `true` | diff --git a/deploy/chart/local-path-provisioner/templates/storageclass.yaml b/deploy/chart/local-path-provisioner/templates/storageclass.yaml index 6a41ce15e..0bddf7b0f 100644 --- a/deploy/chart/local-path-provisioner/templates/storageclass.yaml +++ b/deploy/chart/local-path-provisioner/templates/storageclass.yaml @@ -20,6 +20,10 @@ provisioner: {{ template "local-path-provisioner.provisionerName" $dot }} volumeBindingMode: {{ $values.storageClass.volumeBindingMode }} reclaimPolicy: {{ $values.storageClass.reclaimPolicy }} allowVolumeExpansion: true +{{- if .Values.storageClass.pathPattern }} +parameters: + pathPattern: {{ .Values.storageClass.pathPattern | quote }} +{{ end -}} {{- end }} --- {{- end }} diff --git a/deploy/chart/local-path-provisioner/values.yaml b/deploy/chart/local-path-provisioner/values.yaml index b3a6c549c..a2d4a4805 100644 --- a/deploy/chart/local-path-provisioner/values.yaml +++ b/deploy/chart/local-path-provisioner/values.yaml @@ -48,6 +48,9 @@ storageClass: ## volumeBindingMode field controls when volume binding and dynamic provisioning should occur, can be "Immediate" or "WaitForFirstConsumer" volumeBindingMode: WaitForFirstConsumer + ## Set a path pattern, if unset the default will be used + # pathPattern: "{{ .PVC.Namespace }}-{{ .PVC.Name }}" + # nodePathMap is the place user can customize where to store the data on each node. # 1. If one node is not listed on the nodePathMap, and Kubernetes wants to create volume on it, the paths specified in # DEFAULT_PATH_FOR_NON_LISTED_NODES will be used for provisioning.