docs(volume-policy): propose PVC volume mode and access mode criteria

Signed-off-by: chlins <chlins.zhang@gmail.com>
This commit is contained in:
chlins
2026-06-05 16:05:11 +08:00
parent 3b545b506b
commit e431df1a34
@@ -0,0 +1,364 @@
# Add PVC VolumeMode and AccessModes as Criteria for Volume Policy
## Abstract
This proposal extends Velero VolumePolicy conditions with two PVC-based criteria, `pvcVolumeMode` and `pvcAccessModes`.
These conditions allow users to select volumes according to the `volumeMode` and `accessModes` of the associated PersistentVolumeClaim (PVC), enabling backup behavior such as skipping block-mode PVCs or choosing a specific backup method for volumes with selected access modes.
## Background
Velero VolumePolicy already supports selecting volumes by attributes such as capacity, storage class, volume source, volume type, PVC labels, and PVC phase.
PVC metadata and spec fields are often the most direct way for users to express the intended storage semantics of a workload.
Kubernetes PVCs include a `spec.volumeMode` field that describes whether the volume is exposed as a filesystem or as a raw block device.
The field supports values such as `Filesystem` and `Block`.
Kubernetes PVCs also include a `spec.accessModes` field that describes how the volume can be mounted.
Common values are `ReadWriteOnce`, `ReadOnlyMany`, `ReadWriteMany`, and `ReadWriteOncePod`.
Kubernetes matching semantics for access modes require all requested modes to be satisfied by the PV/PVC relationship, so this proposal uses an all-of match for `pvcAccessModes`.
## Goals
- Add a `pvcVolumeMode` VolumePolicy condition to match volumes by a single `spec.volumeMode` value of their associated PVC.
- Add a `pvcAccessModes` VolumePolicy condition to match volumes whose associated PVC contains all configured `spec.accessModes` values.
- Keep the new conditions consistent with existing VolumePolicy behavior, where all conditions in a policy must match and the first matching policy wins.
## Non-Goals
- This proposal does not add new VolumePolicy actions.
- This proposal does not change how PVCs are discovered or passed into the resource policy matching code.
- This proposal does not add set-based or negative matching operators such as `NotIn`, `Exists`, or `DoesNotExist`.
- This proposal does not change Kubernetes PVC semantics or validate storage provider capabilities.
## Use-cases/Scenarios
### Skip block-mode PVCs
A user wants to skip volumes whose associated PVC is configured with raw block volume mode.
```yaml
version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Block
action:
type: skip
```
### Snapshot filesystem PVCs
A user wants to use snapshots only for volumes whose associated PVC has filesystem mode.
```yaml
version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Filesystem
action:
type: snapshot
```
### Match PVCs by access mode
A user wants to apply a policy to PVCs that include `ReadWriteOnce` in `spec.accessModes`.
```yaml
version: v1
volumePolicies:
- conditions:
pvcAccessModes:
- ReadWriteOnce
action:
type: skip
```
### Match all configured access modes
A user wants to match volumes whose associated PVC includes both `ReadOnlyMany` and `ReadWriteMany`.
A PVC that includes only one of these modes does not match.
```yaml
version: v1
volumePolicies:
- conditions:
pvcAccessModes:
- ReadOnlyMany
- ReadWriteMany
action:
type: snapshot
```
### Combine PVC spec criteria
A user wants to select block-mode PVCs that also include `ReadWriteOnce`.
Because VolumePolicy conditions are conjunctive, the volume must satisfy both conditions.
```yaml
version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Block
pvcAccessModes:
- ReadWriteOnce
action:
type: snapshot
```
## High-Level Design
The VolumePolicy condition schema is extended with two optional fields, `pvcVolumeMode` and `pvcAccessModes`.
`pvcVolumeMode` is represented as a single string value in the resource policy YAML.
`pvcAccessModes` is represented as a string list in the resource policy YAML.
The internal `structuredVolume` representation is extended to store the associated PVC's volume mode and access modes.
The existing PVC parsing path populates these fields when a PVC is available in `VolumeFilterData`.
The policy builder creates a `pvcVolumeModeCondition` when `pvcVolumeMode` is specified and creates a `pvcAccessModesCondition` when `pvcAccessModes` is specified.
The existing matching flow remains unchanged: each condition implements the `volumeCondition` interface, all conditions in a policy must match, and the first matching policy's action is returned.
## Detailed Design
### Resource policy YAML schema
Two new fields are added under `volumePolicies[].conditions`.
```yaml
version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Block
pvcAccessModes:
- ReadWriteOnce
- ReadWriteMany
action:
type: snapshot
```
`pvcVolumeMode` is a string.
The intended values are Kubernetes PVC volume mode values, including `Filesystem` and `Block`.
The condition matches only when the PVC volume mode value observed by Velero exactly equals the configured value.
Matching is case-sensitive, so `block` does not match `Block`.
`pvcAccessModes` is a list of strings.
The intended values are Kubernetes PVC access mode values, including `ReadWriteOnce`, `ReadOnlyMany`, `ReadWriteMany`, and `ReadWriteOncePod`.
The condition matches only when every configured access mode is present in the PVC's `spec.accessModes`.
Matching is case-sensitive, so `readwriteonce` does not match `ReadWriteOnce`.
The implementation validates that `pvcVolumeMode`, when present, is a string.
The implementation validates that `pvcAccessModes`, when present, is a list of strings.
The implementation does not strictly reject unknown string values so that the condition format remains tolerant of Kubernetes additions or storage-provider-specific behavior.
Unknown values simply do not match unless the PVC has the same string value.
### Volume condition struct
The parsed condition struct is extended as follows.
```go
type volumeConditions struct {
Capacity string `yaml:"capacity,omitempty"`
StorageClass []string `yaml:"storageClass,omitempty"`
NFS *nFSVolumeSource `yaml:"nfs,omitempty"`
CSI *csiVolumeSource `yaml:"csi,omitempty"`
VolumeTypes []SupportedVolume `yaml:"volumeTypes,omitempty"`
PVCLabels map[string]string `yaml:"pvcLabels,omitempty"`
PVCPhase []string `yaml:"pvcPhase,omitempty"`
PVCVolumeMode string `yaml:"pvcVolumeMode,omitempty"`
PVCAccessModes []string `yaml:"pvcAccessModes,omitempty"`
}
```
### Structured volume data
The internal `structuredVolume` is extended with `pvcVolumeMode` and `pvcAccessModes`.
```go
type structuredVolume struct {
capacity resource.Quantity
storageClass string
nfs *nFSVolumeSource
csi *csiVolumeSource
volumeType SupportedVolume
pvcLabels map[string]string
pvcPhase string
pvcVolumeMode string
pvcAccessModes []string
}
```
When a PVC is available, `parsePVC` extracts PVC attributes into `structuredVolume` for later condition evaluation.
This parsing step does not create or imply a `pvcVolumeMode` policy condition; `pvcVolumeMode` only constrains matching when the user explicitly configures `conditions.pvcVolumeMode` in the VolumePolicy.
Velero uses `pvc.Spec.VolumeMode` as-is when it is present.
If `pvc.Spec.VolumeMode` is nil, `pvcVolumeMode` remains empty and does not match any non-empty `pvcVolumeMode` condition.
If `pvc.Spec.AccessModes` is empty, `pvcAccessModes` remains empty and does not match any non-empty `pvcAccessModes` condition.
```go
func (s *structuredVolume) parsePVC(pvc *corev1api.PersistentVolumeClaim) {
if pvc != nil {
if len(pvc.GetLabels()) > 0 {
s.pvcLabels = pvc.Labels
}
s.pvcPhase = string(pvc.Status.Phase)
if pvc.Spec.VolumeMode != nil {
s.pvcVolumeMode = string(*pvc.Spec.VolumeMode)
}
if len(pvc.Spec.AccessModes) > 0 {
s.pvcAccessModes = make([]string, 0, len(pvc.Spec.AccessModes))
for _, accessMode := range pvc.Spec.AccessModes {
s.pvcAccessModes = append(s.pvcAccessModes, string(accessMode))
}
}
}
}
```
### PVC volume mode condition
`pvcVolumeModeCondition` matches when the associated PVC's parsed volume mode exactly equals the configured value.
The comparison is case-sensitive and does not normalize values.
An empty configured value is treated as no constraint and always matches, consistent with other VolumePolicy conditions.
A non-empty configured value does not match if no PVC volume mode is available.
```go
type pvcVolumeModeCondition struct {
volumeMode string
}
func (c *pvcVolumeModeCondition) match(v *structuredVolume) bool {
if c.volumeMode == "" {
return true
}
if v.pvcVolumeMode == "" {
return false
}
return v.pvcVolumeMode == c.volumeMode
}
```
### PVC access modes condition
`pvcAccessModesCondition` matches when all configured access modes are present in the associated PVC's access modes.
This all-of match aligns with Kubernetes access mode matching semantics.
The comparison is case-sensitive and does not normalize values.
An empty configured list is treated as no constraint and always matches.
A non-empty configured list does not match if the structured volume has no PVC access modes.
```go
type pvcAccessModesCondition struct {
accessModes []string
}
func (c *pvcAccessModesCondition) match(v *structuredVolume) bool {
if len(c.accessModes) == 0 {
return true
}
if len(v.pvcAccessModes) == 0 {
return false
}
for _, conditionAccessMode := range c.accessModes {
if !slices.Contains(v.pvcAccessModes, conditionAccessMode) {
return false
}
}
return true
}
```
### Condition validation
Both `pvcVolumeModeCondition` and `pvcAccessModesCondition` implement the `validate()` method required by the `volumeCondition` interface.
The `validate()` method returns nil for both conditions.
```go
func (c *pvcVolumeModeCondition) validate() error {
return nil
}
func (c *pvcAccessModesCondition) validate() error {
return nil
}
```
YAML shape validation is handled when resource policy conditions are unmarshaled.
`pvcVolumeMode` must be a string, and `pvcAccessModes` must be a list of strings.
Condition-level validation intentionally does not reject unknown string values.
This keeps the policy format forward-compatible with future Kubernetes values and consistent with other string-based VolumePolicy conditions.
Unknown values simply do not match normal PVCs unless the PVC contains the same value.
### Policy builder integration
The policy builder appends the new conditions only when the corresponding YAML fields are present.
```go
func (p *Policies) BuildPolicy(resPolicies *ResourcePolicies) error {
for _, vp := range resPolicies.VolumePolicies {
con, err := unmarshalVolConditions(vp.Conditions)
if err != nil {
return errors.WithStack(err)
}
// Existing conditions are appended here.
if con.PVCVolumeMode != "" {
volP.conditions = append(volP.conditions, &pvcVolumeModeCondition{volumeMode: con.PVCVolumeMode})
}
if len(con.PVCAccessModes) > 0 {
volP.conditions = append(volP.conditions, &pvcAccessModesCondition{accessModes: con.PVCAccessModes})
}
}
return nil
}
```
### Matching behavior with other conditions
The new conditions follow the existing VolumePolicy matching behavior.
Within a single policy, every configured condition must match.
If `pvcVolumeMode` is omitted from a policy, Velero does not add a volume mode condition and the policy does not restrict volume mode.
`pvcVolumeMode` and `pvcAccessModes` are PVC-specific conditions and only match when the volume policy evaluation has associated PVC data.
For non-PVC volumes such as `emptyDir`, `configMap`, or inline volumes without an associated PVC, the parsed PVC fields are empty and policies requiring `pvcVolumeMode` or `pvcAccessModes` do not match.
Across multiple policies, the first matching policy wins.
For example, this policy matches only PVC-backed volumes that are both `Block` mode and have `ReadWriteOnce` in their access modes.
```yaml
version: v1
volumePolicies:
- conditions:
pvcVolumeMode: Block
pvcAccessModes:
- ReadWriteOnce
action:
type: snapshot
```
## Alternatives Considered
### A single `pvcSpec` condition object
One alternative is to add a nested object such as `pvcSpec.volumeMode` and `pvcSpec.accessModes`.
This was not chosen because existing PVC-based VolumePolicy conditions use flat field names such as `pvcLabels` and `pvcPhase`.
Flat names keep the YAML concise and consistent with existing conditions.
### List-based `pvcVolumeMode`
One alternative is to make `pvcVolumeMode` a list, similar to `pvcPhase`.
This was not chosen because Kubernetes PVC `spec.volumeMode` is a single value and the policy condition is intended to describe an exact match against that value.
Using a string avoids implying that multiple volume modes can apply to one PVC.
### Any-of access mode matching
Another alternative is to make `pvcAccessModes` match when any configured access mode is present on the PVC.
This was not chosen because Kubernetes access mode matching is based on satisfying all requested access modes.
Using all-of matching avoids selecting PVCs that satisfy only part of the requested access mode set.
### Strict validation of allowed Kubernetes values
Another alternative is to reject `pvcVolumeMode` or `pvcAccessModes` values that are not currently known Kubernetes constants.
This was not chosen because accepting strings is more forward-compatible and keeps behavior consistent with other string-based resource policy conditions.
Invalid or unknown values naturally fail to match unless a PVC has the same value.
## Security Considerations
This proposal does not introduce new privileges or access to additional Kubernetes resources.
It only uses PVC data already available to the volume policy matching path.
The new conditions can cause Velero to skip or choose different backup actions for matched volumes.
Users should review policy configuration carefully because an overly broad policy can exclude data from backup or select an unintended backup method.
## Compatibility
The new fields are optional and do not affect existing resource policy files.
Existing VolumePolicy behavior remains unchanged when `pvcVolumeMode` and `pvcAccessModes` are not configured.
PVCs without a parsed `spec.volumeMode` value do not match non-empty `pvcVolumeMode` conditions.
PVCs without `spec.accessModes` do not match non-empty `pvcAccessModes` conditions.
Unknown `pvcVolumeMode` or `pvcAccessModes` string values in a policy are accepted as strings but will not match normal Kubernetes PVCs unless the PVC contains the same string value.
## Implementation
Implementation requires changes in the resource policies package and documentation.
- Extend `volumeConditions` with `PVCVolumeMode string` and `PVCAccessModes []string`.
- Extend `structuredVolume` with `pvcVolumeMode string` and `pvcAccessModes []string`.
- Update `parsePVC` to populate the new fields from the PVC spec.
- Add `pvcVolumeModeCondition` and `pvcAccessModesCondition` implementations.
- Update `Policies.BuildPolicy` to append the new conditions.
- Add YAML type validation to ensure `pvcVolumeMode` is a string and `pvcAccessModes` is a string list.
- Add unit tests for parsing, validation, condition matching, and end-to-end `GetMatchAction` behavior.
- Update user documentation in `site/content/docs/main/resource-filtering.md`.