diff --git a/csi/controller_server.go b/csi/controller_server.go index b58f7fd3e8..b1b60984ba 100644 --- a/csi/controller_server.go +++ b/csi/controller_server.go @@ -69,6 +69,8 @@ func NewControllerServer(apiClient *longhornclient.RancherClient, nodeID string) accessModes: getVolumeCapabilityAccessModes( []csi.VolumeCapability_AccessMode_Mode{ csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER, + csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER, csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, }), log: logrus.StandardLogger().WithField("component", "csi-controller-server"), @@ -220,6 +222,10 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol volumeParameters["share"] = "true" break } + if requireExclusiveAccess(nil, cap) { + volumeParameters["exclusive"] = "true" + break + } } vol, err := getVolumeOptions(volumeID, volumeParameters) @@ -454,6 +460,13 @@ func (cs *ControllerServer) ControllerPublishVolume(ctx context.Context, req *cs } } + if requireExclusiveAccess(volume, volumeCapability) { + volume, err = cs.updateVolumeAccessMode(volume, longhorn.AccessModeReadWriteOncePod) + if err != nil { + return nil, err + } + } + // TODO: JM Restore should be handled by the volume attach call, consider returning `codes.Aborted` // TODO: JM should readiness be handled by the caller? // Most of the readiness conditions are covered by the attach, except auto attachment which requires changes to the design diff --git a/csi/util.go b/csi/util.go index 1b2020d3f3..e2c8cf9c47 100644 --- a/csi/util.go +++ b/csi/util.go @@ -107,6 +107,16 @@ func getVolumeOptions(volumeID string, volOptions map[string]string) (*longhornc vol.StaleReplicaTimeout = defaultStaleReplicaTimeout } + if exclusive, ok := volOptions["exclusive"]; ok { + isExclusive, err := strconv.ParseBool(exclusive) + if err != nil { + return nil, errors.Wrap(err, "invalid parameter exclusive") + } + if isExclusive && vol.AccessMode == string(longhorn.AccessModeReadWriteOnce) { + vol.AccessMode = string(longhorn.AccessModeReadWriteOncePod) + } + } + if share, ok := volOptions["share"]; ok { isShared, err := strconv.ParseBool(share) if err != nil { @@ -115,11 +125,13 @@ func getVolumeOptions(volumeID string, volOptions map[string]string) (*longhornc if isShared { vol.AccessMode = string(longhorn.AccessModeReadWriteMany) - } else { - vol.AccessMode = string(longhorn.AccessModeReadWriteOnce) } } + if vol.AccessMode == "" { + vol.AccessMode = string(longhorn.AccessModeReadWriteOnce) + } + if migratable, ok := volOptions["migratable"]; ok { isMigratable, err := strconv.ParseBool(migratable) if err != nil { @@ -453,6 +465,20 @@ func requiresSharedAccess(vol *longhornclient.Volume, cap *csi.VolumeCapability) mode == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER } +func requireExclusiveAccess(vol *longhornclient.Volume, cap *csi.VolumeCapability) bool { + isExclusive := false + if vol != nil { + isExclusive = vol.AccessMode == string(longhorn.AccessModeReadWriteOncePod) + } + + mode := csi.VolumeCapability_AccessMode_UNKNOWN + if cap != nil { + mode = cap.AccessMode.Mode + } + + return isExclusive || mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER +} + func getStageBlockVolumePath(stagingTargetPath, volumeID string) string { return filepath.Join(stagingTargetPath, volumeID) } diff --git a/datastore/kubernetes.go b/datastore/kubernetes.go index 2142c40501..8013082790 100644 --- a/datastore/kubernetes.go +++ b/datastore/kubernetes.go @@ -1015,8 +1015,11 @@ func NewPVManifest(size int64, pvName, volumeName, storageClassName, fsType stri // NewPVCManifestForVolume returns a new PersistentVolumeClaim object for a longhorn volume func NewPVCManifestForVolume(v *longhorn.Volume, pvName, ns, pvcName, storageClassName string) *corev1.PersistentVolumeClaim { accessMode := corev1.ReadWriteOnce - if v.Spec.AccessMode == longhorn.AccessModeReadWriteMany { + switch v.Spec.AccessMode { + case longhorn.AccessModeReadWriteMany: accessMode = corev1.ReadWriteMany + case longhorn.AccessModeReadWriteOncePod: + accessMode = corev1.ReadWriteOncePod } return NewPVCManifest(v.Spec.Size, pvName, ns, pvcName, storageClassName, accessMode) diff --git a/k8s/pkg/apis/longhorn/v1beta1/volume.go b/k8s/pkg/apis/longhorn/v1beta1/volume.go index e74e9bc8d3..a2f04f794f 100644 --- a/k8s/pkg/apis/longhorn/v1beta1/volume.go +++ b/k8s/pkg/apis/longhorn/v1beta1/volume.go @@ -59,8 +59,9 @@ const ( type AccessMode string const ( - AccessModeReadWriteOnce = AccessMode("rwo") - AccessModeReadWriteMany = AccessMode("rwx") + AccessModeReadWriteOnce = AccessMode("rwo") + AccessModeReadWriteOncePod = AccessMode("rwop") + AccessModeReadWriteMany = AccessMode("rwx") ) type ReplicaAutoBalance string diff --git a/k8s/pkg/apis/longhorn/v1beta2/volume.go b/k8s/pkg/apis/longhorn/v1beta2/volume.go index 528a4d2870..93ecc0f1c8 100644 --- a/k8s/pkg/apis/longhorn/v1beta2/volume.go +++ b/k8s/pkg/apis/longhorn/v1beta2/volume.go @@ -53,12 +53,13 @@ const ( DataLocalityStrictLocal = DataLocality("strict-local") ) -// +kubebuilder:validation:Enum=rwo;rwx +// +kubebuilder:validation:Enum=rwo;rwop,rwx type AccessMode string const ( - AccessModeReadWriteOnce = AccessMode("rwo") - AccessModeReadWriteMany = AccessMode("rwx") + AccessModeReadWriteOnce = AccessMode("rwo") + AccessModeReadWriteOncePod = AccessMode("rwop") + AccessModeReadWriteMany = AccessMode("rwx") ) // +kubebuilder:validation:Enum=ignored;disabled;least-effort;best-effort diff --git a/types/types.go b/types/types.go index e3adfd8430..bd2bfefeeb 100644 --- a/types/types.go +++ b/types/types.go @@ -844,7 +844,7 @@ func ValidateDataLocality(mode longhorn.DataLocality) error { } func ValidateAccessMode(mode longhorn.AccessMode) error { - if mode != longhorn.AccessModeReadWriteMany && mode != longhorn.AccessModeReadWriteOnce { + if mode != longhorn.AccessModeReadWriteMany && mode != longhorn.AccessModeReadWriteOnce && mode != longhorn.AccessModeReadWriteOncePod { return fmt.Errorf("invalid access mode: %v", mode) } return nil