diff --git a/config/samples/velero_v1_serverstatusrequest.yaml b/config/samples/velero_v1_serverstatusrequest.yaml deleted file mode 100644 index 60cfcfc9b..000000000 --- a/config/samples/velero_v1_serverstatusrequest.yaml +++ /dev/null @@ -1,25 +0,0 @@ ---- -apiVersion: velero.io/v1 -kind: ServerStatusRequest -metadata: - creationTimestamp: "2020-08-21T15:34:34Z" - generateName: velero-cli- - generation: 1 - name: velero-cli-6wkzd - namespace: velero - resourceVersion: "544749" - selfLink: /apis/velero.io/v1/namespaces/velero/serverstatusrequests/velero-cli-6wkzd - uid: 335ea64e-1904-40ec-8106-1f2b22e9540e -spec: {} -status: - phase: Processed - plugins: - - kind: ObjectStore - name: velero.io/aws - - kind: VolumeSnapshotter - name: velero.io/aws - - kind: BackupItemAction - name: velero.io/crd-remap-version - - kind: BackupItemAction - name: velero.io/pod - processedTimestamp: "2020-08-21T15:34:34Z" \ No newline at end of file diff --git a/hack/update-generated-crd-code.sh b/hack/update-generated-crd-code.sh index 39b23e479..193fa121c 100755 --- a/hack/update-generated-crd-code.sh +++ b/hack/update-generated-crd-code.sh @@ -47,7 +47,6 @@ ${GOPATH}/src/k8s.io/code-generator/generate-groups.sh \ # Generate manifests e.g. CRD, RBAC etc. controller-gen \ crd:crdVersions=v1beta1,preserveUnknownFields=false,trivialVersions=true \ - rbac:roleName=manager-role \ paths=./pkg/apis/velero/v1/... \ paths=./pkg/controller/... \ output:crd:artifacts:config=config/crd/bases diff --git a/internal/velero/serverstatusrequest.go b/internal/velero/serverstatusrequest.go index e57ca2a32..a220c5c19 100644 --- a/internal/velero/serverstatusrequest.go +++ b/internal/velero/serverstatusrequest.go @@ -17,47 +17,17 @@ limitations under the License. package velero import ( - "context" - - "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/clock" - "sigs.k8s.io/controller-runtime/pkg/client" - velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - "github.com/vmware-tanzu/velero/pkg/buildinfo" "github.com/vmware-tanzu/velero/pkg/plugin/framework" ) -// ServerStatus holds information for retrieving installed -// plugins and for updating the ServerStatusRequest timestamp. -type ServerStatus struct { - PluginRegistry PluginLister - Clock clock.Clock -} - -// PatchStatusProcessed patches status fields, including loading the plugin info, and updates -// the ServerStatusRequest.Status.Phase to ServerStatusRequestPhaseProcessed. -func (s *ServerStatus) PatchStatusProcessed(kbClient client.Client, req *velerov1api.ServerStatusRequest, ctx context.Context) error { - statusPatch := client.MergeFrom(req.DeepCopyObject()) - req.Status.ServerVersion = buildinfo.Version - req.Status.Phase = velerov1api.ServerStatusRequestPhaseProcessed - req.Status.ProcessedTimestamp = &metav1.Time{Time: s.Clock.Now()} - req.Status.Plugins = getInstalledPluginInfo(s.PluginRegistry) - - if err := kbClient.Status().Patch(ctx, req, statusPatch); err != nil { - return errors.WithStack(err) - } - - return nil -} - type PluginLister interface { // List returns all PluginIdentifiers for kind. List(kind framework.PluginKind) []framework.PluginIdentifier } -func getInstalledPluginInfo(pluginLister PluginLister) []velerov1api.PluginInfo { +// GetInstalledPluginInfo returns a list of installed plugins +func GetInstalledPluginInfo(pluginLister PluginLister) []velerov1api.PluginInfo { var plugins []velerov1api.PluginInfo for _, v := range framework.AllPluginKinds() { list := pluginLister.List(v) diff --git a/internal/velero/serverstatusrequest_test.go b/internal/velero/serverstatusrequest_test.go deleted file mode 100644 index ff9285ccb..000000000 --- a/internal/velero/serverstatusrequest_test.go +++ /dev/null @@ -1,202 +0,0 @@ -/* -Copyright 2020 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 velero - -import ( - "context" - "sort" - "testing" - "time" - - . "github.com/onsi/gomega" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/util/clock" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - "github.com/vmware-tanzu/velero/pkg/builder" - "github.com/vmware-tanzu/velero/pkg/buildinfo" - "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme" - "github.com/vmware-tanzu/velero/pkg/plugin/framework" -) - -func statusRequestBuilder(resourceVersion string) *builder.ServerStatusRequestBuilder { - return builder.ForServerStatusRequest(velerov1api.DefaultNamespace, "sr-1", resourceVersion) -} - -func TestPatchStatusProcessed(t *testing.T) { - // now will be used to set the fake clock's time; capture - // it here so it can be referenced in the test case defs. - now, err := time.Parse(time.RFC1123, time.RFC1123) - require.NoError(t, err) - now = now.Local() - - buildinfo.Version = "test-version-val" - - tests := []struct { - name string - req *velerov1api.ServerStatusRequest - reqPluginLister *fakePluginLister - expected *velerov1api.ServerStatusRequest - }{ - { - name: "server status request with empty phase gets processed", - req: statusRequestBuilder("0").Result(), - reqPluginLister: &fakePluginLister{ - plugins: []framework.PluginIdentifier{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }, - }, - expected: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase(velerov1api.ServerStatusRequestPhaseProcessed). - ProcessedTimestamp(now). - Plugins([]velerov1api.PluginInfo{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - }, - { - name: "server status request with phase=New gets processed", - req: statusRequestBuilder("0"). - Phase(velerov1api.ServerStatusRequestPhaseNew). - Result(), - reqPluginLister: &fakePluginLister{ - plugins: []framework.PluginIdentifier{ - { - Name: "velero.io/aws", - Kind: "ObjectStore", - }, - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }, - }, - expected: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase(velerov1api.ServerStatusRequestPhaseProcessed). - ProcessedTimestamp(now). - Plugins([]velerov1api.PluginInfo{ - { - Name: "velero.io/aws", - Kind: "ObjectStore", - }, - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - }, - { - name: "server status request with phase=Processed gets processed", - req: statusRequestBuilder("0"). - Phase(velerov1api.ServerStatusRequestPhaseProcessed). - Result(), - reqPluginLister: &fakePluginLister{ - plugins: []framework.PluginIdentifier{ - { - Name: "velero.io/aws", - Kind: "ObjectStore", - }, - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }, - }, - expected: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase(velerov1api.ServerStatusRequestPhaseProcessed). - ProcessedTimestamp(now). - Plugins([]velerov1api.PluginInfo{ - { - Name: "velero.io/aws", - Kind: "ObjectStore", - }, - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - g := NewWithT(t) - - serverStatusInfo := ServerStatus{ - PluginRegistry: tc.reqPluginLister, - Clock: clock.NewFakeClock(now), - } - - kbClient := fake.NewFakeClientWithScheme(scheme.Scheme, tc.req) - err := serverStatusInfo.PatchStatusProcessed(kbClient, tc.req, context.Background()) - assert.Nil(t, err) - - key := client.ObjectKey{Name: tc.req.Name, Namespace: tc.req.Namespace} - instance := &velerov1api.ServerStatusRequest{} - err = kbClient.Get(context.Background(), key, instance) - - if tc.expected == nil { - g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) - } else { - sortPluginsByKindAndName(tc.expected.Status.Plugins) - sortPluginsByKindAndName(instance.Status.Plugins) - g.Expect(instance.Status.Plugins).To(BeEquivalentTo((tc.expected.Status.Plugins))) - g.Expect(instance).To(BeEquivalentTo((tc.expected))) - g.Expect(err).To(BeNil()) - } - }) - } -} - -func sortPluginsByKindAndName(plugins []velerov1api.PluginInfo) { - sort.Slice(plugins, func(i, j int) bool { - if plugins[i].Kind != plugins[j].Kind { - return plugins[i].Kind < plugins[j].Kind - } - return plugins[i].Name < plugins[j].Name - }) -} - -type fakePluginLister struct { - plugins []framework.PluginIdentifier -} - -func (l *fakePluginLister) List(kind framework.PluginKind) []framework.PluginIdentifier { - var plugins []framework.PluginIdentifier - for _, plugin := range l.plugins { - if plugin.Kind == kind { - plugins = append(plugins, plugin) - } - } - - return plugins -} diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 8a51eeb03..62e10a926 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -76,7 +76,6 @@ import ( "github.com/vmware-tanzu/velero/internal/storage" "github.com/vmware-tanzu/velero/internal/util/managercontroller" - "github.com/vmware-tanzu/velero/internal/velero" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" ) @@ -768,8 +767,9 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string controller.DownloadRequest: downloadrequestControllerRunInfo, } // Note: all runtime type controllers that can be disabled are grouped separately, below: - enabledRuntimeControllers := make(map[string]struct{}) - enabledRuntimeControllers[controller.ServerStatusRequest] = struct{}{} + enabledRuntimeControllers := map[string]struct{}{ + controller.ServerStatusRequest: {}, + } if s.config.restoreOnly { s.logger.Info("Restore only mode - not starting the backup, schedule, delete-backup, or GC controllers") @@ -781,20 +781,10 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string ) } - // remove disabled controllers - for _, controllerName := range s.config.disabledControllers { - if _, ok := enabledControllers[controllerName]; ok { - s.logger.Infof("Disabling controller: %s", controllerName) - delete(enabledControllers, controllerName) - } else { - // maybe it is a runtime type controllers, so attempt to remove that - if _, ok := enabledRuntimeControllers[controllerName]; ok { - s.logger.Infof("Disabling controller: %s", controllerName) - delete(enabledRuntimeControllers, controllerName) - } else { - s.logger.Fatalf("Invalid value for --disable-controllers flag provided: %s. Valid values are: %s", controllerName, strings.Join(controller.DisableableControllers, ",")) - } - } + // Remove disabled controllers so they are not initialized. If a match is not found we want + // to hault the system so the user knows this operation was not possible. + if err := removeControllers(s.config.disabledControllers, enabledControllers, enabledRuntimeControllers, s.logger); err != nil { + log.Fatal(err, "unable to disable a controller") } // Instantiate the enabled controllers. This needs to be done *before* @@ -844,14 +834,12 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string if _, ok := enabledRuntimeControllers[controller.ServerStatusRequest]; ok { r := controller.ServerStatusRequestReconciler{ - Scheme: s.mgr.GetScheme(), - Client: s.mgr.GetClient(), - Ctx: s.ctx, - ServerStatus: velero.ServerStatus{ - PluginRegistry: s.pluginRegistry, - Clock: clock.RealClock{}, - }, - Log: s.logger, + Scheme: s.mgr.GetScheme(), + Client: s.mgr.GetClient(), + Ctx: s.ctx, + PluginRegistry: s.pluginRegistry, + Clock: clock.RealClock{}, + Log: s.logger, } if err := r.SetupWithManager(s.mgr); err != nil { s.logger.Fatal(err, "unable to create controller", "controller", controller.ServerStatusRequest) @@ -878,6 +866,29 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string return nil } +// removeControllers will remove any controller listed to be disabled from the list +// of controllers to be initialized. First it will check the legacy list of controllers, +// then it will check the new runtime controllers. If both checks fail a match +// wasn't found and it returns an error. +func removeControllers(disabledControllers []string, enabledControllers map[string]func() controllerRunInfo, enabledRuntimeControllers map[string]struct{}, logger logrus.FieldLogger) error { + for _, controllerName := range disabledControllers { + if _, ok := enabledControllers[controllerName]; ok { + logger.Infof("Disabling controller: %s", controllerName) + delete(enabledControllers, controllerName) + } else { + // maybe it is a runtime type controllers, so attempt to remove that + if _, ok := enabledRuntimeControllers[controllerName]; ok { + logger.Infof("Disabling controller: %s", controllerName) + delete(enabledRuntimeControllers, controllerName) + } else { + msg := fmt.Sprintf("Invalid value for --disable-controllers flag provided: %s. Valid values are: %s", controllerName, strings.Join(controller.DisableableControllers, ",")) + return errors.New(msg) + } + } + } + return nil +} + func (s *server) runProfiler() { mux := http.NewServeMux() mux.HandleFunc("/debug/pprof/", pprof.Index) diff --git a/pkg/cmd/server/server_test.go b/pkg/cmd/server/server_test.go index a1477b2d2..df726e6bf 100644 --- a/pkg/cmd/server/server_test.go +++ b/pkg/cmd/server/server_test.go @@ -23,6 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/controller" velerotest "github.com/vmware-tanzu/velero/pkg/test" ) @@ -69,3 +70,87 @@ func TestVeleroResourcesExist(t *testing.T) { veleroAPIResourceList.APIResources = veleroAPIResourceList.APIResources[:3] assert.Error(t, server.veleroResourcesExist()) } + +func TestRemoveControllers(t *testing.T) { + logger := velerotest.NewLogger() + + tests := []struct { + name string + disabledControllers []string + errorExpected bool + }{ + { + name: "Remove one disabable controller", + disabledControllers: []string{ + controller.Backup, + }, + errorExpected: false, + }, + { + name: "Remove all disabable controllers", + disabledControllers: []string{ + controller.Backup, + controller.BackupDeletion, + controller.BackupSync, + controller.DownloadRequest, + controller.GarbageCollection, + controller.ResticRepo, + controller.Restore, + controller.Schedule, + controller.ServerStatusRequest, + }, + errorExpected: false, + }, + { + name: "Remove with a non-disabable controller included", + disabledControllers: []string{ + controller.Backup, + controller.BackupStorageLocation, + }, + errorExpected: true, + }, + { + name: "Remove with a misspelled/inexisting controller name", + disabledControllers: []string{ + "go", + }, + errorExpected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + enabledControllers := map[string]func() controllerRunInfo{ + controller.BackupSync: func() controllerRunInfo { return controllerRunInfo{} }, + controller.Backup: func() controllerRunInfo { return controllerRunInfo{} }, + controller.Schedule: func() controllerRunInfo { return controllerRunInfo{} }, + controller.GarbageCollection: func() controllerRunInfo { return controllerRunInfo{} }, + controller.BackupDeletion: func() controllerRunInfo { return controllerRunInfo{} }, + controller.Restore: func() controllerRunInfo { return controllerRunInfo{} }, + controller.ResticRepo: func() controllerRunInfo { return controllerRunInfo{} }, + controller.DownloadRequest: func() controllerRunInfo { return controllerRunInfo{} }, + } + + enabledRuntimeControllers := map[string]struct{}{ + controller.ServerStatusRequest: {}, + } + + totalNumOriginalControllers := len(enabledControllers) + len(enabledRuntimeControllers) + + if tt.errorExpected { + assert.Error(t, removeControllers(tt.disabledControllers, enabledControllers, enabledRuntimeControllers, logger)) + } else { + assert.NoError(t, removeControllers(tt.disabledControllers, enabledControllers, enabledRuntimeControllers, logger)) + + totalNumEnabledControllers := len(enabledControllers) + len(enabledRuntimeControllers) + assert.Equal(t, totalNumEnabledControllers, totalNumOriginalControllers-len(tt.disabledControllers)) + + for _, disabled := range tt.disabledControllers { + _, ok := enabledControllers[disabled] + assert.False(t, ok) + _, ok = enabledRuntimeControllers[disabled] + assert.False(t, ok) + } + } + }) + } +} diff --git a/pkg/controller/backup_storage_location_controller_test.go b/pkg/controller/backup_storage_location_controller_test.go index 6e5e7d2b3..a0349a6d6 100644 --- a/pkg/controller/backup_storage_location_controller_test.go +++ b/pkg/controller/backup_storage_location_controller_test.go @@ -41,9 +41,6 @@ import ( ) var _ = Describe("Backup Storage Location Reconciler", func() { - BeforeEach(func() {}) - AfterEach(func() {}) - It("Should successfully patch a backup storage location object status phase according to whether its storage is valid or not", func() { tests := []struct { backupLocation *velerov1api.BackupStorageLocation diff --git a/pkg/controller/server_status_request_controller.go b/pkg/controller/server_status_request_controller.go index 0c537bc9e..bbe54aadf 100644 --- a/pkg/controller/server_status_request_controller.go +++ b/pkg/controller/server_status_request_controller.go @@ -23,13 +23,17 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/clock" + "sigs.k8s.io/cluster-api/util/patch" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "github.com/vmware-tanzu/velero/internal/velero" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/buildinfo" "github.com/vmware-tanzu/velero/pkg/plugin/framework" ) @@ -45,10 +49,11 @@ type PluginLister interface { // ServerStatusRequestReconciler reconciles a ServerStatusRequest object type ServerStatusRequestReconciler struct { - Scheme *runtime.Scheme - Client client.Client - Ctx context.Context - ServerStatus velero.ServerStatus + Scheme *runtime.Scheme + Client client.Client + Ctx context.Context + PluginRegistry PluginLister + Clock clock.Clock Log logrus.FieldLogger } @@ -85,14 +90,26 @@ func (r *ServerStatusRequestReconciler) Reconcile(req ctrl.Request) (ctrl.Result case "", velerov1api.ServerStatusRequestPhaseNew: log.Info("Processing new ServerStatusRequest") - if err := r.ServerStatus.PatchStatusProcessed(r.Client, statusRequest, r.Ctx); err != nil { - log.WithError(err).Error("Unable to update the request") + // Initialize the patch helper. + patchHelper, err := patch.NewHelper(statusRequest, r.Client) + if err != nil { + log.WithError(err).Error("Error getting a patch helper to update this resource") + return ctrl.Result{}, err + } + + statusRequest.Status.ServerVersion = buildinfo.Version + statusRequest.Status.Phase = velerov1api.ServerStatusRequestPhaseProcessed + statusRequest.Status.ProcessedTimestamp = &metav1.Time{Time: r.Clock.Now()} + statusRequest.Status.Plugins = velero.GetInstalledPluginInfo(r.PluginRegistry) + + if err := patchHelper.Patch(r.Ctx, statusRequest); err != nil { + log.WithError(err).Error("Error updating ServerStatusRequest status") return ctrl.Result{RequeueAfter: statusRequestResyncPeriod}, err } case velerov1api.ServerStatusRequestPhaseProcessed: log.Debug("Checking whether ServerStatusRequest has expired") expiration := statusRequest.Status.ProcessedTimestamp.Add(ttl) - if expiration.After(r.ServerStatus.Clock.Now()) { + if expiration.After(r.Clock.Now()) { log.Debug("ServerStatusRequest has not expired") return ctrl.Result{RequeueAfter: statusRequestResyncPeriod}, nil } diff --git a/pkg/controller/server_status_request_controller_test.go b/pkg/controller/server_status_request_controller_test.go index 668ccf672..587d87c8c 100644 --- a/pkg/controller/server_status_request_controller_test.go +++ b/pkg/controller/server_status_request_controller_test.go @@ -21,16 +21,17 @@ import ( "time" . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/clock" "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" kbclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/vmware-tanzu/velero/internal/velero" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/builder" "github.com/vmware-tanzu/velero/pkg/buildinfo" @@ -43,186 +44,30 @@ func statusRequestBuilder(resourceVersion string) *builder.ServerStatusRequestBu } var _ = Describe("Server Status Request Reconciler", func() { - BeforeEach(func() {}) - AfterEach(func() {}) + type request struct { + req *velerov1api.ServerStatusRequest + reqPluginLister *fakePluginLister + expected *velerov1api.ServerStatusRequest + expectedRequeue ctrl.Result + expectedErrMsg string + } - It("Should successfully patch a server status request object status phase", func() { - // now will be used to set the fake clock's time; capture - // it here so it can be referenced in the test case defs. - now, err := time.Parse(time.RFC1123, time.RFC1123) - Expect(err).To(BeNil()) - now = now.Local() + // `now` will be used to set the fake clock's time; capture + // it here so it can be referenced in the test case defs. + now, err := time.Parse(time.RFC1123, time.RFC1123) + Expect(err).To(BeNil()) + now = now.Local() - tests := []struct { - req *velerov1api.ServerStatusRequest - reqPluginLister *fakePluginLister - expected *velerov1api.ServerStatusRequest - expectedRequeue ctrl.Result - expectedErrMsg string - }{ - { - // server status request with phase=empty will be processed - req: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - ProcessedTimestamp(now). - Plugins([]velerov1api.PluginInfo{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - reqPluginLister: &fakePluginLister{ - plugins: []framework.PluginIdentifier{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }, - }, - expected: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase(velerov1api.ServerStatusRequestPhaseProcessed). - ProcessedTimestamp(now). - Plugins([]velerov1api.PluginInfo{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: statusRequestResyncPeriod}, - }, - { - // server status request with phase=new will be processed - req: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase(velerov1api.ServerStatusRequestPhaseNew). - ProcessedTimestamp(now). - Plugins([]velerov1api.PluginInfo{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - reqPluginLister: &fakePluginLister{ - plugins: []framework.PluginIdentifier{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }, - }, - expected: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase(velerov1api.ServerStatusRequestPhaseProcessed). - ProcessedTimestamp(now). - Plugins([]velerov1api.PluginInfo{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: statusRequestResyncPeriod}, - }, - { - // server status request with phase=Processed does not get deleted if not expired - req: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase(velerov1api.ServerStatusRequestPhaseProcessed). - ProcessedTimestamp(now). // not yet expired - Plugins([]velerov1api.PluginInfo{ - { - Name: "custom.io/myotherown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - reqPluginLister: &fakePluginLister{ - plugins: []framework.PluginIdentifier{ - { - Name: "custom.io/myotherown", - Kind: "VolumeSnapshotter", - }, - }, - }, - expected: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase(velerov1api.ServerStatusRequestPhaseProcessed). - ProcessedTimestamp(now). - Plugins([]velerov1api.PluginInfo{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: statusRequestResyncPeriod}, - }, - { - // server status request with phase=Processed gets deleted if expire - req: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase(velerov1api.ServerStatusRequestPhaseProcessed). - ProcessedTimestamp(now.Add(-61 * time.Second)). // expired - Plugins([]velerov1api.PluginInfo{ - { - Name: "custom.io/myotherown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - reqPluginLister: &fakePluginLister{ - plugins: []framework.PluginIdentifier{ - { - Name: "custom.io/myotherown", - Kind: "VolumeSnapshotter", - }, - }, - }, - expected: nil, - expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: statusRequestResyncPeriod}, - }, - { - // server status request with invalid phase returns an error and does not requeue - req: statusRequestBuilder("1"). - ServerVersion(buildinfo.Version). - Phase("an-invalid-phase"). - ProcessedTimestamp(now). - Plugins([]velerov1api.PluginInfo{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }). - Result(), - reqPluginLister: &fakePluginLister{ - plugins: []framework.PluginIdentifier{ - { - Name: "custom.io/myown", - Kind: "VolumeSnapshotter", - }, - }, - }, - expectedErrMsg: "unexpected ServerStatusRequest phase", - expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: 0}, - }, - } - - for _, test := range tests { + DescribeTable("a Server Status request", + func(test request) { // Setup reconciler Expect(velerov1api.AddToScheme(scheme.Scheme)).To(Succeed()) - serverStatusInfo := velero.ServerStatus{ + r := ServerStatusRequestReconciler{ + Client: fake.NewFakeClientWithScheme(scheme.Scheme, test.req), + Ctx: context.Background(), PluginRegistry: test.reqPluginLister, Clock: clock.NewFakeClock(now), - } - r := ServerStatusRequestReconciler{ - Client: fake.NewFakeClientWithScheme(scheme.Scheme, test.req), - ServerStatus: serverStatusInfo, - Ctx: context.Background(), - Log: velerotest.NewLogger(), + Log: velerotest.NewLogger(), } actualResult, err := r.Reconcile(ctrl.Request{ @@ -250,8 +95,152 @@ var _ = Describe("Server Status Request Reconciler", func() { Expect(err).To(BeNil()) Eventually(instance.Status.Phase == test.expected.Status.Phase, timeout).Should(BeTrue()) } - } - }) + }, + Entry("with phase=empty will be processed and phased successfully patched", request{ + req: statusRequestBuilder("1"). + ServerVersion(buildinfo.Version). + ProcessedTimestamp(now). + Plugins([]velerov1api.PluginInfo{ + { + Name: "custom.io/myown", + Kind: "VolumeSnapshotter", + }, + }). + Result(), + reqPluginLister: &fakePluginLister{ + plugins: []framework.PluginIdentifier{ + { + Name: "custom.io/myown", + Kind: "VolumeSnapshotter", + }, + }, + }, + expected: statusRequestBuilder("1"). + ServerVersion(buildinfo.Version). + Phase(velerov1api.ServerStatusRequestPhaseProcessed). + ProcessedTimestamp(now). + Plugins([]velerov1api.PluginInfo{ + { + Name: "custom.io/myown", + Kind: "VolumeSnapshotter", + }, + }). + Result(), + expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: statusRequestResyncPeriod}, + }), + Entry("with phase=new will be processed and phased successfully patched", request{ + req: statusRequestBuilder("1"). + ServerVersion(buildinfo.Version). + Phase(velerov1api.ServerStatusRequestPhaseNew). + ProcessedTimestamp(now). + Plugins([]velerov1api.PluginInfo{ + { + Name: "custom.io/myown", + Kind: "VolumeSnapshotter", + }, + }). + Result(), + reqPluginLister: &fakePluginLister{ + plugins: []framework.PluginIdentifier{ + { + Name: "custom.io/myown", + Kind: "VolumeSnapshotter", + }, + }, + }, + expected: statusRequestBuilder("1"). + ServerVersion(buildinfo.Version). + Phase(velerov1api.ServerStatusRequestPhaseProcessed). + ProcessedTimestamp(now). + Plugins([]velerov1api.PluginInfo{ + { + Name: "custom.io/myown", + Kind: "VolumeSnapshotter", + }, + }). + Result(), + expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: statusRequestResyncPeriod}, + }), + Entry("with phase=Processed does not get deleted if not expired", request{ + req: statusRequestBuilder("1"). + ServerVersion(buildinfo.Version). + Phase(velerov1api.ServerStatusRequestPhaseProcessed). + ProcessedTimestamp(now). // not yet expired + Plugins([]velerov1api.PluginInfo{ + { + Name: "custom.io/myotherown", + Kind: "VolumeSnapshotter", + }, + }). + Result(), + reqPluginLister: &fakePluginLister{ + plugins: []framework.PluginIdentifier{ + { + Name: "custom.io/myotherown", + Kind: "VolumeSnapshotter", + }, + }, + }, + expected: statusRequestBuilder("1"). + ServerVersion(buildinfo.Version). + Phase(velerov1api.ServerStatusRequestPhaseProcessed). + ProcessedTimestamp(now). + Plugins([]velerov1api.PluginInfo{ + { + Name: "custom.io/myown", + Kind: "VolumeSnapshotter", + }, + }). + Result(), + expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: statusRequestResyncPeriod}, + }), + Entry("with phase=Processed gets deleted if expired", request{ + req: statusRequestBuilder("1"). + ServerVersion(buildinfo.Version). + Phase(velerov1api.ServerStatusRequestPhaseProcessed). + ProcessedTimestamp(now.Add(-61 * time.Second)). // expired + Plugins([]velerov1api.PluginInfo{ + { + Name: "custom.io/myotherown", + Kind: "VolumeSnapshotter", + }, + }). + Result(), + reqPluginLister: &fakePluginLister{ + plugins: []framework.PluginIdentifier{ + { + Name: "custom.io/myotherown", + Kind: "VolumeSnapshotter", + }, + }, + }, + expected: nil, + expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: statusRequestResyncPeriod}, + }), + Entry("with invalid phase returns an error and does not requeue", request{ + req: statusRequestBuilder("1"). + ServerVersion(buildinfo.Version). + Phase("an-invalid-phase"). + ProcessedTimestamp(now). + Plugins([]velerov1api.PluginInfo{ + { + Name: "custom.io/myown", + Kind: "VolumeSnapshotter", + }, + }). + Result(), + reqPluginLister: &fakePluginLister{ + plugins: []framework.PluginIdentifier{ + { + Name: "custom.io/myown", + Kind: "VolumeSnapshotter", + }, + }, + }, + expectedErrMsg: "unexpected ServerStatusRequest phase", + expectedRequeue: ctrl.Result{Requeue: false, RequeueAfter: 0}, + }), + ) }) type fakePluginLister struct { diff --git a/site/content/docs/main/tilt.md b/site/content/docs/main/tilt.md index ce252951b..ac0bd52b8 100644 --- a/site/content/docs/main/tilt.md +++ b/site/content/docs/main/tilt.md @@ -31,7 +31,6 @@ Note: To properly configure any plugin you use, please follow the plugin's docum ### tl;dr - Copy all sample files under `velero/tilt-resources/examples` into `velero/tilt-resources`. -- Copy the `config/samples/velero_v1_backupstoragelocation.yaml` file into `velero/tilt-resources`. - Configure the `velero_v1_backupstoragelocation.yaml` file, and the `cloud` file for the storage credentials/secret. - Run `tilt up`. @@ -101,7 +100,7 @@ storage location. A sample file is provided that needs to be modified with the s configuration for your object storage. See the next sub-section for more details on this. ### Configure a backup storage location -You will have to configure the `backupstoragelocation.yaml` with the proper values according to your storage provider. Read the [plugin documentation](https://velero.io/plugins/) +You will have to configure the `velero/tilt-resources/velero_v1_backupstoragelocation.yaml` with the proper values according to your storage provider. Read the [plugin documentation](https://velero.io/plugins/) to learn what field/value pairs are required for your particular provider's backup storage location configuration. Below are some ways to configure a backup storage location for Velero. diff --git a/site/content/docs/v1.5/tilt.md b/site/content/docs/v1.5/tilt.md index b797990cb..ac0bd52b8 100644 --- a/site/content/docs/v1.5/tilt.md +++ b/site/content/docs/v1.5/tilt.md @@ -27,12 +27,10 @@ files in this directory are gitignored so you may configure your setup according 1. Clone any [provider plugin(s)](https://velero.io/plugins/) you want to make changes to and deploy (optional, must be configured to be deployed by the Velero Tilt's setup, [more info below](#provider-plugins)) Note: To properly configure any plugin you use, please follow the plugin's documentation. - ## Getting started ### tl;dr - Copy all sample files under `velero/tilt-resources/examples` into `velero/tilt-resources`. -- Copy the `config/samples/velero_v1_backupstoragelocation.yaml` file into `velero/tilt-resources`. - Configure the `velero_v1_backupstoragelocation.yaml` file, and the `cloud` file for the storage credentials/secret. - Run `tilt up`. @@ -74,7 +72,7 @@ Here is an example: **provider_repos** (Array[]String, default=[]): A list of paths to all the provider plugins you want to make changes to. Each provider must have a `tilt-provider.json` file describing how to build the provider. -**enable_providers** (Array[]String, default=[]): A list of the provider plugins to enable. See [provider plugins](#provider-plugins) +**enable_providers** (Array[]String, default=[]): A list of the provider plugins to enable. See [provider plugins](provider-plugins) for more details. Note: when not making changes to a plugin, it is not necessary to load them into Tilt: an existing image and version might be specified in the Velero deployment instead, and Tilt will load that. @@ -102,7 +100,7 @@ storage location. A sample file is provided that needs to be modified with the s configuration for your object storage. See the next sub-section for more details on this. ### Configure a backup storage location -You will have to configure the `backupstoragelocation.yaml` with the proper values according to your storage provider. Read the [plugin documentation](https://velero.io/plugins/) +You will have to configure the `velero/tilt-resources/velero_v1_backupstoragelocation.yaml` with the proper values according to your storage provider. Read the [plugin documentation](https://velero.io/plugins/) to learn what field/value pairs are required for your particular provider's backup storage location configuration. Below are some ways to configure a backup storage location for Velero. diff --git a/config/samples/velero_v1_backupstoragelocation.yaml b/tilt-resources/examples/velero_v1_backupstoragelocation.yaml similarity index 100% rename from config/samples/velero_v1_backupstoragelocation.yaml rename to tilt-resources/examples/velero_v1_backupstoragelocation.yaml