diff --git a/charts/kubernetes-zfs-provisioner/templates/storageclass.yaml b/charts/kubernetes-zfs-provisioner/templates/storageclass.yaml index a4fb411..ae531f2 100644 --- a/charts/kubernetes-zfs-provisioner/templates/storageclass.yaml +++ b/charts/kubernetes-zfs-provisioner/templates/storageclass.yaml @@ -12,6 +12,9 @@ metadata: {{ toYaml . | nindent 4 }} {{- end }} provisioner: {{ $.Values.provisioner.instance }} +{{- if eq .type "auto" }} +volumeBindingMode: WaitForFirstConsumer +{{- end }} reclaimPolicy: {{ .policy | default "Delete" }} parameters: parentDataset: {{ .parentDataset }} diff --git a/charts/kubernetes-zfs-provisioner/values.yaml b/charts/kubernetes-zfs-provisioner/values.yaml index 9afdc8d..04f43ae 100644 --- a/charts/kubernetes-zfs-provisioner/values.yaml +++ b/charts/kubernetes-zfs-provisioner/values.yaml @@ -37,7 +37,7 @@ storageClass: # policy: "Delete" # # -- NFS export properties (see `exports(5)`) # shareProperties: "" - # # -- Provision type, one of [`nfs`, `hostpath`] + # # -- Provision type, one of [`nfs`, `hostpath`, `auto`] # type: "nfs" # # -- Override `kubernetes.io/hostname` from `hostName` parameter for # # `HostPath` node affinity diff --git a/pkg/provisioner/parameters.go b/pkg/provisioner/parameters.go index 9cbb3b8..f45e313 100644 --- a/pkg/provisioner/parameters.go +++ b/pkg/provisioner/parameters.go @@ -19,12 +19,20 @@ const ( parameters: parentDataset: tank/volumes hostname: my-zfs-host.localdomain - type: nfs|hostpath + type: nfs|hostPath|auto shareProperties: rw=10.0.0.0/8,no_root_squash node: my-zfs-host reserveSpace: true|false */ +type ProvisioningType string + +const ( + Nfs ProvisioningType = "nfs" + HostPath ProvisioningType = "hostPath" + Auto ProvisioningType = "auto" +) + type ( // ZFSStorageClassParameters represents the parameters on the `StorageClass` // object. It is used to ease access and validate those parameters at run time. @@ -33,18 +41,13 @@ type ( ParentDataset string // Hostname of the target ZFS host. Will be used to connect over SSH. Hostname string - NFS *NFSParameters - HostPath *HostPathParameters + Type ProvisioningType + // NFSShareProperties specifies additional properties to pass to 'zfs create sharenfs=%s'. + NFSShareProperties string + // HostPathNodeName overrides the hostname if the Kubernetes node name is different than the ZFS target host. Used for Affinity + HostPathNodeName string ReserveSpace bool } - NFSParameters struct { - // ShareProperties specifies additional properties to pass to 'zfs create sharenfs=%s'. - ShareProperties string - } - HostPathParameters struct { - // NodeName overrides the hostname if the Kubernetes node name is different than the ZFS target host. Used for Affinity - NodeName string - } ) // NewStorageClassParameters takes a storage class parameters, validates it for invalid configuration and returns a @@ -79,16 +82,26 @@ func NewStorageClassParameters(parameters map[string]string) (*ZFSStorageClassPa typeParam := parameters[TypeParameter] switch typeParam { case "hostpath", "hostPath", "HostPath", "Hostpath", "HOSTPATH": - p.HostPath = &HostPathParameters{NodeName: parameters[NodeNameParameter]} - return p, nil + p.Type = HostPath case "nfs", "Nfs", "NFS": + p.Type = Nfs + case "auto", "Auto", "AUTO": + p.Type = Auto + default: + return nil, fmt.Errorf("invalid '%s' parameter value: %s", TypeParameter, typeParam) + } + + if p.Type == HostPath || p.Type == Auto { + p.HostPathNodeName = parameters[NodeNameParameter] + } + + if p.Type == Nfs || p.Type == Auto { shareProps := parameters[SharePropertiesParameter] if shareProps == "" { shareProps = "on" } - p.NFS = &NFSParameters{ShareProperties: shareProps} - return p, nil - default: - return nil, fmt.Errorf("invalid '%s' parameter value: %s", TypeParameter, typeParam) + p.NFSShareProperties = shareProps } + + return p, nil } diff --git a/pkg/provisioner/parameters_test.go b/pkg/provisioner/parameters_test.go index f105260..6dcd081 100644 --- a/pkg/provisioner/parameters_test.go +++ b/pkg/provisioner/parameters_test.go @@ -77,7 +77,7 @@ func TestNewStorageClassParameters(t *testing.T) { SharePropertiesParameter: "rw", }, }, - want: &ZFSStorageClassParameters{NFS: &NFSParameters{ShareProperties: "rw"}}, + want: &ZFSStorageClassParameters{NFSShareProperties: "rw"}, }, { name: "GivenCorrectSpec_WhenTypeNfsWithoutProperties_ThenReturnNfsParametersWithDefault", @@ -88,7 +88,7 @@ func TestNewStorageClassParameters(t *testing.T) { TypeParameter: "nfs", }, }, - want: &ZFSStorageClassParameters{NFS: &NFSParameters{ShareProperties: "on"}}, + want: &ZFSStorageClassParameters{NFSShareProperties: "on"}, }, { name: "GivenCorrectSpec_WhenTypeHostPath_ThenReturnHostPathParameters", @@ -100,7 +100,7 @@ func TestNewStorageClassParameters(t *testing.T) { NodeNameParameter: "my-node", }, }, - want: &ZFSStorageClassParameters{HostPath: &HostPathParameters{NodeName: "my-node"}}, + want: &ZFSStorageClassParameters{HostPathNodeName: "my-node"}, }, } for _, tt := range tests { @@ -112,8 +112,8 @@ func TestNewStorageClassParameters(t *testing.T) { return } assert.NoError(t, err) - assert.Equal(t, tt.want.NFS, result.NFS) - assert.Equal(t, tt.want.HostPath, result.HostPath) + assert.Equal(t, tt.want.NFSShareProperties, result.NFSShareProperties) + assert.Equal(t, tt.want.HostPathNodeName, result.HostPathNodeName) }) } } diff --git a/pkg/provisioner/provision.go b/pkg/provisioner/provision.go index ec58243..936aabe 100644 --- a/pkg/provisioner/provision.go +++ b/pkg/provisioner/provision.go @@ -24,8 +24,10 @@ func (p *ZFSProvisioner) Provision(ctx context.Context, options controller.Provi datasetPath := fmt.Sprintf("%s/%s", parameters.ParentDataset, options.PVName) properties := make(map[string]string) - if parameters.NFS != nil { - properties["sharenfs"] = parameters.NFS.ShareProperties + useHostPath := true + if parameters.Type == "nfs" || parameters.Type == "auto" && (options.SelectedNode == nil || parameters.HostPathNodeName != options.SelectedNode.Name) { + useHostPath = false + properties[ShareNfsProperty] = parameters.NFSShareProperties } var reclaimPolicy v1.PersistentVolumeReclaimPolicy @@ -77,24 +79,15 @@ func (p *ZFSProvisioner) Provision(ctx context.Context, options controller.Provi Capacity: v1.ResourceList{ v1.ResourceStorage: options.PVC.Spec.Resources.Requests[v1.ResourceStorage], }, - PersistentVolumeSource: createVolumeSource(parameters, dataset), - NodeAffinity: createNodeAffinity(parameters), + PersistentVolumeSource: createVolumeSource(parameters, dataset, useHostPath), + NodeAffinity: createNodeAffinity(parameters, useHostPath), }, } return pv, controller.ProvisioningFinished, nil } -func createVolumeSource(parameters *ZFSStorageClassParameters, dataset *zfs.Dataset) v1.PersistentVolumeSource { - if parameters.NFS != nil { - return v1.PersistentVolumeSource{ - NFS: &v1.NFSVolumeSource{ - Server: parameters.Hostname, - Path: dataset.Mountpoint, - ReadOnly: false, - }, - } - } - if parameters.HostPath != nil { +func createVolumeSource(parameters *ZFSStorageClassParameters, dataset *zfs.Dataset, useHostPath bool) v1.PersistentVolumeSource { + if useHostPath { hostPathType := v1.HostPathDirectory return v1.PersistentVolumeSource{ HostPath: &v1.HostPathVolumeSource{ @@ -103,27 +96,34 @@ func createVolumeSource(parameters *ZFSStorageClassParameters, dataset *zfs.Data }, } } - klog.Exitf("Programmer error: Missing implementation for volume source: %v", parameters) - return v1.PersistentVolumeSource{} + + return v1.PersistentVolumeSource{ + NFS: &v1.NFSVolumeSource{ + Server: parameters.Hostname, + Path: dataset.Mountpoint, + ReadOnly: false, + }, + } } -func createNodeAffinity(parameters *ZFSStorageClassParameters) *v1.VolumeNodeAffinity { - if parameters.HostPath != nil { - node := parameters.HostPath.NodeName - if node == "" { - node = parameters.Hostname - } - return &v1.VolumeNodeAffinity{Required: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ - { - MatchExpressions: []v1.NodeSelectorRequirement{ - { - Values: []string{node}, - Operator: v1.NodeSelectorOpIn, - Key: v1.LabelHostname, - }, +func createNodeAffinity(parameters *ZFSStorageClassParameters, useHostPath bool) *v1.VolumeNodeAffinity { + if !useHostPath { + return nil + } + + node := parameters.HostPathNodeName + if node == "" { + node = parameters.Hostname + } + return &v1.VolumeNodeAffinity{Required: &v1.NodeSelector{NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Values: []string{node}, + Operator: v1.NodeSelectorOpIn, + Key: v1.LabelHostname, }, }, - }}} - } - return nil + }, + }}} } diff --git a/pkg/provisioner/provisioner.go b/pkg/provisioner/provisioner.go index e40db5e..259cf95 100644 --- a/pkg/provisioner/provisioner.go +++ b/pkg/provisioner/provisioner.go @@ -11,6 +11,7 @@ const ( RefQuotaProperty = "refquota" RefReservationProperty = "refreservation" + ShareNfsProperty = "sharenfs" ManagedByProperty = "io.kubernetes.pv.zfs:managed_by" ReclaimPolicyProperty = "io.kubernetes.pv.zfs:reclaim_policy" ) diff --git a/pkg/zfs/zfs.go b/pkg/zfs/zfs.go index 015fa16..cc3196b 100644 --- a/pkg/zfs/zfs.go +++ b/pkg/zfs/zfs.go @@ -114,7 +114,7 @@ func (z *zfsImpl) SetPermissions(dataset *Dataset) error { if dataset.Mountpoint == "" { return fmt.Errorf("undefined mountpoint for dataset: %s", dataset.Name) } - cmd := exec.Command("update-permissions", dataset.Hostname, dataset.Mountpoint) + cmd := exec.Command("chmod", "g+w", dataset.Mountpoint) out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("could not update permissions on '%s': %w: %s", dataset.Hostname, err, out)