Add cmd to list plugins (#1535)

* Add cmd to list plugins

Signed-off-by: Carlisia <carlisiac@vmware.com>
This commit is contained in:
KubeKween
2019-06-05 10:41:03 -07:00
committed by Nolan Brubaker
parent bc7ee686d7
commit 0a771e6a53
20 changed files with 403 additions and 143 deletions

View File

@@ -0,0 +1 @@
Add CLI command to list (get) all Velero plugins

View File

@@ -16,7 +16,9 @@ limitations under the License.
package v1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -45,6 +47,12 @@ const (
ServerStatusRequestPhaseProcessed ServerStatusRequestPhase = "Processed"
)
// PluginInfo contains attributes of a Velero plugin
type PluginInfo struct {
Name string `json:"name"`
Kind string `json:"kind"`
}
// ServerStatusRequestStatus is the current status of a ServerStatusRequest.
type ServerStatusRequestStatus struct {
// Phase is the current lifecycle phase of the ServerStatusRequest.
@@ -56,6 +64,9 @@ type ServerStatusRequestStatus struct {
// ServerVersion is the Velero server version.
ServerVersion string `json:"serverVersion"`
// Plugins list information about the plugins running on the Velero server
Plugins []PluginInfo `json:"plugins"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@@ -616,6 +616,22 @@ func (in *ObjectStorageLocation) DeepCopy() *ObjectStorageLocation {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PluginInfo) DeepCopyInto(out *PluginInfo) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginInfo.
func (in *PluginInfo) DeepCopy() *PluginInfo {
if in == nil {
return nil
}
out := new(PluginInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PodVolumeBackup) DeepCopyInto(out *PodVolumeBackup) {
*out = *in
@@ -1227,6 +1243,11 @@ func (in *ServerStatusRequestSpec) DeepCopy() *ServerStatusRequestSpec {
func (in *ServerStatusRequestStatus) DeepCopyInto(out *ServerStatusRequestStatus) {
*out = *in
in.ProcessedTimestamp.DeepCopyInto(&out.ProcessedTimestamp)
if in.Plugins != nil {
in, out := &in.Plugins, &out.Plugins
*out = make([]PluginInfo, len(*in))
copy(*out, *in)
}
return
}

View File

@@ -22,6 +22,7 @@ import (
"github.com/heptio/velero/pkg/client"
"github.com/heptio/velero/pkg/cmd/cli/backup"
"github.com/heptio/velero/pkg/cmd/cli/backuplocation"
"github.com/heptio/velero/pkg/cmd/cli/plugin"
"github.com/heptio/velero/pkg/cmd/cli/restore"
"github.com/heptio/velero/pkg/cmd/cli/schedule"
"github.com/heptio/velero/pkg/cmd/cli/snapshotlocation"
@@ -49,12 +50,16 @@ func NewCommand(f client.Factory) *cobra.Command {
snapshotLocationCommand := snapshotlocation.NewGetCommand(f, "snapshot-locations")
snapshotLocationCommand.Aliases = []string{"snapshot-location"}
pluginCommand := plugin.NewGetCommand(f, "plugins")
pluginCommand.Aliases = []string{"plugin"}
c.AddCommand(
backupCommand,
scheduleCommand,
restoreCommand,
backupLocationCommand,
snapshotLocationCommand,
pluginCommand,
)
return c

69
pkg/cmd/cli/plugin/get.go Normal file
View File

@@ -0,0 +1,69 @@
/*
Copyright 2019 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 plugin
import (
"fmt"
"os"
"time"
"github.com/spf13/cobra"
"github.com/heptio/velero/pkg/client"
"github.com/heptio/velero/pkg/cmd"
"github.com/heptio/velero/pkg/cmd/cli/serverstatus"
"github.com/heptio/velero/pkg/cmd/util/output"
)
func NewGetCommand(f client.Factory, use string) *cobra.Command {
serverStatusGetter := &serverstatus.DefaultServerStatusGetter{
Timeout: 5 * time.Second,
}
c := &cobra.Command{
Use: use,
Short: "Get information for all plugins on the velero server",
Run: func(c *cobra.Command, args []string) {
err := output.ValidateFlags(c)
cmd.CheckError(err)
serverStatusGetter := &serverstatus.DefaultServerStatusGetter{
Namespace: f.Namespace(),
Timeout: 5 * time.Second,
}
client, err := f.Client()
cmd.CheckError(err)
veleroClient := client.VeleroV1()
serverStatus, err := serverStatusGetter.GetServerStatus(veleroClient)
if err != nil {
fmt.Fprintf(os.Stdout, "<error getting plugin information: %s>\n", err)
return
}
_, err = output.PrintWithFormat(c, serverStatus)
cmd.CheckError(err)
},
}
c.Flags().DurationVar(&serverStatusGetter.Timeout, "timeout", serverStatusGetter.Timeout, "maximum time to wait for plugin information to be reported")
output.BindFlagsSimple(c.Flags())
return c
}

View File

@@ -32,6 +32,7 @@ func NewCommand(f client.Factory) *cobra.Command {
c.AddCommand(
NewAddCommand(f),
NewRemoveCommand(f),
NewGetCommand(f, "get"),
)
return c

View File

@@ -1,5 +1,5 @@
/*
Copyright 2017 the Velero contributors.
Copyright 2019 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,93 +14,37 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package version
package serverstatus
import (
"fmt"
"io"
"os"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
"github.com/heptio/velero/pkg/buildinfo"
"github.com/heptio/velero/pkg/client"
"github.com/heptio/velero/pkg/cmd"
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
"github.com/heptio/velero/pkg/serverstatusrequest"
)
func NewCommand(f client.Factory) *cobra.Command {
clientOnly := false
serverStatusGetter := &defaultServerStatusGetter{
namespace: f.Namespace(),
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
if !clientOnly {
client, err := f.Client()
cmd.CheckError(err)
veleroClient = client.VeleroV1()
}
printVersion(os.Stdout, clientOnly, veleroClient, 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")
return c
type ServerStatusGetter interface {
GetServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error)
}
func printVersion(w io.Writer, clientOnly bool, client velerov1client.ServerStatusRequestsGetter, serverStatusGetter serverStatusGetter) {
fmt.Fprintln(w, "Client:")
fmt.Fprintf(w, "\tVersion: %s\n", buildinfo.Version)
fmt.Fprintf(w, "\tGit commit: %s\n", buildinfo.FormattedGitSHA())
if clientOnly {
return
}
serverStatus, err := serverStatusGetter.getServerStatus(client)
if err != nil {
fmt.Fprintf(w, "<error getting server version: %s>\n", err)
return
}
fmt.Fprintln(w, "Server:")
fmt.Fprintf(w, "\tVersion: %s\n", serverStatus.Status.ServerVersion)
type DefaultServerStatusGetter struct {
Namespace string
Timeout time.Duration
}
type serverStatusGetter interface {
getServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error)
}
func (g *DefaultServerStatusGetter) GetServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error) {
req := serverstatusrequest.NewBuilder().Namespace(g.Namespace).GenerateName("velero-cli-").ServerStatusRequest()
type defaultServerStatusGetter struct {
namespace string
timeout time.Duration
}
func (g *defaultServerStatusGetter) getServerStatus(client velerov1client.ServerStatusRequestsGetter) (*velerov1api.ServerStatusRequest, error) {
req := serverstatusrequest.NewBuilder().Namespace(g.namespace).GenerateName("velero-cli-").Build()
created, err := client.ServerStatusRequests(g.namespace).Create(req)
created, err := client.ServerStatusRequests(g.Namespace).Create(req)
if err != nil {
return nil, errors.WithStack(err)
}
defer client.ServerStatusRequests(g.namespace).Delete(created.Name, nil)
defer client.ServerStatusRequests(g.Namespace).Delete(created.Name, nil)
listOptions := metav1.ListOptions{
// TODO: once the minimum supported Kubernetes version is v1.9.0, uncomment the following line.
@@ -108,13 +52,13 @@ func (g *defaultServerStatusGetter) getServerStatus(client velerov1client.Server
//FieldSelector: "metadata.name=" + req.Name
ResourceVersion: created.ResourceVersion,
}
watcher, err := client.ServerStatusRequests(g.namespace).Watch(listOptions)
watcher, err := client.ServerStatusRequests(g.Namespace).Watch(listOptions)
if err != nil {
return nil, errors.WithStack(err)
}
defer watcher.Stop()
expired := time.NewTimer(g.timeout)
expired := time.NewTimer(g.Timeout)
defer expired.Stop()
Loop:

View File

@@ -0,0 +1,81 @@
/*
Copyright 2017, 2019 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 version
import (
"fmt"
"io"
"os"
"time"
"github.com/spf13/cobra"
"github.com/heptio/velero/pkg/buildinfo"
"github.com/heptio/velero/pkg/client"
"github.com/heptio/velero/pkg/cmd"
"github.com/heptio/velero/pkg/cmd/cli/serverstatus"
velerov1client "github.com/heptio/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,
}
c := &cobra.Command{
Use: "version",
Short: "Print the velero version and associated image",
Run: func(c *cobra.Command, args []string) {
var veleroClient velerov1client.ServerStatusRequestsGetter
if !clientOnly {
client, err := f.Client()
cmd.CheckError(err)
veleroClient = client.VeleroV1()
}
printVersion(os.Stdout, clientOnly, veleroClient, 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")
return c
}
func printVersion(w io.Writer, clientOnly bool, client velerov1client.ServerStatusRequestsGetter, serverStatusGetter serverstatus.ServerStatusGetter) {
fmt.Fprintln(w, "Client:")
fmt.Fprintf(w, "\tVersion: %s\n", buildinfo.Version)
fmt.Fprintf(w, "\tGit commit: %s\n", buildinfo.FormattedGitSHA())
if clientOnly {
return
}
serverStatus, err := serverStatusGetter.GetServerStatus(client)
if err != nil {
fmt.Fprintf(w, "<error getting server version: %s>\n", err)
return
}
fmt.Fprintln(w, "Server:")
fmt.Fprintf(w, "\tVersion: %s\n", serverStatus.Status.ServerVersion)
}

View File

@@ -73,7 +73,7 @@ func TestPrintVersion(t *testing.T) {
{
name: "server status getter returns normally",
clientOnly: false,
serverStatusRequest: serverstatusrequest.NewBuilder().ServerVersion("v1.0.1").Build(),
serverStatusRequest: serverstatusrequest.NewBuilder().ServerVersion("v1.0.1").ServerStatusRequest(),
getterError: nil,
want: clientVersion + "Server:\n\tVersion: v1.0.1\n",
},
@@ -88,9 +88,9 @@ func TestPrintVersion(t *testing.T) {
)
defer serverStatusGetter.AssertExpectations(t)
// getServerStatus should only be called when clientOnly = false
// GetServerStatus should only be called when clientOnly = false
if !tc.clientOnly {
serverStatusGetter.On("getServerStatus", client.VeleroV1()).Return(tc.serverStatusRequest, tc.getterError)
serverStatusGetter.On("GetServerStatus", client.VeleroV1()).Return(tc.serverStatusRequest, tc.getterError)
}
printVersion(buf, tc.clientOnly, client.VeleroV1(), serverStatusGetter)
@@ -105,8 +105,8 @@ type mockServerStatusGetter struct {
mock.Mock
}
// getServerStatus provides a mock function with given fields: client
func (_m *mockServerStatusGetter) getServerStatus(client v1.ServerStatusRequestsGetter) (*velerov1.ServerStatusRequest, error) {
// GetServerStatus provides a mock function with given fields: client
func (_m *mockServerStatusGetter) GetServerStatus(client v1.ServerStatusRequestsGetter) (*velerov1.ServerStatusRequest, error) {
ret := _m.Called(client)
var r0 *velerov1.ServerStatusRequest

View File

@@ -734,6 +734,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
s.logger,
s.veleroClient.VeleroV1(),
s.sharedInformerFactory.Velero().V1().ServerStatusRequests(),
s.pluginRegistry,
)
return controllerRunInfo{

View File

@@ -43,6 +43,11 @@ func BindFlags(flags *pflag.FlagSet) {
flags.Bool("show-labels", false, "show labels in the last column")
}
// BindFlagsSimple defines the output format flag only.
func BindFlagsSimple(flags *pflag.FlagSet) {
flags.StringP("output", "o", "table", "Output display format. For create commands, display the object but do not send it to the server. Valid formats are 'table', 'json', and 'yaml'. 'table' is not valid for the install command.")
}
// ClearOutputFlagDefault sets the current and default value
// of the "output" flag to the empty string.
func ClearOutputFlagDefault(cmd *cobra.Command) {
@@ -153,6 +158,7 @@ func printTable(cmd *cobra.Command, obj runtime.Object) (bool, error) {
printer.Handler(backupStorageLocationColumns, nil, printBackupStorageLocationList)
printer.Handler(volumeSnapshotLocationColumns, nil, printVolumeSnapshotLocation)
printer.Handler(volumeSnapshotLocationColumns, nil, printVolumeSnapshotLocationList)
printer.Handler(pluginColumns, nil, printPluginList)
err = printer.PrintObj(obj, os.Stdout)
if err != nil {

View File

@@ -0,0 +1,62 @@
/*
Copyright 2019 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 output
import (
"fmt"
"io"
"sort"
"k8s.io/kubernetes/pkg/printers"
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
)
var (
pluginColumns = []string{"NAME", "KIND"}
)
func printPluginList(list *velerov1api.ServerStatusRequest, w io.Writer, options printers.PrintOptions) error {
plugins := list.Status.Plugins
sortByKindAndName(plugins)
for _, plugin := range plugins {
if err := printPlugin(plugin, w, options); err != nil {
return err
}
}
return nil
}
func sortByKindAndName(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
})
}
func printPlugin(plugin velerov1api.PluginInfo, w io.Writer, options printers.PrintOptions) error {
name := printers.FormatResourceName(options.Kind, plugin.Name, options.WithKind)
if _, err := fmt.Fprintf(w, "%s\t%s\n", name, plugin.Kind); err != nil {
return err
}
return nil
}

View File

@@ -37,9 +37,9 @@ import (
"github.com/heptio/velero/pkg/cmd/cli/restore"
"github.com/heptio/velero/pkg/cmd/cli/schedule"
"github.com/heptio/velero/pkg/cmd/cli/snapshotlocation"
"github.com/heptio/velero/pkg/cmd/cli/version"
"github.com/heptio/velero/pkg/cmd/server"
runplugin "github.com/heptio/velero/pkg/cmd/server/plugin"
"github.com/heptio/velero/pkg/cmd/version"
)
func NewCommand(name string) *cobra.Command {

View File

@@ -30,6 +30,7 @@ import (
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
velerov1informers "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
velerov1listers "github.com/heptio/velero/pkg/generated/listers/velero/v1"
"github.com/heptio/velero/pkg/plugin/clientmgmt"
"github.com/heptio/velero/pkg/serverstatusrequest"
kubeutil "github.com/heptio/velero/pkg/util/kube"
)
@@ -39,20 +40,23 @@ const statusRequestResyncPeriod = 5 * time.Minute
type statusRequestController struct {
*genericController
client velerov1client.ServerStatusRequestsGetter
lister velerov1listers.ServerStatusRequestLister
clock clock.Clock
client velerov1client.ServerStatusRequestsGetter
lister velerov1listers.ServerStatusRequestLister
pluginRegistry clientmgmt.Registry
clock clock.Clock
}
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{},
}
@@ -102,7 +106,7 @@ func (c *statusRequestController) processItem(key string) error {
return errors.Wrap(err, "error getting ServerStatusRequest")
}
return serverstatusrequest.Process(req.DeepCopy(), c.client, c.clock, log)
return serverstatusrequest.Process(req.DeepCopy(), c.client, c.pluginRegistry, c.clock, log)
}
func (c *statusRequestController) enqueueAllItems() {

View File

@@ -16,10 +16,6 @@ limitations under the License.
package framework
import (
"k8s.io/apimachinery/pkg/util/sets"
)
// PluginKind is a type alias for a string that describes
// the kind of a Velero-supported plugin.
type PluginKind string
@@ -46,11 +42,13 @@ const (
PluginKindPluginLister PluginKind = "PluginLister"
)
// allPluginKinds contains all the valid plugin kinds that Velero supports, excluding PluginLister because that is not a
// AllPluginKinds contains all the valid plugin kinds that Velero supports, excluding PluginLister because that is not a
// kind that a developer would ever need to implement (it's handled by Velero and the Velero plugin library code).
var allPluginKinds = sets.NewString(
PluginKindObjectStore.String(),
PluginKindVolumeSnapshotter.String(),
PluginKindBackupItemAction.String(),
PluginKindRestoreItemAction.String(),
)
func AllPluginKinds() map[string]PluginKind {
allPluginKinds := make(map[string]PluginKind)
allPluginKinds[PluginKindObjectStore.String()] = PluginKindObjectStore
allPluginKinds[PluginKindVolumeSnapshotter.String()] = PluginKindVolumeSnapshotter
allPluginKinds[PluginKindBackupItemAction.String()] = PluginKindBackupItemAction
allPluginKinds[PluginKindRestoreItemAction.String()] = PluginKindRestoreItemAction
return allPluginKinds
}

View File

@@ -1,34 +0,0 @@
/*
Copyright 2018, 2019 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 framework
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/util/sets"
)
func TestAllPluginKinds(t *testing.T) {
expected := sets.NewString(
PluginKindObjectStore.String(),
PluginKindVolumeSnapshotter.String(),
PluginKindBackupItemAction.String(),
PluginKindRestoreItemAction.String(),
)
assert.True(t, expected.Equal(allPluginKinds))
}

View File

@@ -87,7 +87,7 @@ func (c *PluginListerGRPCClient) ListPlugins() ([]PluginIdentifier, error) {
ret := make([]PluginIdentifier, len(resp.Plugins))
for i, id := range resp.Plugins {
if !allPluginKinds.Has(id.Kind) {
if _, ok := AllPluginKinds()[id.Kind]; !ok {
return nil, errors.Errorf("invalid plugin kind: %s", id.Kind)
}
@@ -126,7 +126,7 @@ func (s *PluginListerGRPCServer) ListPlugins(ctx context.Context, req *proto.Emp
plugins := make([]*proto.PluginIdentifier, len(list))
for i, id := range list {
if !allPluginKinds.Has(id.Kind.String()) {
if _, ok := AllPluginKinds()[id.Kind.String()]; !ok {
return nil, errors.Errorf("invalid plugin kind: %s", id.Kind)
}

View File

@@ -28,6 +28,7 @@ type Builder struct {
serverStatusRequest velerov1api.ServerStatusRequest
}
// NewBuilder returns a Builder for a ServerStatusRequest.
func NewBuilder() *Builder {
return &Builder{
serverStatusRequest: velerov1api.ServerStatusRequest{
@@ -39,7 +40,8 @@ func NewBuilder() *Builder {
}
}
func (b *Builder) Build() *velerov1api.ServerStatusRequest {
// ServerStatusRequest returns the built ServerStatusRequest API object.
func (b *Builder) ServerStatusRequest() *velerov1api.ServerStatusRequest {
return &b.serverStatusRequest
}
@@ -72,3 +74,8 @@ func (b *Builder) ServerVersion(version string) *Builder {
b.serverStatusRequest.Status.ServerVersion = version
return b
}
func (b *Builder) Plugins(plugins []velerov1api.PluginInfo) *Builder {
b.serverStatusRequest.Status.Plugins = plugins
return b
}

View File

@@ -29,13 +29,19 @@ import (
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
"github.com/heptio/velero/pkg/buildinfo"
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
"github.com/heptio/velero/pkg/plugin/framework"
)
const ttl = time.Minute
type PluginLister interface {
// List returns all PluginIdentifiers for kind.
List(kind framework.PluginKind) []framework.PluginIdentifier
}
// Process fills out new ServerStatusRequest objects and deletes processed ones
// that have expired.
func Process(req *velerov1api.ServerStatusRequest, client velerov1client.ServerStatusRequestsGetter, clock clock.Clock, log logrus.FieldLogger) error {
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")
@@ -43,6 +49,7 @@ func Process(req *velerov1api.ServerStatusRequest, client velerov1client.ServerS
req.Status.ServerVersion = buildinfo.Version
req.Status.ProcessedTimestamp.Time = clock.Now()
req.Status.Phase = velerov1api.ServerStatusRequestPhaseProcessed
req.Status.Plugins = plugins(pluginLister)
}))
case velerov1api.ServerStatusRequestPhaseProcessed:
log.Debug("Checking whether ServerStatusRequest has expired")
@@ -88,3 +95,18 @@ func patch(client velerov1client.ServerStatusRequestsGetter, req *velerov1api.Se
return nil
}
func plugins(pluginLister PluginLister) []velerov1api.PluginInfo {
var plugins []velerov1api.PluginInfo
for _, v := range framework.AllPluginKinds() {
list := pluginLister.List(v)
for _, plugin := range list {
pluginInfo := velerov1api.PluginInfo{
Name: plugin.Name,
Kind: plugin.Kind.String(),
}
plugins = append(plugins, pluginInfo)
}
}
return plugins
}

View File

@@ -30,6 +30,7 @@ import (
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
"github.com/heptio/velero/pkg/buildinfo"
"github.com/heptio/velero/pkg/generated/clientset/versioned/fake"
"github.com/heptio/velero/pkg/plugin/framework"
)
func statusRequestBuilder() *Builder {
@@ -46,37 +47,82 @@ func TestProcess(t *testing.T) {
buildinfo.Version = "test-version-val"
tests := []struct {
name string
req *velerov1api.ServerStatusRequest
expected *velerov1api.ServerStatusRequest
expectedErrMsg string
name string
req *velerov1api.ServerStatusRequest
reqPluginLister *fakePluginLister
expected *velerov1api.ServerStatusRequest
expectedErrMsg string
}{
{
name: "server status request with empty phase gets processed",
req: statusRequestBuilder().Build(),
req: statusRequestBuilder().ServerStatusRequest(),
reqPluginLister: &fakePluginLister{
plugins: []framework.PluginIdentifier{
{
Name: "custom.io/myown",
Kind: "VolumeSnapshotter",
},
},
},
expected: statusRequestBuilder().
ServerVersion(buildinfo.Version).
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now).
Build(),
Plugins([]velerov1api.PluginInfo{
{
Name: "custom.io/myown",
Kind: "VolumeSnapshotter",
},
}).
ServerStatusRequest(),
},
{
name: "server status request with phase=New gets processed",
req: statusRequestBuilder().
Phase(velerov1api.ServerStatusRequestPhaseNew).
Build(),
ServerStatusRequest(),
reqPluginLister: &fakePluginLister{
plugins: []framework.PluginIdentifier{
{
Name: "velero.io/aws",
Kind: "ObjectStore",
},
{
Name: "custom.io/myown",
Kind: "VolumeSnapshotter",
},
},
},
expected: statusRequestBuilder().
ServerVersion(buildinfo.Version).
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now).
Build(),
Plugins([]velerov1api.PluginInfo{
{
Name: "velero.io/aws",
Kind: "ObjectStore",
},
{
Name: "custom.io/myown",
Kind: "VolumeSnapshotter",
},
}).
ServerStatusRequest(),
},
{
name: "server status request with phase=Processed gets deleted if expired",
req: statusRequestBuilder().
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now.Add(-61 * time.Second)).
Build(),
ServerStatusRequest(),
reqPluginLister: &fakePluginLister{
plugins: []framework.PluginIdentifier{
{
Name: "custom.io/myown",
Kind: "VolumeSnapshotter",
},
},
},
expected: nil,
},
{
@@ -84,20 +130,20 @@ func TestProcess(t *testing.T) {
req: statusRequestBuilder().
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now.Add(-59 * time.Second)).
Build(),
ServerStatusRequest(),
expected: statusRequestBuilder().
Phase(velerov1api.ServerStatusRequestPhaseProcessed).
ProcessedTimestamp(now.Add(-59 * time.Second)).
Build(),
ServerStatusRequest(),
},
{
name: "server status request with invalid phase returns an error",
req: statusRequestBuilder().
Phase(velerov1api.ServerStatusRequestPhase("an-invalid-phase")).
Build(),
ServerStatusRequest(),
expected: statusRequestBuilder().
Phase(velerov1api.ServerStatusRequestPhase("an-invalid-phase")).
Build(),
ServerStatusRequest(),
expectedErrMsg: "unexpected ServerStatusRequest phase \"an-invalid-phase\"",
},
}
@@ -106,7 +152,7 @@ func TestProcess(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
client := fake.NewSimpleClientset(tc.req)
err := Process(tc.req, client.VeleroV1(), clock.NewFakeClock(now), logrus.StandardLogger())
err := Process(tc.req, client.VeleroV1(), tc.reqPluginLister, clock.NewFakeClock(now), logrus.StandardLogger())
if tc.expectedErrMsg == "" {
assert.Nil(t, err)
} else {
@@ -124,3 +170,18 @@ func TestProcess(t *testing.T) {
})
}
}
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
}