mirror of
https://github.com/vmware-tanzu/velero.git
synced 2025-12-23 06:15:21 +00:00
Some checks failed
Run the E2E test on kind / build (push) Failing after 6m23s
Run the E2E test on kind / setup-test-matrix (push) Successful in 3s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / Build (push) Failing after 38s
Close stale issues and PRs / stale (push) Successful in 9s
Trivy Nightly Scan / Trivy nightly scan (velero, main) (push) Failing after 1m5s
Trivy Nightly Scan / Trivy nightly scan (velero-restore-helper, main) (push) Failing after 55s
* Add labels as a criteria for volume policy Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> add changelog file Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> handle err Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> use labels selector.matches Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> make update Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> remove fetching pvc from volume policy filtering Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> add more ut coverage Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> * minor updates Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> use VolumeFilterData struct in GetMatchAction func Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> update parsePVC func and add more ut Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> lint fix Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com> --------- Signed-off-by: Shubham Pampattiwar <spampatt@redhat.com>
274 lines
6.0 KiB
Go
274 lines
6.0 KiB
Go
/*
|
|
Copyright 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 resourcepolicies
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
|
|
"github.com/pkg/errors"
|
|
"gopkg.in/yaml.v3"
|
|
corev1api "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
)
|
|
|
|
type volPolicy struct {
|
|
action Action
|
|
conditions []volumeCondition
|
|
}
|
|
|
|
type volumeCondition interface {
|
|
match(v *structuredVolume) bool
|
|
validate() error
|
|
}
|
|
|
|
// capacity consist of the lower and upper boundary
|
|
type capacity struct {
|
|
lower resource.Quantity
|
|
upper resource.Quantity
|
|
}
|
|
|
|
type structuredVolume struct {
|
|
capacity resource.Quantity
|
|
storageClass string
|
|
nfs *nFSVolumeSource
|
|
csi *csiVolumeSource
|
|
volumeType SupportedVolume
|
|
pvcLabels map[string]string
|
|
}
|
|
|
|
func (s *structuredVolume) parsePV(pv *corev1api.PersistentVolume) {
|
|
s.capacity = *pv.Spec.Capacity.Storage()
|
|
s.storageClass = pv.Spec.StorageClassName
|
|
nfs := pv.Spec.NFS
|
|
if nfs != nil {
|
|
s.nfs = &nFSVolumeSource{Server: nfs.Server, Path: nfs.Path}
|
|
}
|
|
|
|
csi := pv.Spec.CSI
|
|
if csi != nil {
|
|
s.csi = &csiVolumeSource{Driver: csi.Driver, VolumeAttributes: csi.VolumeAttributes}
|
|
}
|
|
|
|
s.volumeType = getVolumeTypeFromPV(pv)
|
|
}
|
|
|
|
func (s *structuredVolume) parsePVC(pvc *corev1api.PersistentVolumeClaim) {
|
|
if pvc != nil && len(pvc.GetLabels()) > 0 {
|
|
s.pvcLabels = pvc.Labels
|
|
}
|
|
}
|
|
|
|
func (s *structuredVolume) parsePodVolume(vol *corev1api.Volume) {
|
|
nfs := vol.NFS
|
|
if nfs != nil {
|
|
s.nfs = &nFSVolumeSource{Server: nfs.Server, Path: nfs.Path}
|
|
}
|
|
|
|
csi := vol.CSI
|
|
if csi != nil {
|
|
s.csi = &csiVolumeSource{Driver: csi.Driver, VolumeAttributes: csi.VolumeAttributes}
|
|
}
|
|
|
|
s.volumeType = getVolumeTypeFromVolume(vol)
|
|
}
|
|
|
|
// pvcLabelsCondition defines a condition that matches if the PVC's labels contain all the provided key/value pairs.
|
|
type pvcLabelsCondition struct {
|
|
labels map[string]string
|
|
}
|
|
|
|
func (c *pvcLabelsCondition) match(v *structuredVolume) bool {
|
|
// No labels specified: always match.
|
|
if len(c.labels) == 0 {
|
|
return true
|
|
}
|
|
if v.pvcLabels == nil {
|
|
return false
|
|
}
|
|
selector := labels.SelectorFromSet(c.labels)
|
|
return selector.Matches(labels.Set(v.pvcLabels))
|
|
}
|
|
|
|
func (c *pvcLabelsCondition) validate() error {
|
|
return nil
|
|
}
|
|
|
|
type capacityCondition struct {
|
|
capacity capacity
|
|
}
|
|
|
|
func (c *capacityCondition) match(v *structuredVolume) bool {
|
|
return c.capacity.isInRange(v.capacity)
|
|
}
|
|
|
|
type storageClassCondition struct {
|
|
storageClass []string
|
|
}
|
|
|
|
func (s *storageClassCondition) match(v *structuredVolume) bool {
|
|
if len(s.storageClass) == 0 {
|
|
return true
|
|
}
|
|
|
|
if v.storageClass == "" {
|
|
return false
|
|
}
|
|
|
|
for _, sc := range s.storageClass {
|
|
if v.storageClass == sc {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
type nfsCondition struct {
|
|
nfs *nFSVolumeSource
|
|
}
|
|
|
|
func (c *nfsCondition) match(v *structuredVolume) bool {
|
|
if c.nfs == nil {
|
|
return true
|
|
}
|
|
if v.nfs == nil {
|
|
return false
|
|
}
|
|
|
|
if c.nfs.Path == "" {
|
|
if c.nfs.Server == "" { // match nfs: {}
|
|
return v.nfs != nil
|
|
}
|
|
if c.nfs.Server != v.nfs.Server {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
if c.nfs.Path != v.nfs.Path {
|
|
return false
|
|
}
|
|
if c.nfs.Server == "" {
|
|
return true
|
|
}
|
|
if c.nfs.Server != v.nfs.Server {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
type csiCondition struct {
|
|
csi *csiVolumeSource
|
|
}
|
|
|
|
func (c *csiCondition) match(v *structuredVolume) bool {
|
|
if c.csi == nil {
|
|
return true
|
|
}
|
|
|
|
if c.csi.Driver == "" { // match csi: {}
|
|
return v.csi != nil
|
|
}
|
|
|
|
if v.csi == nil {
|
|
return false
|
|
}
|
|
|
|
if c.csi.Driver != v.csi.Driver {
|
|
return false
|
|
}
|
|
|
|
if len(c.csi.VolumeAttributes) == 0 {
|
|
return true
|
|
}
|
|
|
|
if len(v.csi.VolumeAttributes) == 0 {
|
|
return false
|
|
}
|
|
|
|
for key, value := range c.csi.VolumeAttributes {
|
|
if value != v.csi.VolumeAttributes[key] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// parseCapacity parse string into capacity format
|
|
func parseCapacity(cap string) (*capacity, error) {
|
|
if cap == "" {
|
|
cap = ","
|
|
}
|
|
capacities := strings.Split(cap, ",")
|
|
var quantities []resource.Quantity
|
|
if len(capacities) != 2 {
|
|
return nil, fmt.Errorf("wrong format of Capacity %v", cap)
|
|
}
|
|
|
|
for _, v := range capacities {
|
|
if strings.TrimSpace(v) == "" {
|
|
// case similar "10Gi,"
|
|
// if empty, the quantity will assigned with 0
|
|
quantities = append(quantities, *resource.NewQuantity(int64(0), resource.DecimalSI))
|
|
} else {
|
|
quantity, err := resource.ParseQuantity(strings.TrimSpace(v))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("wrong format of Capacity %v with err %v", v, err)
|
|
}
|
|
quantities = append(quantities, quantity)
|
|
}
|
|
}
|
|
|
|
return &capacity{lower: quantities[0], upper: quantities[1]}, nil
|
|
}
|
|
|
|
// isInRange returns true if the quantity y is in range of capacity, or it returns false
|
|
func (c *capacity) isInRange(y resource.Quantity) bool {
|
|
if c.lower.IsZero() && c.upper.Cmp(y) >= 0 {
|
|
// [0, a] y
|
|
return true
|
|
}
|
|
if c.upper.IsZero() && c.lower.Cmp(y) <= 0 {
|
|
// [b, 0] y
|
|
return true
|
|
}
|
|
if !c.lower.IsZero() && !c.upper.IsZero() {
|
|
// [a, b] y
|
|
return c.lower.Cmp(y) <= 0 && c.upper.Cmp(y) >= 0
|
|
}
|
|
return false
|
|
}
|
|
|
|
// unmarshalVolConditions parse map[string]any into volumeConditions format
|
|
// and validate key fields of the map.
|
|
func unmarshalVolConditions(con map[string]any) (*volumeConditions, error) {
|
|
volConditons := &volumeConditions{}
|
|
buffer := new(bytes.Buffer)
|
|
err := yaml.NewEncoder(buffer).Encode(con)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to encode volume conditions")
|
|
}
|
|
|
|
if err := decodeStruct(buffer, volConditons); err != nil {
|
|
return nil, errors.Wrap(err, "failed to decode volume conditions")
|
|
}
|
|
return volConditons, nil
|
|
}
|