Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 6693: partially fail restore if CSI snapshot is involved but CSI feature is not ready #7077

Merged
merged 3 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelogs/unreleased/7077-Lyndon-Li
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix issue #6693, partially fail restore if CSI snapshot is involved but CSI feature is not ready, i.e., CSI feature gate is not enabled or CSI plugin is not installed.
13 changes: 7 additions & 6 deletions pkg/cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
"github.com/vmware-tanzu/velero/pkg/persistence"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
"github.com/vmware-tanzu/velero/pkg/podexec"
"github.com/vmware-tanzu/velero/pkg/podvolume"
"github.com/vmware-tanzu/velero/pkg/repository"
Expand Down Expand Up @@ -268,6 +267,7 @@
mgr manager.Manager
credentialFileStore credentials.FileStore
credentialSecretStore credentials.SecretStore
featureVerifier features.Verifier
}

func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*server, error) {
Expand Down Expand Up @@ -309,11 +309,10 @@
return nil, err
}

if !features.IsEnabled(velerov1api.CSIFeatureFlag) {
_, err = pluginRegistry.Get(common.PluginKindBackupItemActionV2, "velero.io/csi-pvc-backupper")
if err == nil {
logger.Warn("CSI plugins are registered, but the EnableCSI feature is not enabled.")
}
featureVerifier := features.NewVerifier(pluginRegistry)

if _, err := featureVerifier.Verify(velerov1api.CSIFeatureFlag); err != nil {
logger.WithError(err).Warn("CSI feature verification failed, the feature may not be ready.")

Check warning on line 315 in pkg/cmd/server/server.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/server/server.go#L312-L315

Added lines #L312 - L315 were not covered by tests
}

// cancelFunc is not deferred here because if it was, then ctx would immediately
Expand Down Expand Up @@ -397,6 +396,7 @@
mgr: mgr,
credentialFileStore: credentialFileStore,
credentialSecretStore: credentialSecretStore,
featureVerifier: featureVerifier,

Check warning on line 399 in pkg/cmd/server/server.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/server/server.go#L399

Added line #L399 was not covered by tests
}

// Setup CSI snapshot client and lister
Expand Down Expand Up @@ -942,6 +942,7 @@
s.kubeClient.CoreV1().RESTClient(),
s.credentialFileStore,
s.mgr.GetClient(),
s.featureVerifier,

Check warning on line 945 in pkg/cmd/server/server.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/server/server.go#L945

Added line #L945 was not covered by tests
)

cmd.CheckError(err)
Expand Down
43 changes: 43 additions & 0 deletions pkg/features/mocks/PluginFinder.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions pkg/features/mocks/Verifier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 71 additions & 0 deletions pkg/features/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
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 features

import (
"errors"

velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
)

type PluginFinder interface {
Find(kind common.PluginKind, name string) bool
}

type Verifier interface {
Verify(name string) (bool, error)
}

type verifier struct {
finder PluginFinder
}

func NewVerifier(finder PluginFinder) Verifier {
return &verifier{
finder: finder,
}

Check warning on line 41 in pkg/features/verify.go

View check run for this annotation

Codecov / codecov/patch

pkg/features/verify.go#L38-L41

Added lines #L38 - L41 were not covered by tests
}

func (v *verifier) Verify(name string) (bool, error) {
enabled := IsEnabled(name)

switch name {
case velerov1api.CSIFeatureFlag:
return verifyCSIFeature(v.finder, enabled)
default:
return false, nil

Check warning on line 51 in pkg/features/verify.go

View check run for this annotation

Codecov / codecov/patch

pkg/features/verify.go#L50-L51

Added lines #L50 - L51 were not covered by tests
}
}

func verifyCSIFeature(finder PluginFinder, enabled bool) (bool, error) {
installed := false
installed = finder.Find(common.PluginKindBackupItemActionV2, "velero.io/csi-pvc-backupper")
if installed {
installed = finder.Find(common.PluginKindRestoreItemActionV2, "velero.io/csi-pvc-restorer")
}

if !enabled && installed {
return false, errors.New("CSI plugins are registered, but the EnableCSI feature is not enabled")
} else if enabled && !installed {
return false, errors.New("CSI feature is enabled, but CSI plugins are not registered")
} else if !enabled && !installed {
return false, nil
} else {
return true, nil
}
}
61 changes: 61 additions & 0 deletions pkg/features/verify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
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 features

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

findermocks "github.com/vmware-tanzu/velero/pkg/features/mocks"
)

func TestVerify(t *testing.T) {
NewFeatureFlagSet()
verifier := verifier{}

finder := new(findermocks.PluginFinder)
finder.On("Find", mock.Anything, mock.Anything).Return(false)
verifier.finder = finder
ready, err := verifier.Verify("EnableCSI")
assert.Equal(t, false, ready)
assert.Nil(t, err)

finder = new(findermocks.PluginFinder)
finder.On("Find", mock.Anything, mock.Anything).Return(true)
verifier.finder = finder
ready, err = verifier.Verify("EnableCSI")
assert.Equal(t, false, ready)
assert.EqualError(t, err, "CSI plugins are registered, but the EnableCSI feature is not enabled")

Enable("EnableCSI")
finder = new(findermocks.PluginFinder)
finder.On("Find", mock.Anything, mock.Anything).Return(false)
verifier.finder = finder
ready, err = verifier.Verify("EnableCSI")
assert.Equal(t, false, ready)
assert.EqualError(t, err, "CSI feature is enabled, but CSI plugins are not registered")

Enable("EnableCSI")
finder = new(findermocks.PluginFinder)
finder.On("Find", mock.Anything, mock.Anything).Return(true)
verifier.finder = finder
ready, err = verifier.Verify("EnableCSI")
assert.Equal(t, true, ready)
assert.Nil(t, err)
}
4 changes: 4 additions & 0 deletions pkg/plugin/clientmgmt/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ func (r *mockRegistry) Get(kind common.PluginKind, name string) (framework.Plugi
return id, args.Error(1)
}

func (r *mockRegistry) Find(kind common.PluginKind, name string) bool {
return false
}

func TestNewManager(t *testing.T) {
logger := test.NewLogger()
logLevel := logrus.InfoLevel
Expand Down
9 changes: 9 additions & 0 deletions pkg/plugin/clientmgmt/process/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
List(kind common.PluginKind) []framework.PluginIdentifier
// Get returns the PluginIdentifier for kind and name.
Get(kind common.PluginKind, name string) (framework.PluginIdentifier, error)

// Find checks if the specified plugin exists in the registry
Find(kind common.PluginKind, name string) bool
}

// KindAndName is a convenience struct that combines a PluginKind and a name.
Expand Down Expand Up @@ -125,6 +128,12 @@
return p, nil
}

// Contain if the specified plugin exists in the registry
func (r *registry) Find(kind common.PluginKind, name string) bool {
_, found := r.pluginsByID[KindAndName{Kind: kind, Name: name}]
return found

Check warning on line 134 in pkg/plugin/clientmgmt/process/registry.go

View check run for this annotation

Codecov / codecov/patch

pkg/plugin/clientmgmt/process/registry.go#L132-L134

Added lines #L132 - L134 were not covered by tests
}

// readPluginsDir recursively reads dir looking for plugins.
func (r *registry) readPluginsDir(dir string) ([]string, error) {
if _, err := r.fs.Stat(dir); err != nil {
Expand Down
15 changes: 15 additions & 0 deletions pkg/restore/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ type kubernetesRestorer struct {
podGetter cache.Getter
credentialFileStore credentials.FileStore
kbClient crclient.Client
featureVerifier features.Verifier
}

// NewKubernetesRestorer creates a new kubernetesRestorer.
Expand All @@ -132,6 +133,7 @@ func NewKubernetesRestorer(
podGetter cache.Getter,
credentialStore credentials.FileStore,
kbClient crclient.Client,
featureVerifier features.Verifier,
) (Restorer, error) {
return &kubernetesRestorer{
discoveryHelper: discoveryHelper,
Expand All @@ -156,6 +158,7 @@ func NewKubernetesRestorer(
podGetter: podGetter,
credentialFileStore: credentialStore,
kbClient: kbClient,
featureVerifier: featureVerifier,
}, nil
}

Expand Down Expand Up @@ -321,6 +324,7 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
itemOperationsList: req.GetItemOperationsList(),
resourceModifiers: req.ResourceModifiers,
disableInformerCache: req.DisableInformerCache,
featureVerifier: kr.featureVerifier,
}

return restoreCtx.execute()
Expand Down Expand Up @@ -372,6 +376,7 @@ type restoreContext struct {
itemOperationsList *[]*itemoperation.RestoreOperation
resourceModifiers *resourcemodifiers.ResourceModifiers
disableInformerCache bool
featureVerifier features.Verifier
}

type resourceClientKey struct {
Expand Down Expand Up @@ -1310,6 +1315,11 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
}).Infof("Dynamically re-provisioning persistent volume because it has a related CSI VolumeSnapshot.")
ctx.pvsToProvision.Insert(name)

if ready, err := ctx.featureVerifier.Verify(velerov1api.CSIFeatureFlag); !ready {
ctx.log.Errorf("Failed to verify CSI modules, ready %v, err %v", ready, err)
errs.Add(namespace, fmt.Errorf("CSI modules are not ready for restore. Check CSI feature is enabled and CSI plugin is installed"))
}

// Return early because we don't want to restore the PV itself, we
// want to dynamically re-provision it.
return warnings, errs, itemExists
Expand All @@ -1322,6 +1332,11 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
}).Infof("Dynamically re-provisioning persistent volume because it has a related snapshot DataUpload.")
ctx.pvsToProvision.Insert(name)

if ready, err := ctx.featureVerifier.Verify(velerov1api.CSIFeatureFlag); !ready {
ctx.log.Errorf("Failed to verify CSI modules, ready %v, err %v", ready, err)
errs.Add(namespace, fmt.Errorf("CSI modules are not ready for restore. Check CSI feature is enabled and CSI plugin is installed"))
}

// Return early because we don't want to restore the PV itself, we
// want to dynamically re-provision it.
return warnings, errs, itemExists
Expand Down
Loading
Loading