Merge branch 'main' into batch-delete-snapshot

This commit is contained in:
Lyndon-Li
2024-02-18 14:54:01 +08:00
164 changed files with 1121 additions and 489 deletions
+1 -1
View File
@@ -159,7 +159,7 @@ type BackupSpec struct {
CSISnapshotTimeout metav1.Duration `json:"csiSnapshotTimeout,omitempty"`
// ItemOperationTimeout specifies the time used to wait for asynchronous BackupItemAction operations
// The default value is 1 hour.
// The default value is 4 hour.
// +optional
ItemOperationTimeout metav1.Duration `json:"itemOperationTimeout,omitempty"`
// ResourcePolicy specifies the referenced resource policies that backup should follow
+1 -1
View File
@@ -115,7 +115,7 @@ type RestoreSpec struct {
ExistingResourcePolicy PolicyType `json:"existingResourcePolicy,omitempty"`
// ItemOperationTimeout specifies the time used to wait for RestoreItemAction operations
// The default value is 1 hour.
// The default value is 4 hour.
// +optional
ItemOperationTimeout metav1.Duration `json:"itemOperationTimeout,omitempty"`
+4 -4
View File
@@ -35,8 +35,8 @@ type DynamicFactory interface {
// ClientForGroupVersionResource returns a Dynamic client for the given group/version
// and resource for the given namespace.
ClientForGroupVersionResource(gv schema.GroupVersion, resource metav1.APIResource, namespace string) (Dynamic, error)
// DynamicSharedInformerFactoryForNamespace returns a DynamicSharedInformerFactory for the given namespace.
DynamicSharedInformerFactoryForNamespace(namespace string) dynamicinformer.DynamicSharedInformerFactory
// DynamicSharedInformerFactory returns a DynamicSharedInformerFactory.
DynamicSharedInformerFactory() dynamicinformer.DynamicSharedInformerFactory
}
// dynamicFactory implements DynamicFactory.
@@ -55,8 +55,8 @@ func (f *dynamicFactory) ClientForGroupVersionResource(gv schema.GroupVersion, r
}, nil
}
func (f *dynamicFactory) DynamicSharedInformerFactoryForNamespace(namespace string) dynamicinformer.DynamicSharedInformerFactory {
return dynamicinformer.NewFilteredDynamicSharedInformerFactory(f.dynamicClient, time.Minute, namespace, nil)
func (f *dynamicFactory) DynamicSharedInformerFactory() dynamicinformer.DynamicSharedInformerFactory {
return dynamicinformer.NewDynamicSharedInformerFactory(f.dynamicClient, time.Minute)
}
// Creator creates an object.
+1 -1
View File
@@ -155,7 +155,7 @@ func NewInstallOptions() *Options {
DefaultVolumesToFsBackup: false,
UploaderType: uploader.KopiaType,
DefaultSnapshotMoveData: false,
DisableInformerCache: true,
DisableInformerCache: false,
ScheduleSkipImmediately: false,
}
}
+6
View File
@@ -171,6 +171,12 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
schedule.Spec.Template.ResourcePolicy = &v1.TypedLocalObjectReference{Kind: resourcepolicies.ConfigmapRefType, Name: o.BackupOptions.ResPoliciesConfigmap}
}
if o.BackupOptions.ParallelFilesUpload > 0 {
schedule.Spec.Template.UploaderConfig = &api.UploaderConfigForBackup{
ParallelFilesUpload: o.BackupOptions.ParallelFilesUpload,
}
}
if printed, err := output.PrintWithFormat(c, schedule); printed || err != nil {
return err
}
+9 -3
View File
@@ -33,6 +33,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
corev1api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@@ -88,8 +89,8 @@ const (
defaultResourceTerminatingTimeout = 10 * time.Minute
// server's client default qps and burst
defaultClientQPS float32 = 20.0
defaultClientBurst int = 30
defaultClientQPS float32 = 100.0
defaultClientBurst int = 100
defaultClientPageSize int = 500
defaultProfilerAddress = "localhost:6060"
@@ -467,7 +468,12 @@ func setDefaultBackupLocation(ctx context.Context, client ctrlclient.Client, nam
backupLocation := &velerov1api.BackupStorageLocation{}
if err := client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: defaultBackupLocation}, backupLocation); err != nil {
return errors.WithStack(err)
if apierrors.IsNotFound(err) {
logger.WithField("backupStorageLocation", defaultBackupLocation).WithError(err).Warn("Failed to set default backup storage location at server start")
return nil
} else {
return errors.WithStack(err)
}
}
if !backupLocation.Spec.Default {
+9
View File
@@ -409,4 +409,13 @@ func Test_setDefaultBackupLocation(t *testing.T) {
nonDefaultLocation := &velerov1api.BackupStorageLocation{}
require.Nil(t, c.Get(context.Background(), client.ObjectKey{Namespace: "velero", Name: "non-default"}, nonDefaultLocation))
assert.False(t, nonDefaultLocation.Spec.Default)
// no default location specified
c = fake.NewClientBuilder().WithScheme(scheme).Build()
err := setDefaultBackupLocation(context.Background(), c, "velero", "", logrus.New())
assert.NoError(t, err)
// no default location created
err = setDefaultBackupLocation(context.Background(), c, "velero", "default", logrus.New())
assert.NoError(t, err)
}
+3 -2
View File
@@ -252,8 +252,6 @@ func (b *backupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
if err := kubeutil.PatchResource(original, request.Backup, b.kbClient); err != nil {
return ctrl.Result{}, errors.Wrapf(err, "error updating Backup status to %s", request.Status.Phase)
}
// store ref to just-updated item for creating patch
original = request.Backup.DeepCopy()
backupScheduleName := request.GetLabels()[velerov1api.ScheduleNameLabel]
@@ -265,6 +263,9 @@ func (b *backupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
return ctrl.Result{}, nil
}
// store ref to just-updated item for creating patch
original = request.Backup.DeepCopy()
b.backupTracker.Add(request.Namespace, request.Name)
defer func() {
switch request.Status.Phase {
@@ -76,7 +76,11 @@ func (r *BackupRepoReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(&velerov1api.BackupRepository{}).
Watches(s, nil).
Watches(&source.Kind{Type: &velerov1api.BackupStorageLocation{}}, kube.EnqueueRequestsFromMapUpdateFunc(r.invalidateBackupReposForBSL),
builder.WithPredicates(kube.NewUpdateEventPredicate(r.needInvalidBackupRepo))).
builder.WithPredicates(
// When BSL updates, check if the backup repositories need to be invalidated
kube.NewUpdateEventPredicate(r.needInvalidBackupRepo),
// When BSL is created, invalidate any backup repositories that reference it
kube.NewCreateEventPredicate(func(client.Object) bool { return true }))).
Complete(r)
}
@@ -90,13 +94,13 @@ func (r *BackupRepoReconciler) invalidateBackupReposForBSL(bslObj client.Object)
}).AsSelector(),
}
if err := r.List(context.TODO(), list, options); err != nil {
r.logger.WithField("BSL", bsl.Name).WithError(err).Error("unable to list BackupRepositorys")
r.logger.WithField("BSL", bsl.Name).WithError(err).Error("unable to list BackupRepositories")
return []reconcile.Request{}
}
for i := range list.Items {
r.logger.WithField("BSL", bsl.Name).Infof("Invalidating Backup Repository %s", list.Items[i].Name)
if err := r.patchBackupRepository(context.Background(), &list.Items[i], repoNotReady("re-establish on BSL change")); err != nil {
if err := r.patchBackupRepository(context.Background(), &list.Items[i], repoNotReady("re-establish on BSL change or create")); err != nil {
r.logger.WithField("BSL", bsl.Name).WithError(err).Errorf("fail to patch BackupRepository %s", list.Items[i].Name)
}
}
@@ -104,6 +108,7 @@ func (r *BackupRepoReconciler) invalidateBackupReposForBSL(bslObj client.Object)
return []reconcile.Request{}
}
// needInvalidBackupRepo returns true if the BSL's storage type, bucket, prefix, CACert, or config has changed
func (r *BackupRepoReconciler) needInvalidBackupRepo(oldObj client.Object, newObj client.Object) bool {
oldBSL := oldObj.(*velerov1api.BackupStorageLocation)
newBSL := newObj.(*velerov1api.BackupStorageLocation)
@@ -260,7 +265,7 @@ func (r *BackupRepoReconciler) getRepositoryMaintenanceFrequency(req *velerov1ap
r.logger.WithError(err).WithField("returned frequency", frequency).Warn("Failed to get maitanance frequency, use the default one")
frequency = defaultMaintainFrequency
} else {
r.logger.WithField("frequency", frequency).Info("Set matainenance according to repository suggestion")
r.logger.WithField("frequency", frequency).Info("Set maintenance according to repository suggestion")
}
return frequency
+67 -5
View File
@@ -19,7 +19,13 @@ package config
import (
"context"
"fmt"
"os"
"time"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
@@ -31,13 +37,13 @@ import (
const (
// AWS specific environment variable
awsProfileEnvVar = "AWS_PROFILE"
awsRoleEnvVar = "AWS_ROLE_ARN"
awsKeyIDEnvVar = "AWS_ACCESS_KEY_ID"
awsSecretKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
awsSessTokenEnvVar = "AWS_SESSION_TOKEN"
awsProfileKey = "profile"
awsCredentialsFileEnvVar = "AWS_SHARED_CREDENTIALS_FILE"
awsConfigFileEnvVar = "AWS_CONFIG_FILE"
awsDefaultProfile = "default"
)
// GetS3ResticEnvVars gets the environment variables that restic
@@ -72,10 +78,6 @@ func GetS3ResticEnvVars(config map[string]string) (map[string]string, error) {
// GetS3Credentials gets the S3 credential values according to the information
// of the provided config or the system's environment variables
func GetS3Credentials(config map[string]string) (*aws.Credentials, error) {
if os.Getenv(awsRoleEnvVar) != "" {
return nil, nil
}
var opts []func(*awsconfig.LoadOptions) error
credentialsFile := config[CredentialsFileKey]
if credentialsFile == "" {
@@ -93,6 +95,25 @@ func GetS3Credentials(config map[string]string) (*aws.Credentials, error) {
if err != nil {
return nil, err
}
if credentialsFile != "" && os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE") != "" && os.Getenv("AWS_ROLE_ARN") != "" {
// Reset the config to use the credentials from the credentials/config file
profile := config[awsProfileKey]
if profile == "" {
profile = awsDefaultProfile
}
sfp, err := awsconfig.LoadSharedConfigProfile(context.Background(), profile, func(o *awsconfig.LoadSharedConfigOptions) {
o.ConfigFiles = []string{credentialsFile}
o.CredentialsFiles = []string{credentialsFile}
})
if err != nil {
return nil, fmt.Errorf("error loading config profile '%s': %v", profile, err)
}
if err := resolveCredsFromProfile(&cfg, &sfp); err != nil {
return nil, fmt.Errorf("error resolving creds from profile '%s': %v", profile, err)
}
}
creds, err := cfg.Credentials.Retrieve(context.Background())
return &creds, err
@@ -115,3 +136,44 @@ func GetAWSBucketRegion(bucket string) (string, error) {
}
return region, nil
}
func resolveCredsFromProfile(cfg *aws.Config, sharedConfig *awsconfig.SharedConfig) error {
var err error
switch {
case sharedConfig.Source != nil:
// Assume IAM role with credentials source from a different profile.
err = resolveCredsFromProfile(cfg, sharedConfig.Source)
case sharedConfig.Credentials.HasKeys():
// Static Credentials from Shared Config/Credentials file.
cfg.Credentials = credentials.StaticCredentialsProvider{
Value: sharedConfig.Credentials,
}
}
if err != nil {
return err
}
if len(sharedConfig.RoleARN) > 0 {
credsFromAssumeRole(cfg, sharedConfig)
}
return nil
}
func credsFromAssumeRole(cfg *aws.Config, sharedCfg *awsconfig.SharedConfig) {
optFns := []func(*stscreds.AssumeRoleOptions){
func(options *stscreds.AssumeRoleOptions) {
options.RoleSessionName = sharedCfg.RoleSessionName
if sharedCfg.RoleDurationSeconds != nil {
if *sharedCfg.RoleDurationSeconds/time.Minute > 15 {
options.Duration = *sharedCfg.RoleDurationSeconds
}
}
if len(sharedCfg.ExternalID) > 0 {
options.ExternalID = aws.String(sharedCfg.ExternalID)
}
if len(sharedCfg.MFASerial) != 0 {
options.SerialNumber = aws.String(sharedCfg.MFASerial)
}
},
}
cfg.Credentials = stscreds.NewAssumeRoleProvider(sts.NewFromConfig(*cfg), sharedCfg.RoleARN, optFns...)
}
+7 -14
View File
@@ -53,7 +53,7 @@ var getGCPCredentials = repoconfig.GetGCPCredentials
var getS3BucketRegion = repoconfig.GetAWSBucketRegion
type localFuncTable struct {
getStorageVariables func(*velerov1api.BackupStorageLocation, string, string, credentials.FileStore) (map[string]string, error)
getStorageVariables func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error)
getStorageCredentials func(*velerov1api.BackupStorageLocation, credentials.FileStore) (map[string]string, error)
}
@@ -397,7 +397,7 @@ func (urp *unifiedRepoProvider) GetStoreOptions(param interface{}) (map[string]s
return map[string]string{}, errors.Errorf("invalid parameter, expect %T, actual %T", RepoParam{}, param)
}
storeVar, err := funcTable.getStorageVariables(repoParam.BackupLocation, urp.repoBackend, repoParam.BackupRepo.Spec.VolumeNamespace, urp.credentialGetter.FromFile)
storeVar, err := funcTable.getStorageVariables(repoParam.BackupLocation, urp.repoBackend, repoParam.BackupRepo.Spec.VolumeNamespace)
if err != nil {
return map[string]string{}, errors.Wrap(err, "error to get storage variables")
}
@@ -488,8 +488,9 @@ func getStorageCredentials(backupLocation *velerov1api.BackupStorageLocation, cr
result[udmrepo.StoreOptionS3Token] = credValue.SessionToken
}
case repoconfig.AzureBackend:
// do nothing here, will retrieve the credential in Azure Storage
return nil, nil
if config[repoconfig.CredentialsFileKey] != "" {
result[repoconfig.CredentialsFileKey] = config[repoconfig.CredentialsFileKey]
}
case repoconfig.GCPBackend:
result[udmrepo.StoreOptionCredentialFile] = getGCPCredentials(config)
}
@@ -497,8 +498,7 @@ func getStorageCredentials(backupLocation *velerov1api.BackupStorageLocation, cr
return result, nil
}
func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repoBackend string, repoName string,
credentialFileStore credentials.FileStore) (map[string]string, error) {
func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repoBackend string, repoName string) (map[string]string, error) {
result := make(map[string]string)
backendType := repoconfig.GetBackendType(backupLocation.Spec.Provider, backupLocation.Spec.Config)
@@ -510,13 +510,6 @@ func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repo
if config == nil {
config = map[string]string{}
}
if backupLocation.Spec.Credential != nil {
credsFile, err := credentialFileStore.Path(backupLocation.Spec.Credential)
if err != nil {
return map[string]string{}, errors.WithStack(err)
}
config[repoconfig.CredentialsFileKey] = credsFile
}
bucket := strings.Trim(config["bucket"], "/")
prefix := strings.Trim(config["prefix"], "/")
@@ -555,7 +548,7 @@ func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repo
}
s3URL = url.Host
disableTLS = (url.Scheme == "http")
disableTLS = url.Scheme == "http"
}
result[udmrepo.StoreOptionS3Endpoint] = strings.Trim(s3URL, "/")
+20 -21
View File
@@ -168,7 +168,7 @@ func TestGetStorageCredentials(t *testing.T) {
},
},
credFileStore: new(credmock.FileStore),
expected: nil,
expected: map[string]string{},
},
{
name: "gcp, Credential section not exists in BSL",
@@ -437,12 +437,11 @@ func TestGetStorageVariables(t *testing.T) {
},
}
credFileStore := new(credmock.FileStore)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
getS3BucketRegion = tc.getS3BucketRegion
actual, err := getStorageVariables(&tc.backupLocation, tc.repoBackend, tc.repoName, credFileStore)
actual, err := getStorageVariables(&tc.backupLocation, tc.repoBackend, tc.repoName)
require.Equal(t, tc.expected, actual)
@@ -531,7 +530,7 @@ func TestGetStoreOptions(t *testing.T) {
BackupRepo: &velerov1api.BackupRepository{},
},
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, errors.New("fake-error-2")
},
},
@@ -545,7 +544,7 @@ func TestGetStoreOptions(t *testing.T) {
BackupRepo: &velerov1api.BackupRepository{},
},
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -605,7 +604,7 @@ func TestPrepareRepo(t *testing.T) {
repoService: new(reposervicenmocks.BackupRepoService),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, errors.New("fake-store-option-error")
},
},
@@ -616,7 +615,7 @@ func TestPrepareRepo(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -636,7 +635,7 @@ func TestPrepareRepo(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -657,7 +656,7 @@ func TestPrepareRepo(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -734,7 +733,7 @@ func TestForget(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -758,7 +757,7 @@ func TestForget(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -786,7 +785,7 @@ func TestForget(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -878,7 +877,7 @@ func TestInitRepo(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -896,7 +895,7 @@ func TestInitRepo(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -966,7 +965,7 @@ func TestConnectToRepo(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -984,7 +983,7 @@ func TestConnectToRepo(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -1058,7 +1057,7 @@ func TestBoostRepoConnect(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -1085,7 +1084,7 @@ func TestBoostRepoConnect(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -1111,7 +1110,7 @@ func TestBoostRepoConnect(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -1198,7 +1197,7 @@ func TestPruneRepo(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
@@ -1216,7 +1215,7 @@ func TestPruneRepo(t *testing.T) {
getter: new(credmock.SecretStore),
credStoreReturn: "fake-password",
funcTable: localFuncTable{
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) {
getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) {
return map[string]string{}, nil
},
getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) {
+45 -63
View File
@@ -45,7 +45,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/informers"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
crclient "sigs.k8s.io/controller-runtime/pkg/client"
@@ -309,8 +308,6 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
resourceTerminatingTimeout: kr.resourceTerminatingTimeout,
resourceTimeout: kr.resourceTimeout,
resourceClients: make(map[resourceClientKey]client.Dynamic),
dynamicInformerFactories: make(map[string]*informerFactoryWithContext),
resourceInformers: make(map[resourceClientKey]informers.GenericInformer),
restoredItems: req.RestoredItems,
renamedPVs: make(map[string]string),
pvRenamer: kr.pvRenamer,
@@ -362,8 +359,7 @@ type restoreContext struct {
resourceTerminatingTimeout time.Duration
resourceTimeout time.Duration
resourceClients map[resourceClientKey]client.Dynamic
dynamicInformerFactories map[string]*informerFactoryWithContext
resourceInformers map[resourceClientKey]informers.GenericInformer
dynamicInformerFactory *informerFactoryWithContext
restoredItems map[itemKey]restoredItemStatus
renamedPVs map[string]string
pvRenamer func(string) (string, error)
@@ -447,11 +443,16 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) {
// Need to stop all informers if enabled
if !ctx.disableInformerCache {
context, cancel := signal.NotifyContext(go_context.Background(), os.Interrupt)
ctx.dynamicInformerFactory = &informerFactoryWithContext{
factory: ctx.dynamicFactory.DynamicSharedInformerFactory(),
context: context,
cancel: cancel,
}
defer func() {
// Call the cancel func to close the channel for each started informer
for _, factory := range ctx.dynamicInformerFactories {
factory.cancel()
}
ctx.dynamicInformerFactory.cancel()
// After upgrading to client-go 0.27 or newer, also call Shutdown for each informer factory
}()
}
@@ -579,28 +580,29 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) {
// initialize informer caches for selected resources if enabled
if !ctx.disableInformerCache {
// CRD informer will have already been initialized if any CRDs were created,
// but already-initialized informers aren't re-initialized because getGenericInformer
// looks for an existing one first.
factoriesToStart := make(map[string]*informerFactoryWithContext)
for _, informerResource := range selectedResourceCollection {
gr := schema.ParseGroupResource(informerResource.resource)
if informerResource.totalItems == 0 {
continue
}
version := ""
for _, items := range informerResource.selectedItemsByNamespace {
// don't use ns key since it represents original ns, not mapped ns
if len(items) == 0 {
continue
}
// use the first item in the list to initialize the informer. The rest of the list
// should share the same gvr and namespace
_, factory := ctx.getGenericInformerInternal(gr, items[0].version, items[0].targetNamespace)
if factory != nil {
factoriesToStart[items[0].targetNamespace] = factory
}
version = items[0].version
break
}
gvr := schema.ParseGroupResource(informerResource.resource).WithVersion(version)
_, _, err := ctx.discoveryHelper.ResourceFor(gvr)
if err != nil {
ctx.log.Infof("failed to create informer for %s: %v", gvr, err)
continue
}
ctx.dynamicInformerFactory.factory.ForResource(gvr)
}
for _, factoryWithContext := range factoriesToStart {
factoryWithContext.factory.WaitForCacheSync(factoryWithContext.context.Done())
}
ctx.dynamicInformerFactory.factory.Start(ctx.dynamicInformerFactory.context.Done())
ctx.log.Info("waiting informer cache sync ...")
ctx.dynamicInformerFactory.factory.WaitForCacheSync(ctx.dynamicInformerFactory.context.Done())
}
// reset processedItems and totalItems before processing full resource list
@@ -1061,47 +1063,23 @@ func (ctx *restoreContext) getResourceClient(groupResource schema.GroupResource,
return client, nil
}
// if new informer is created, non-nil factory is returned
func (ctx *restoreContext) getGenericInformerInternal(groupResource schema.GroupResource, version, namespace string) (informers.GenericInformer, *informerFactoryWithContext) {
var returnFactory *informerFactoryWithContext
func (ctx *restoreContext) getResourceLister(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) (cache.GenericNamespaceLister, error) {
_, _, err := ctx.discoveryHelper.KindFor(obj.GroupVersionKind())
if err != nil {
return nil, err
}
informer := ctx.dynamicInformerFactory.factory.ForResource(groupResource.WithVersion(obj.GroupVersionKind().Version))
// if the restore contains CRDs or the RIA returns new resources, need to make sure the corresponding informers are synced
if !informer.Informer().HasSynced() {
ctx.dynamicInformerFactory.factory.Start(ctx.dynamicInformerFactory.context.Done())
ctx.log.Infof("waiting informer cache sync for %s, %s/%s ...", groupResource, namespace, obj.GetName())
ctx.dynamicInformerFactory.factory.WaitForCacheSync(ctx.dynamicInformerFactory.context.Done())
}
key := getResourceClientKey(groupResource, version, namespace)
factoryWithContext, ok := ctx.dynamicInformerFactories[key.namespace]
if !ok {
factory := ctx.dynamicFactory.DynamicSharedInformerFactoryForNamespace(namespace)
informerContext, informerCancel := signal.NotifyContext(go_context.Background(), os.Interrupt)
factoryWithContext = &informerFactoryWithContext{
factory: factory,
context: informerContext,
cancel: informerCancel,
}
ctx.dynamicInformerFactories[key.namespace] = factoryWithContext
}
informer, ok := ctx.resourceInformers[key]
if !ok {
ctx.log.Infof("[debug] Creating factory for %s in namespace %s", key.resource, key.namespace)
informer = factoryWithContext.factory.ForResource(key.resource)
factoryWithContext.factory.Start(factoryWithContext.context.Done())
ctx.resourceInformers[key] = informer
returnFactory = factoryWithContext
}
return informer, returnFactory
}
func (ctx *restoreContext) getGenericInformer(groupResource schema.GroupResource, version, namespace string) informers.GenericInformer {
informer, factoryWithContext := ctx.getGenericInformerInternal(groupResource, version, namespace)
if factoryWithContext != nil {
factoryWithContext.factory.WaitForCacheSync(factoryWithContext.context.Done())
}
return informer
}
func (ctx *restoreContext) getResourceLister(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) cache.GenericNamespaceLister {
informer := ctx.getGenericInformer(groupResource, obj.GroupVersionKind().Version, namespace)
if namespace == "" {
return informer.Lister()
} else {
return informer.Lister().ByNamespace(namespace)
return informer.Lister(), nil
}
return informer.Lister().ByNamespace(namespace), nil
}
func getResourceID(groupResource schema.GroupResource, namespace, name string) string {
@@ -1113,7 +1091,10 @@ func getResourceID(groupResource schema.GroupResource, namespace, name string) s
}
func (ctx *restoreContext) getResource(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace, name string) (*unstructured.Unstructured, error) {
lister := ctx.getResourceLister(groupResource, obj, namespace)
lister, err := ctx.getResourceLister(groupResource, obj, namespace)
if err != nil {
return nil, errors.Wrapf(err, "Error getting lister for %s", getResourceID(groupResource, namespace, name))
}
clusterObj, err := lister.Get(name)
if err != nil {
return nil, errors.Wrapf(err, "error getting resource from lister for %s, %s/%s", groupResource, namespace, name)
@@ -1123,6 +1104,7 @@ func (ctx *restoreContext) getResource(groupResource schema.GroupResource, obj *
ctx.log.WithError(errors.WithStack(fmt.Errorf("expected *unstructured.Unstructured but got %T", u))).Error("unable to understand entry returned from client")
return nil, fmt.Errorf("expected *unstructured.Unstructured but got %T", u)
}
ctx.log.Debugf("get %s, %s/%s from informer cache", groupResource, namespace, name)
return u, nil
}
@@ -1757,7 +1739,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
if groupResource == kuberesource.CustomResourceDefinitions {
available, err := ctx.crdAvailable(name, resourceClient)
if err != nil {
errs.Add(namespace, errors.Wrapf(err, "error verifying custom resource definition is ready to use"))
errs.Add(namespace, errors.Wrapf(err, "error verifying the CRD %s is ready to use", name))
} else if !available {
errs.Add(namespace, fmt.Errorf("the CRD %s is not available to use for custom resources", name))
}
+1 -4
View File
@@ -19,9 +19,6 @@ package test
import (
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
discoveryfake "k8s.io/client-go/discovery/fake"
@@ -76,7 +73,7 @@ func (c *DiscoveryClient) WithAPIResource(resource *APIResource) *DiscoveryClien
Namespaced: resource.Namespaced,
Group: resource.Group,
Version: resource.Version,
Kind: cases.Title(language.Und).String(strings.TrimSuffix(resource.Name, "s")),
Kind: resource.Kind,
Verbs: metav1.Verbs([]string{"list", "create", "get", "delete"}),
ShortNames: []string{resource.ShortName},
})
+2 -2
View File
@@ -38,8 +38,8 @@ func (df *FakeDynamicFactory) ClientForGroupVersionResource(gv schema.GroupVersi
return args.Get(0).(client.Dynamic), args.Error(1)
}
func (df *FakeDynamicFactory) DynamicSharedInformerFactoryForNamespace(namespace string) dynamicinformer.DynamicSharedInformerFactory {
args := df.Called(namespace)
func (df *FakeDynamicFactory) DynamicSharedInformerFactory() dynamicinformer.DynamicSharedInformerFactory {
args := df.Called()
return args.Get(0).(dynamicinformer.DynamicSharedInformerFactory)
}
+16
View File
@@ -27,6 +27,7 @@ type APIResource struct {
Group string
Version string
Name string
Kind string
ShortName string
Namespaced bool
Items []metav1.Object
@@ -50,6 +51,7 @@ func Pods(items ...metav1.Object) *APIResource {
ShortName: "po",
Namespaced: true,
Items: items,
Kind: "Pod",
}
}
@@ -59,6 +61,7 @@ func PVCs(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "persistentvolumeclaims",
ShortName: "pvc",
Kind: "PersistentVolumeClaim",
Namespaced: true,
Items: items,
}
@@ -70,6 +73,7 @@ func PVs(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "persistentvolumes",
ShortName: "pv",
Kind: "PersistentVolume",
Namespaced: false,
Items: items,
}
@@ -81,6 +85,7 @@ func Secrets(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "secrets",
ShortName: "secrets",
Kind: "Secret",
Namespaced: true,
Items: items,
}
@@ -92,6 +97,7 @@ func Deployments(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "deployments",
ShortName: "deploy",
Kind: "Deployment",
Namespaced: true,
Items: items,
}
@@ -103,6 +109,7 @@ func ExtensionsDeployments(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "deployments",
ShortName: "deploy",
Kind: "Deployment",
Namespaced: true,
Items: items,
}
@@ -115,6 +122,7 @@ func VeleroDeployments(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "deployments",
ShortName: "deploy",
Kind: "Deployment",
Namespaced: true,
Items: items,
}
@@ -126,6 +134,7 @@ func Namespaces(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "namespaces",
ShortName: "ns",
Kind: "Namespace",
Namespaced: false,
Items: items,
}
@@ -137,6 +146,7 @@ func ServiceAccounts(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "serviceaccounts",
ShortName: "sa",
Kind: "ServiceAccount",
Namespaced: true,
Items: items,
}
@@ -148,6 +158,7 @@ func ConfigMaps(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "configmaps",
ShortName: "cm",
Kind: "ConfigMap",
Namespaced: true,
Items: items,
}
@@ -159,6 +170,7 @@ func CRDs(items ...metav1.Object) *APIResource {
Version: "v1beta1",
Name: "customresourcedefinitions",
ShortName: "crd",
Kind: "CustomResourceDefinition",
Namespaced: false,
Items: items,
}
@@ -169,6 +181,7 @@ func VSLs(items ...metav1.Object) *APIResource {
Group: "velero.io",
Version: "v1",
Name: "volumesnapshotlocations",
Kind: "VolumeSnapshotLocation",
Namespaced: true,
Items: items,
}
@@ -179,6 +192,7 @@ func Backups(items ...metav1.Object) *APIResource {
Group: "velero.io",
Version: "v1",
Name: "backups",
Kind: "Backup",
Namespaced: true,
Items: items,
}
@@ -190,6 +204,7 @@ func Services(items ...metav1.Object) *APIResource {
Version: "v1",
Name: "services",
ShortName: "svc",
Kind: "Service",
Namespaced: true,
Items: items,
}
@@ -200,6 +215,7 @@ func DataUploads(items ...metav1.Object) *APIResource {
Group: "velero.io",
Version: "v2alpha1",
Name: "datauploads",
Kind: "DataUpload",
Namespaced: true,
Items: items,
}
+2 -4
View File
@@ -26,10 +26,8 @@ import (
type MapUpdateFunc func(client.Object) []reconcile.Request
// EnqueueRequestsFromMapUpdateFunc is for the same purpose with EnqueueRequestsFromMapFunc.
// Merely, it is more friendly to updating the mapped objects in the MapUpdateFunc, because
// on Update event, MapUpdateFunc is called for only once with the new object, so if MapUpdateFunc
// does some update to the mapped objects, the update is done for once
// EnqueueRequestsFromMapUpdateFunc has the same purpose with handler.EnqueueRequestsFromMapFunc.
// MapUpdateFunc is simpler on Update event because mapAndEnqueue is called once with the new object. EnqueueRequestsFromMapFunc is called twice with the old and new object.
func EnqueueRequestsFromMapUpdateFunc(fn MapUpdateFunc) handler.EventHandler {
return &enqueueRequestsFromMapFunc{
toRequests: fn,