Unverified Commit c952932f authored by Carlisia Campos's avatar Carlisia Campos Committed by GitHub
Browse files

Migrate ServerStatusRequest controller and resource to kubebuilder (#2838)


* Convert ServerStatusRequest controller to controller-runtime
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Add select stm
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Fixed status patch bug
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Add mgr start
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Trying to sync
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Clean async now
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Clean up + move context out
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Bug: not closing the channel
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Clean up some tests
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Much better way to fetch an update using a backoff loop
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Even better way to retry: use apimachinery lib
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Refactor controller + add test
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* partially fix unit tests
Signed-off-by: default avatarAshish Amarnath <ashisham@vmware.com>

* Fix and add tests
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Add changelog
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Add ability to disable the controller + cleanups
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Fix bug w/ disabling controllers + fix test + clean up
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Move role.yaml to the correct folder
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Add sample serverstatusrequest.yaml
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Add requeue + better formatting
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>

* Increase # of max concurrent reconciles
Signed-off-by: default avatarCarlisia <carlisia@vmware.com>
Co-authored-by: default avatarAshish Amarnath <ashisham@vmware.com>
parent aed504a0
Showing with 760 additions and 269 deletions
+760 -269
Convert ServerStatusRequest controller to kubebuilder
\ No newline at end of file
......@@ -16,6 +16,8 @@ spec:
singular: serverstatusrequest
preserveUnknownFields: false
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
description: ServerStatusRequest is a request to access current status information
......
This diff is collapsed.
......@@ -26,3 +26,23 @@ rules:
- get
- patch
- update
- apiGroups:
- velero.io
resources:
- serverstatusrequests
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- velero.io
resources:
- serverstatusrequests/status
verbs:
- get
- patch
- update
---
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
......@@ -30,7 +30,9 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7
google.golang.org/grpc v1.28.0
google.golang.org/genproto v0.0.0-20200731012542-8145dea6a485 // indirect
google.golang.org/grpc v1.31.0
google.golang.org/protobuf v1.25.0 // indirect
k8s.io/api v0.18.4
k8s.io/apiextensions-apiserver v0.18.4
k8s.io/apimachinery v0.18.4
......
This diff is collapsed.
/*
Copyright 2018 the Velero contributors.
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.
......@@ -14,91 +14,50 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package serverstatusrequest
package velero
import (
"context"
"encoding/json"
"time"
jsonpatch "github.com/evanphx/json-patch"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"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"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
)
const ttl = time.Minute
type PluginLister interface {
// List returns all PluginIdentifiers for kind.
List(kind framework.PluginKind) []framework.PluginIdentifier
// ServerStatus holds information for retrieving installed
// plugins and for updating the ServerStatusRequest timestamp.
type ServerStatus struct {
PluginRegistry PluginLister
Clock clock.Clock
}
// Process fills out new ServerStatusRequest objects and deletes processed ones
// that have expired.
func Process(req *velerov1api.ServerStatusRequest, client velerov1client.ServerStatusRequestsGetter, pluginLister PluginLister, clock clock.Clock, log logrus.FieldLogger) error {
switch req.Status.Phase {
case "", velerov1api.ServerStatusRequestPhaseNew:
log.Info("Processing new ServerStatusRequest")
return errors.WithStack(patch(client, req, func(req *velerov1api.ServerStatusRequest) {
req.Status.ServerVersion = buildinfo.Version
req.Status.ProcessedTimestamp = &metav1.Time{Time: clock.Now()}
req.Status.Phase = velerov1api.ServerStatusRequestPhaseProcessed
req.Status.Plugins = plugins(pluginLister)
}))
case velerov1api.ServerStatusRequestPhaseProcessed:
log.Debug("Checking whether ServerStatusRequest has expired")
expiration := req.Status.ProcessedTimestamp.Add(ttl)
if expiration.After(clock.Now()) {
log.Debug("ServerStatusRequest has not expired")
return nil
}
log.Debug("ServerStatusRequest has expired, deleting it")
if err := client.ServerStatusRequests(req.Namespace).Delete(context.TODO(), req.Name, metav1.DeleteOptions{}); err != nil {
return errors.WithStack(err)
}
// 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)
return nil
default:
return errors.Errorf("unexpected ServerStatusRequest phase %q", req.Status.Phase)
}
}
func patch(client velerov1client.ServerStatusRequestsGetter, req *velerov1api.ServerStatusRequest, updateFunc func(*velerov1api.ServerStatusRequest)) error {
originalJSON, err := json.Marshal(req)
if err != nil {
return errors.WithStack(err)
}
updateFunc(req)
updatedJSON, err := json.Marshal(req)
if err != nil {
return errors.WithStack(err)
}
patchBytes, err := jsonpatch.CreateMergePatch(originalJSON, updatedJSON)
if err != nil {
return errors.WithStack(err)
}
_, err = client.ServerStatusRequests(req.Namespace).Patch(context.TODO(), req.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
if err != nil {
if err := kbClient.Status().Patch(ctx, req, statusPatch); err != nil {
return errors.WithStack(err)
}
return nil
}
func plugins(pluginLister PluginLister) []velerov1api.PluginInfo {
type PluginLister interface {
// List returns all PluginIdentifiers for kind.
List(kind framework.PluginKind) []framework.PluginIdentifier
}
func getInstalledPluginInfo(pluginLister PluginLister) []velerov1api.PluginInfo {
var plugins []velerov1api.PluginInfo
for _, v := range framework.AllPluginKinds() {
list := pluginLister.List(v)
......
/*
Copyright 2018 the Velero contributors.
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.
......@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package serverstatusrequest
package velero
import (
"context"
......@@ -22,25 +22,26 @@ import (
"testing"
"time"
"github.com/sirupsen/logrus"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/fake"
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
)
func statusRequestBuilder() *builder.ServerStatusRequestBuilder {
return builder.ForServerStatusRequest(velerov1api.DefaultNamespace, "sr-1")
func statusRequestBuilder(resourceVersion string) *builder.ServerStatusRequestBuilder {
return builder.ForServerStatusRequest(velerov1api.DefaultNamespace, "sr-1", resourceVersion)
}
func TestProcess(t *testing.T) {
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)
......@@ -54,11 +55,10 @@ func TestProcess(t *testing.T) {
req *velerov1api.ServerStatusRequest
reqPluginLister *fakePluginLister
expected *velerov1api.ServerStatusRequest
expectedErrMsg string
}{
{
name: "server status request with empty phase gets processed",
req: statusRequestBuilder().Result(),
req: statusRequestBuilder("0").Result(),
reqPluginLister: &fakePluginLister{
plugins: []framework.PluginIdentifier{
{
......@@ -67,7 +67,7 @@ func TestProcess(t *testing.T) {
},
},
},
expected: statusRequestBuilder().
expected: statusRequestBuilder("1").
ServerVersion(buildinfo.Version).
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now).
......@@ -81,7 +81,7 @@ func TestProcess(t *testing.T) {
},
{
name: "server status request with phase=New gets processed",
req: statusRequestBuilder().
req: statusRequestBuilder("0").
Phase(velerov1api.ServerStatusRequestPhaseNew).
Result(),
reqPluginLister: &fakePluginLister{
......@@ -96,7 +96,7 @@ func TestProcess(t *testing.T) {
},
},
},
expected: statusRequestBuilder().
expected: statusRequestBuilder("1").
ServerVersion(buildinfo.Version).
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now).
......@@ -113,65 +113,65 @@ func TestProcess(t *testing.T) {
Result(),
},
{
name: "server status request with phase=Processed gets deleted if expired",
req: statusRequestBuilder().
name: "server status request with phase=Processed gets processed",
req: statusRequestBuilder("0").
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now.Add(-61 * time.Second)).
Result(),
reqPluginLister: &fakePluginLister{
plugins: []framework.PluginIdentifier{
{
Name: "velero.io/aws",
Kind: "ObjectStore",
},
{
Name: "custom.io/myown",
Kind: "VolumeSnapshotter",
},
},
},
expected: nil,
},
{
name: "server status request with phase=Processed does not get deleted if not expired",
req: statusRequestBuilder().
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now.Add(-59 * time.Second)).
Result(),
expected: statusRequestBuilder().
expected: statusRequestBuilder("1").
ServerVersion(buildinfo.Version).
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now.Add(-59 * time.Second)).
Result(),
},
{
name: "server status request with invalid phase returns an error",
req: statusRequestBuilder().
Phase(velerov1api.ServerStatusRequestPhase("an-invalid-phase")).
Result(),
expected: statusRequestBuilder().
Phase(velerov1api.ServerStatusRequestPhase("an-invalid-phase")).
ProcessedTimestamp(now).
Plugins([]velerov1api.PluginInfo{
{
Name: "velero.io/aws",
Kind: "ObjectStore",
},
{
Name: "custom.io/myown",
Kind: "VolumeSnapshotter",
},
}).
Result(),
expectedErrMsg: "unexpected ServerStatusRequest phase \"an-invalid-phase\"",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
client := fake.NewSimpleClientset(tc.req)
g := NewWithT(t)
err := Process(tc.req, client.VeleroV1(), tc.reqPluginLister, clock.NewFakeClock(now), logrus.StandardLogger())
if tc.expectedErrMsg == "" {
assert.Nil(t, err)
} else {
assert.EqualError(t, err, tc.expectedErrMsg)
serverStatusInfo := ServerStatus{
PluginRegistry: tc.reqPluginLister,
Clock: clock.NewFakeClock(now),
}
res, err := client.VeleroV1().ServerStatusRequests(tc.req.Namespace).Get(context.TODO(), tc.req.Name, metav1.GetOptions{})
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 {
assert.Nil(t, res)
assert.True(t, apierrors.IsNotFound(err))
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
} else {
sortPluginsByKindAndName(tc.expected.Status.Plugins)
sortPluginsByKindAndName(res.Status.Plugins)
assert.Equal(t, tc.expected.Status.Plugins, res.Status.Plugins)
assert.Equal(t, tc.expected, res)
assert.Nil(t, err)
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())
}
})
}
......
/*
Copyright 2018 the Velero contributors.
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.
......@@ -20,8 +20,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// TODO(2.0) After converting all resources to use the runttime-controller client,
// the genclient and k8s:deepcopy markers will no longer be needed and should be removed.
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
// +kubebuilder:object:generate=true
// +kubebuilder:storageversion
// +kubebuilder:subresource:status
// ServerStatusRequest is a request to access current status information about
// the Velero server.
......@@ -81,7 +87,12 @@ type ServerStatusRequestStatus struct {
Plugins []PluginInfo `json:"plugins,omitempty"`
}
// TODO(2.0) After converting all resources to use the runttime-controller client,
// the k8s:deepcopy marker will no longer be needed and should be removed.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
// +kubebuilder:rbac:groups=velero.io,resources=serverstatusrequests,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=velero.io,resources=serverstatusrequests/status,verbs=get;update;patch
// ServerStatusRequestList is a list of ServerStatusRequests.
type ServerStatusRequestList struct {
......
......@@ -30,7 +30,7 @@ type ServerStatusRequestBuilder struct {
}
// ForServerStatusRequest is the constructor for for a ServerStatusRequestBuilder.
func ForServerStatusRequest(ns, name string) *ServerStatusRequestBuilder {
func ForServerStatusRequest(ns, name, resourceVersion string) *ServerStatusRequestBuilder {
return &ServerStatusRequestBuilder{
object: &velerov1api.ServerStatusRequest{
TypeMeta: metav1.TypeMeta{
......@@ -38,8 +38,9 @@ func ForServerStatusRequest(ns, name string) *ServerStatusRequestBuilder {
Kind: "ServerStatusRequest",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
Namespace: ns,
Name: name,
ResourceVersion: resourceVersion,
},
},
}
......
......@@ -17,6 +17,7 @@ limitations under the License.
package plugin
import (
"context"
"fmt"
"os"
"time"
......@@ -30,9 +31,7 @@ import (
)
func NewGetCommand(f client.Factory, use string) *cobra.Command {
serverStatusGetter := &serverstatus.DefaultServerStatusGetter{
Timeout: 5 * time.Second,
}
timeout := 5 * time.Second
c := &cobra.Command{
Use: use,
......@@ -41,17 +40,18 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command {
err := output.ValidateFlags(c)
cmd.CheckError(err)
kbClient, err := f.KubebuilderClient()
cmd.CheckError(err)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
serverStatusGetter := &serverstatus.DefaultServerStatusGetter{
Namespace: f.Namespace(),
Timeout: 5 * time.Second,
Context: ctx,
}
client, err := f.Client()
cmd.CheckError(err)
veleroClient := client.VeleroV1()
serverStatus, err := serverStatusGetter.GetServerStatus(veleroClient)
serverStatus, err := serverStatusGetter.GetServerStatus(kbClient)
if err != nil {
fmt.Fprintf(os.Stdout, "<error getting plugin information: %s>\n", err)
return
......@@ -62,7 +62,7 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command {
},
}
c.Flags().DurationVar(&serverStatusGetter.Timeout, "timeout", serverStatusGetter.Timeout, "Maximum time to wait for plugin information to be reported.")
c.Flags().DurationVar(&timeout, "timeout", timeout, "maximum time to wait for plugin information to be reported. Default is 5 seconds.")
output.BindFlagsSimple(c.Flags())
return c
......
/*
Copyright 2019 the Velero contributors.
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.
......@@ -21,78 +21,53 @@ import (
"time"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apimachinery/pkg/util/wait"
"sigs.k8s.io/controller-runtime/pkg/client"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
)
type ServerStatusGetter interface {
GetServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error)
GetServerStatus(kbClient kbclient.Client) (*velerov1api.ServerStatusRequest, error)
}
type DefaultServerStatusGetter struct {
Namespace string
Timeout time.Duration
Context context.Context
}
func (g *DefaultServerStatusGetter) GetServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error) {
req := builder.ForServerStatusRequest(g.Namespace, "").
ObjectMeta(
builder.WithGenerateName("velero-cli-"),
).Result()
func (g *DefaultServerStatusGetter) GetServerStatus(kbClient kbclient.Client) (*velerov1api.ServerStatusRequest, error) {
created := builder.ForServerStatusRequest(g.Namespace, "", "0").ObjectMeta(builder.WithGenerateName("velero-cli-")).Result()
created, err := client.ServerStatusRequests(g.Namespace).Create(context.TODO(), req, metav1.CreateOptions{})
if err != nil {
if err := kbClient.Create(context.Background(), created, &kbclient.CreateOptions{}); err != nil {
return nil, errors.WithStack(err)
}
defer client.ServerStatusRequests(g.Namespace).Delete(context.TODO(), created.Name, metav1.DeleteOptions{})
listOptions := metav1.ListOptions{
// TODO: once the minimum supported Kubernetes version is v1.9.0, uncomment the following line.
ctx, cancel := context.WithCancel(g.Context)
defer cancel()
key := client.ObjectKey{Name: created.Name, Namespace: g.Namespace}
checkFunc := func() {
updated := &velerov1api.ServerStatusRequest{}
if err := kbClient.Get(ctx, key, updated); err != nil {
return
}
// TODO: once the minimum supported Kubernetes version is v1.9.0, remove the following check.
// See http://issue.k8s.io/51046 for details.
//FieldSelector: "metadata.name=" + req.Name
ResourceVersion: created.ResourceVersion,
}
watcher, err := client.ServerStatusRequests(g.Namespace).Watch(context.TODO(), listOptions)
if err != nil {
return nil, errors.WithStack(err)
}
defer watcher.Stop()
expired := time.NewTimer(g.Timeout)
defer expired.Stop()
Loop:
for {
select {
case <-expired.C:
return nil, errors.New("timed out waiting for server status request to be processed")
case e := <-watcher.ResultChan():
updated, ok := e.Object.(*velerov1api.ServerStatusRequest)
if !ok {
return nil, errors.Errorf("unexpected type %T", e.Object)
}
// TODO: once the minimum supported Kubernetes version is v1.9.0, remove the following check.
// See http://issue.k8s.io/51046 for details.
if updated.Name != created.Name {
continue
}
switch e.Type {
case watch.Deleted:
return nil, errors.New("server status request was unexpectedly deleted")
case watch.Modified:
if updated.Status.Phase == velerov1api.ServerStatusRequestPhaseProcessed {
req = updated
break Loop
}
}
if updated.Name != created.Name {
return
}
if updated.Status.Phase == velerov1api.ServerStatusRequestPhaseProcessed {
created = updated
cancel()
}
}
return req, nil
wait.Until(checkFunc, 250*time.Millisecond, ctx.Done())
return created, nil
}
......@@ -17,51 +17,54 @@ limitations under the License.
package version
import (
"context"
"fmt"
"io"
"os"
"time"
"github.com/spf13/cobra"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/vmware-tanzu/velero/pkg/buildinfo"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/serverstatus"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
)
func NewCommand(f client.Factory) *cobra.Command {
clientOnly := false
serverStatusGetter := &serverstatus.DefaultServerStatusGetter{
Namespace: f.Namespace(),
Timeout: 5 * time.Second,
}
var clientOnly bool
timeout := 5 * time.Second
c := &cobra.Command{
Use: "version",
Short: "Print the velero version and associated image",
Run: func(c *cobra.Command, args []string) {
var veleroClient velerov1client.ServerStatusRequestsGetter
var kbClient kbclient.Client
if !clientOnly {
client, err := f.Client()
var err error
kbClient, err = f.KubebuilderClient()
cmd.CheckError(err)
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
veleroClient = client.VeleroV1()
serverStatusGetter := &serverstatus.DefaultServerStatusGetter{
Namespace: f.Namespace(),
Context: ctx,
}
serverStatusGetter.Namespace = f.Namespace()
printVersion(os.Stdout, clientOnly, veleroClient, serverStatusGetter)
printVersion(os.Stdout, clientOnly, kbClient, serverStatusGetter)
},
}
c.Flags().DurationVar(&serverStatusGetter.Timeout, "timeout", serverStatusGetter.Timeout, "Maximum time to wait for server version to be reported")
c.Flags().BoolVar(&clientOnly, "client-only", clientOnly, "Only get velero client version, not server version")
c.Flags().DurationVar(&timeout, "timeout", timeout, "maximum time to wait for server version to be reported. Default is 5 seconds.")
c.Flags().BoolVar(&clientOnly, "client-only", clientOnly, "only get velero client version, not server version")
return c
}
func printVersion(w io.Writer, clientOnly bool, client velerov1client.ServerStatusRequestsGetter, serverStatusGetter serverstatus.ServerStatusGetter) {
func printVersion(w io.Writer, clientOnly bool, kbClient kbclient.Client, serverStatusGetter serverstatus.ServerStatusGetter) {
fmt.Fprintln(w, "Client:")
fmt.Fprintf(w, "\tVersion: %s\n", buildinfo.Version)
fmt.Fprintf(w, "\tGit commit: %s\n", buildinfo.FormattedGitSHA())
......@@ -70,7 +73,7 @@ func printVersion(w io.Writer, clientOnly bool, client velerov1client.ServerStat
return
}
serverStatus, err := serverStatusGetter.GetServerStatus(client)
serverStatus, err := serverStatusGetter.GetServerStatus(kbClient)
if err != nil {
fmt.Fprintf(w, "<error getting server version: %s>\n", err)
return
......
/*
Copyright 2019 the Velero contributors.
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.
......@@ -24,12 +24,13 @@ import (
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
velerov1 "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/fake"
v1 "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme"
)
func TestPrintVersion(t *testing.T) {
......@@ -73,7 +74,7 @@ func TestPrintVersion(t *testing.T) {
{
name: "server status getter returns normally",
clientOnly: false,
serverStatusRequest: builder.ForServerStatusRequest("velero", "ssr-1").ServerVersion("v1.0.1").Result(),
serverStatusRequest: builder.ForServerStatusRequest("velero", "ssr-1", "0").ServerVersion("v1.0.1").Result(),
getterError: nil,
want: clientVersion + "Server:\n\tVersion: v1.0.1\n",
},
......@@ -82,36 +83,37 @@ func TestPrintVersion(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var (
kbClient = fake.NewFakeClientWithScheme(scheme.Scheme)
serverStatusGetter = new(mockServerStatusGetter)
buf = new(bytes.Buffer)
client = fake.NewSimpleClientset()
)
defer serverStatusGetter.AssertExpectations(t)
// GetServerStatus should only be called when clientOnly = false
if !tc.clientOnly {
serverStatusGetter.On("GetServerStatus", client.VeleroV1()).Return(tc.serverStatusRequest, tc.getterError)
serverStatusGetter.On("GetServerStatus", kbClient).Return(tc.serverStatusRequest, tc.getterError)
}
printVersion(buf, tc.clientOnly, client.VeleroV1(), serverStatusGetter)
printVersion(buf, tc.clientOnly, kbClient, serverStatusGetter)
assert.Equal(t, tc.want, buf.String())
})
}
}
// serverStatusGetter is an autogenerated mock type for the serverStatusGetter type
// mockServerStatusGetter is an autogenerated mock type for the serverStatusGetter type
// Code generated by mockery v2.2.1.
type mockServerStatusGetter struct {
mock.Mock
}
// GetServerStatus provides a mock function with given fields: client
func (_m *mockServerStatusGetter) GetServerStatus(client v1.ServerStatusRequestsGetter) (*velerov1.ServerStatusRequest, error) {
ret := _m.Called(client)
// GetServerStatus provides a mock function with given fields: mgr
func (_m *mockServerStatusGetter) GetServerStatus(kbClient kbclient.Client) (*velerov1.ServerStatusRequest, error) {
ret := _m.Called(kbClient)
var r0 *velerov1.ServerStatusRequest
if rf, ok := ret.Get(0).(func(v1.ServerStatusRequestsGetter) *velerov1.ServerStatusRequest); ok {
r0 = rf(client)
if rf, ok := ret.Get(0).(func(kbclient.Client) *velerov1.ServerStatusRequest); ok {
r0 = rf(kbClient)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*velerov1.ServerStatusRequest)
......@@ -119,8 +121,8 @@ func (_m *mockServerStatusGetter) GetServerStatus(client v1.ServerStatusRequests
}
var r1 error
if rf, ok := ret.Get(1).(func(v1.ServerStatusRequestsGetter) error); ok {
r1 = rf(client)
if rf, ok := ret.Get(1).(func(kbclient.Client) error); ok {
r1 = rf(kbClient)
} else {
r1 = ret.Error(1)
}
......
......@@ -34,6 +34,7 @@ import (
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"
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
......@@ -777,31 +778,19 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
}
}
serverStatusRequestControllerRunInfo := func() controllerRunInfo {
serverStatusRequestController := controller.NewServerStatusRequestController(
s.logger,
s.veleroClient.VeleroV1(),
s.sharedInformerFactory.Velero().V1().ServerStatusRequests(),
s.pluginRegistry,
)
return controllerRunInfo{
controller: serverStatusRequestController,
numWorkers: defaultControllerWorkers,
}
}
enabledControllers := map[string]func() controllerRunInfo{
BackupSyncControllerKey: backupSyncControllerRunInfo,
BackupControllerKey: backupControllerRunInfo,
ScheduleControllerKey: scheduleControllerRunInfo,
GcControllerKey: gcControllerRunInfo,
BackupDeletionControllerKey: deletionControllerRunInfo,
RestoreControllerKey: restoreControllerRunInfo,
ResticRepoControllerKey: resticRepoControllerRunInfo,
DownloadRequestControllerKey: downloadrequestControllerRunInfo,
ServerStatusRequestControllerKey: serverStatusRequestControllerRunInfo,
}
BackupSyncControllerKey: backupSyncControllerRunInfo,
BackupControllerKey: backupControllerRunInfo,
ScheduleControllerKey: scheduleControllerRunInfo,
GcControllerKey: gcControllerRunInfo,
BackupDeletionControllerKey: deletionControllerRunInfo,
RestoreControllerKey: restoreControllerRunInfo,
ResticRepoControllerKey: resticRepoControllerRunInfo,
DownloadRequestControllerKey: downloadrequestControllerRunInfo,
}
// Note: all runtime type controllers that can be disabled are grouped separately, below:
enabledRuntimeControllers := make(map[string]struct{})
enabledRuntimeControllers[ServerStatusRequestControllerKey] = struct{}{}
if s.config.restoreOnly {
s.logger.Info("Restore only mode - not starting the backup, schedule, delete-backup, or GC controllers")
......@@ -819,7 +808,13 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.logger.Infof("Disabling controller: %s", controllerName)
delete(enabledControllers, controllerName)
} else {
s.logger.Fatalf("Invalid value for --disable-controllers flag provided: %s. Valid values are: %s", controllerName, strings.Join(disableControllerList, ","))
// 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(disableControllerList, ","))
}
}
}
......@@ -852,20 +847,36 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.logger.WithField("informer", informer).Info("Informer cache synced")
}
storageLocationInfo := velero.StorageLocation{
Client: s.mgr.GetClient(),
Ctx: s.ctx,
DefaultStorageLocation: s.config.defaultBackupLocation,
DefaultStoreValidationFrequency: s.config.storeValidationFrequency,
NewPluginManager: newPluginManager,
NewBackupStore: persistence.NewObjectBackupStore,
}
if err := (&controller.BackupStorageLocationReconciler{
Scheme: s.mgr.GetScheme(),
StorageLocation: storageLocationInfo,
Log: s.logger,
}).SetupWithManager(s.mgr); err != nil {
s.logger.Fatal(err, "unable to create controller", "controller", "BackupStorageLocation")
bslr := controller.BackupStorageLocationReconciler{
Scheme: s.mgr.GetScheme(),
StorageLocation: velero.StorageLocation{
Client: s.mgr.GetClient(),
Ctx: s.ctx,
DefaultStorageLocation: s.config.defaultBackupLocation,
DefaultStoreValidationFrequency: s.config.storeValidationFrequency,
NewPluginManager: newPluginManager,
NewBackupStore: persistence.NewObjectBackupStore,
},
Log: s.logger,
}
if err := bslr.SetupWithManager(s.mgr); err != nil {
s.logger.Fatal(err, "unable to create controller", "controller", "backup-storage-location")
}
if _, ok := enabledRuntimeControllers[ServerStatusRequestControllerKey]; 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,
}
if err := r.SetupWithManager(s.mgr); err != nil {
s.logger.Fatal(err, "unable to create controller", "controller", ServerStatusRequestControllerKey)
}
}
// TODO(2.0): presuming all controllers and resources are converted to runtime-controller
......
......@@ -17,104 +17,105 @@ limitations under the License.
package controller
import (
"context"
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/clock"
"k8s.io/client-go/tools/cache"
"k8s.io/apimachinery/pkg/runtime"
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"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
velerov1informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions/velero/v1"
velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/serverstatusrequest"
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
)
const statusRequestResyncPeriod = 5 * time.Minute
type statusRequestController struct {
*genericController
const (
ttl = time.Minute
statusRequestResyncPeriod = 5 * time.Minute
)
client velerov1client.ServerStatusRequestsGetter
lister velerov1listers.ServerStatusRequestLister
pluginRegistry clientmgmt.Registry
clock clock.Clock
type PluginLister interface {
// List returns all PluginIdentifiers for kind.
List(kind framework.PluginKind) []framework.PluginIdentifier
}
func NewServerStatusRequestController(
logger logrus.FieldLogger,
client velerov1client.ServerStatusRequestsGetter,
informer velerov1informers.ServerStatusRequestInformer,
pluginRegistry clientmgmt.Registry,
) *statusRequestController {
c := &statusRequestController{
genericController: newGenericController("serverstatusrequest", logger),
client: client,
lister: informer.Lister(),
pluginRegistry: pluginRegistry,
clock: clock.RealClock{},
}
c.syncHandler = c.processItem
c.resyncFunc = c.enqueueAllItems
c.resyncPeriod = statusRequestResyncPeriod
informer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
req := obj.(*velerov1api.ServerStatusRequest)
key := kubeutil.NamespaceAndName(req)
c.logger.WithFields(logrus.Fields{
"serverStatusRequest": key,
"phase": req.Status.Phase,
}).Debug("Enqueueing server status request")
c.queue.Add(key)
},
},
)
// ServerStatusRequestReconciler reconciles a ServerStatusRequest object
type ServerStatusRequestReconciler struct {
Scheme *runtime.Scheme
Client client.Client
Ctx context.Context
ServerStatus velero.ServerStatus
return c
Log logrus.FieldLogger
}
func (c *statusRequestController) processItem(key string) error {
log := c.logger.WithField("key", key)
log.Debug("Running processItem")
ns, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
return errors.Wrap(err, "error splitting queue key")
}
// +kubebuilder:rbac:groups=velero.io,resources=serverstatusrequests,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=velero.io,resources=serverstatusrequests/status,verbs=get;update;patch
func (r *ServerStatusRequestReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithFields(logrus.Fields{
"controller": "serverstatusrequest",
"serverStatusRequest": req.NamespacedName,
})
// Fetch the ServerStatusRequest instance.
log.Debug("Getting ServerStatusRequest")
req, err := c.lister.ServerStatusRequests(ns).Get(name)
// server status request no longer exists
if apierrors.IsNotFound(err) {
log.WithError(err).Debug("ServerStatusRequest not found")
return nil
statusRequest := &velerov1api.ServerStatusRequest{}
if err := r.Client.Get(r.Ctx, req.NamespacedName, statusRequest); err != nil {
if apierrors.IsNotFound(err) {
log.WithError(err).Error("ServerStatusRequest not found")
return ctrl.Result{}, nil
}
log.WithError(err).Error("Error getting ServerStatusRequest")
// Error reading the object - requeue the request.
return ctrl.Result{}, err
}
if err != nil {
return errors.Wrap(err, "error getting ServerStatusRequest")
log = r.Log.WithFields(logrus.Fields{
"controller": "serverstatusrequest",
"serverStatusRequest": req.NamespacedName,
"phase": statusRequest.Status.Phase,
})
switch statusRequest.Status.Phase {
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")
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()) {
log.Debug("ServerStatusRequest has not expired")
return ctrl.Result{RequeueAfter: statusRequestResyncPeriod}, nil
}
log.Debug("ServerStatusRequest has expired, deleting it")
if err := r.Client.Delete(r.Ctx, statusRequest); err != nil {
log.WithError(err).Error("Unable to delete the request")
return ctrl.Result{}, nil
}
default:
return ctrl.Result{}, errors.New("unexpected ServerStatusRequest phase")
}
return serverstatusrequest.Process(req.DeepCopy(), c.client, c.pluginRegistry, c.clock, log)
// Requeue is mostly to handle deleting any expired status requests that were not
// deleted as part of the normal client flow for whatever reason.
return ctrl.Result{RequeueAfter: statusRequestResyncPeriod}, nil
}
func (c *statusRequestController) enqueueAllItems() {
items, err := c.lister.List(labels.Everything())
if err != nil {
c.logger.WithError(err).Error("Error listing all server status requests")
return
}
for _, req := range items {
c.enqueue(req)
}
func (r *ServerStatusRequestReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&velerov1api.ServerStatusRequest{}).
WithOptions(controller.Options{
MaxConcurrentReconciles: 10,
}).
Complete(r)
}
/*
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 controller
import (
"context"
"time"
. "github.com/onsi/ginkgo"
. "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"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
)
func statusRequestBuilder(resourceVersion string) *builder.ServerStatusRequestBuilder {
return builder.ForServerStatusRequest(velerov1api.DefaultNamespace, "sr-1", resourceVersion)
}
var _ = Describe("Server Status Request Reconciler", func() {
BeforeEach(func() {})
AfterEach(func() {})
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()
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 {
// Setup reconciler
Expect(velerov1api.AddToScheme(scheme.Scheme)).To(Succeed())
serverStatusInfo := velero.ServerStatus{
PluginRegistry: test.reqPluginLister,
Clock: clock.NewFakeClock(now),
}
r := ServerStatusRequestReconciler{
Client: fake.NewFakeClientWithScheme(scheme.Scheme, test.req),
ServerStatus: serverStatusInfo,
Ctx: context.Background(),
Log: velerotest.NewLogger(),
}
actualResult, err := r.Reconcile(ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: velerov1api.DefaultNamespace,
Name: test.req.Name,
},
})
Expect(actualResult).To(BeEquivalentTo(test.expectedRequeue))
if test.expectedErrMsg == "" {
Expect(err).To(BeNil())
} else {
Expect(err.Error()).To(BeEquivalentTo(test.expectedErrMsg))
return
}
instance := &velerov1api.ServerStatusRequest{}
err = r.Client.Get(ctx, kbclient.ObjectKey{Name: test.req.Name, Namespace: test.req.Namespace}, instance)
// Assertions
if test.expected == nil {
Expect(apierrors.IsNotFound(err)).To(BeTrue())
} else {
Expect(err).To(BeNil())
Eventually(instance.Status.Phase == test.expected.Status.Phase, timeout).Should(BeTrue())
}
}
})
})
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
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment