Files
velero/pkg/backup/volume_skip_tracker.go
T
Xun Jiang 5afc20d03e Support skipped PVC in VolumeInfos.
* Modify according to comments.
* Rename the pvSkipTracker and related fields to indicate both PVC and PV are supported.

Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
2026-09-18 16:51:00 +08:00

160 lines
4.6 KiB
Go

/*
Copyright 2018 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package backup
import (
"sort"
"sync"
)
type SkippedVolume struct {
PVName string `json:"pvName"`
PVCName string `json:"pvcName,omitempty"`
PVCNamespace string `json:"pvcNamespace,omitempty"`
Reasons []Reason `json:"reasons"`
}
func (s *SkippedVolume) SerializeSkipReasons() string {
ret := ""
for _, reason := range s.Reasons {
ret = ret + reason.Approach + ": " + reason.Reason + ";"
}
return ret
}
type Reason struct {
Approach string `json:"approach"`
Reason string `json:"reason"`
}
// skipVolumeTracker keeps track of volumes (PV/PVC) that have been skipped and the reason why they are skipped.
type skipVolumeTracker struct {
*sync.RWMutex
// volumes is a map of volume(PV/PVC) key to the list of reasons why it is skipped.
// The reasons are stored in a map each key of the map is the backup approach, each approach can have one reason
volumes map[string]map[string]string
// includedVolumes is a set of volume key to be included in the backup, the element in this set should not be in the "volumes" map
includedVolumes map[string]struct{}
// volumeInfo is a map of volume key to SkippedVolume info
volumeInfo map[string]SkippedVolume
}
const (
podVolumeApproach = "podvolume"
csiSnapshotApproach = "csiSnapshot"
volumeSnapshotApproach = "volumeSnapshot"
vsphereSnapshotApproach = "vsphereSnapshot"
anyApproach = "any"
)
func NewSkipVolumeTracker() *skipVolumeTracker {
return &skipVolumeTracker{
RWMutex: &sync.RWMutex{},
volumes: make(map[string]map[string]string),
includedVolumes: make(map[string]struct{}),
volumeInfo: make(map[string]SkippedVolume),
}
}
func getVolumeKey(pvName, pvcName, pvcNamespace string) string {
if pvName != "" {
return "pv:" + pvName
}
if pvcName != "" && pvcNamespace != "" {
return "pvc:" + pvcNamespace + "/" + pvcName
}
return ""
}
// Track tracks the Volume(PV/PVC) with the specified name and the reason why it is skipped
func (pt *skipVolumeTracker) Track(pvName, pvcName, pvcNamespace, approach, reason string) {
pt.Lock()
defer pt.Unlock()
key := getVolumeKey(pvName, pvcName, pvcNamespace)
if key == "" || reason == "" {
return
}
if _, ok := pt.includedVolumes[key]; ok {
return
}
skipReasons := pt.volumes[key]
if skipReasons == nil {
skipReasons = make(map[string]string)
pt.volumes[key] = skipReasons
}
if approach == "" {
approach = anyApproach
}
skipReasons[approach] = reason
pt.volumeInfo[key] = SkippedVolume{
PVName: pvName,
PVCName: pvcName,
PVCNamespace: pvcNamespace,
}
}
// Untrack removes the volume(pv/pvc) with the specified namespace and name.
// This func should be called when the volume is taken for snapshot, regardless native snapshot, CSI snapshot or fsb backup
// therefore, in one backup processed if a volume is Untracked once, it will not be tracked again.
func (pt *skipVolumeTracker) Untrack(pvName, pvcName, pvcNamespace string) {
pt.Lock()
defer pt.Unlock()
key := getVolumeKey(pvName, pvcName, pvcNamespace)
if key == "" {
return
}
pt.includedVolumes[key] = struct{}{}
delete(pt.volumes, key)
delete(pt.volumeInfo, key)
}
// Summary returns the summary of the tracked volumes.
func (pt *skipVolumeTracker) Summary() []SkippedVolume {
pt.RLock()
defer pt.RUnlock()
keys := make([]string, 0, len(pt.volumes))
for key := range pt.volumes {
keys = append(keys, key)
}
sort.Strings(keys)
res := make([]SkippedVolume, 0, len(keys))
for _, key := range keys {
if skipReasons := pt.volumes[key]; len(skipReasons) > 0 {
info := pt.volumeInfo[key]
entry := SkippedVolume{
PVName: info.PVName,
PVCName: info.PVCName,
PVCNamespace: info.PVCNamespace,
Reasons: make([]Reason, 0, len(skipReasons)),
}
approaches := make([]string, 0, len(skipReasons))
for a := range skipReasons {
approaches = append(approaches, a)
}
sort.Strings(approaches)
for _, a := range approaches {
entry.Reasons = append(entry.Reasons, Reason{
Approach: a,
Reason: skipReasons[a],
})
}
res = append(res, entry)
}
}
return res
}